Skip to content

Commit

Permalink
Merge pull request #261 from thingineeer/#259---개인코스공유기능
Browse files Browse the repository at this point in the history
[Feat] #259 - 개인 코스 공유 기능을 추가 하였습니다.
  • Loading branch information
thingineeer authored Mar 11, 2024
2 parents ff17e8b + 9d3a764 commit 80707e5
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 100 deletions.
8 changes: 4 additions & 4 deletions Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1703,7 +1703,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2024.0208.0315;
CURRENT_PROJECT_VERSION = 2024.0312.0041;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8Q4H7X3Q58;
GENERATE_INFOPLIST_FILE = NO;
Expand All @@ -1726,7 +1726,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.runnect.Runnect-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.runnect.Runnect-iOS";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.runnect-Runnect-iOS";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
Expand All @@ -1747,7 +1747,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2024.0208.0315;
CURRENT_PROJECT_VERSION = 2024.0312.0041;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8Q4H7X3Q58;
GENERATE_INFOPLIST_FILE = NO;
Expand All @@ -1770,7 +1770,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.runnect.Runnect-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.runnect.Runnect-iOS";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.runnect-Runnect-iOS";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import UIKit

import SnapKit
import FirebaseDynamicLinks

extension UIViewController {

Expand Down Expand Up @@ -89,10 +90,84 @@ extension UIViewController {
}

extension UIViewController {

/**
### Description: 공유 기능에 해당하는 정보를 넣어줍니다.
2025년 8월 25일에 동적링크는 만기 됩니다.
- courseTitle : 타이틀 이름
- courseId : 코스 아이디
- courseImageURL : 코스 사진
- minimumAppVersion : 공유 기능을 사용할 수 있는 최소 버전
- descriptionText : 내용
- parameter : 공유 기능에 필요한 파라미터
*/
/// 공유 기능 메서드
///
func shareCourse(
courseTitle: String,
courseId: Int,
courseImageURL: String,
minimumAppVersion: String,
descriptionText: String? = nil,
parameter: String
) {
let dynamicLinksDomainURIPrefix = "https://rnnt.page.link"
let courseParameter = parameter
guard let link = URL(string: "\(dynamicLinksDomainURIPrefix)/?\(courseParameter)=\(courseId)") else {
print("Invalid link.")
return
}

guard let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: dynamicLinksDomainURIPrefix) else {
print("Failed to create link builder.")
return
}

linkBuilder.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.runnect.Runnect-iOS")
linkBuilder.iOSParameters?.appStoreID = "1663884202"
linkBuilder.iOSParameters?.minimumAppVersion = minimumAppVersion

linkBuilder.androidParameters = DynamicLinkAndroidParameters(packageName: "com.runnect.runnect")

linkBuilder.socialMetaTagParameters = DynamicLinkSocialMetaTagParameters()
linkBuilder.socialMetaTagParameters?.imageURL = URL(string: courseImageURL)
linkBuilder.socialMetaTagParameters?.title = courseTitle
linkBuilder.socialMetaTagParameters?.descriptionText = descriptionText ?? ""

linkBuilder.shorten { [weak self] url, _, error in
guard let shortDynamicLink = url?.absoluteString else {
if let error = error {
print("Error shortening dynamic link: \(error)")
}
return
}

print("Short URL is: \(shortDynamicLink)")
DispatchQueue.main.async {
self?.presentShareActivity(with: shortDynamicLink)
}
}
}

private func presentShareActivity(with url: String) {
let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = self.view
self.present(activityVC, animated: true, completion: nil)
}
}

extension UIViewController {
/**
- Description: 뷰컨에서 GA(구글 애널리틱스) 스크린 , 버튼 이벤트 사용 사는 메서드 입니다.
*/

/// 스크린 이벤트
func analyze(screenName: String) {
GAManager.shared.logEvent(eventType: .screen(screenName: screenName))
}

/// 버튼 이벤트
func analyze(buttonName: String) {
GAManager.shared.logEvent(eventType: .button(buttonName: buttonName))
}
Expand Down
64 changes: 40 additions & 24 deletions Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ import FirebaseDynamicLinks
import FirebaseCore
import FirebaseCoreInternal

// 들어온 링크가 공유된 코스인지, 개인 보관함에 있는 코스인지 나타내기 위한 타입입니다.
enum CourseType {
case publicCourse, privateCourse
}

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

guard let _ = (scene as? UIWindowScene) else { return }
Expand All @@ -39,30 +43,33 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {

if let incomingURL = userActivity.webpageURL {
let linkHandled = DynamicLinks.dynamicLinks()
DynamicLinks.dynamicLinks()
.handleUniversalLink(incomingURL) { dynamicLink, error in

if let courseId = self.handleDynamicLink(dynamicLink) {
guard let _ = (scene as? UIWindowScene) else { return }
if let (courseType, courseId) = self.handleDynamicLink(dynamicLink) {
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
let navigationController = UINavigationController()

if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)

switch courseType {
case .publicCourse:
let courseDetailVC = CourseDetailVC()
courseDetailVC.getUploadedCourseDetail(courseId: Int(courseId))

let tabBarController = TabBarController()
let navigationController = UINavigationController(rootViewController: tabBarController)
navigationController.navigationBar.isHidden = true
courseDetailVC.getUploadedCourseDetail(courseId: courseId)
navigationController.pushViewController(courseDetailVC, animated: false)

// 코스 발견 view 로 이동
tabBarController.selectedIndex = 2
window.rootViewController = navigationController
window.makeKeyAndVisible()
self.window = window

case .privateCourse:
let privateCourseDetailVC = RunningWaitingVC()
privateCourseDetailVC.setData(courseId: courseId, publicCourseId: nil)
navigationController.pushViewController(privateCourseDetailVC, animated: false)
}

let tabBarController = TabBarController()
navigationController.navigationBar.isHidden = true
navigationController.viewControllers = [tabBarController, navigationController.viewControllers.last].compactMap { $0 }

tabBarController.selectedIndex = 2
window.rootViewController = navigationController
window.makeKeyAndVisible()
self.window = window
}
}
}
Expand Down Expand Up @@ -106,17 +113,26 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// to restore the scene back to its current state.
}

func handleDynamicLink(_ dynamicLink: DynamicLink?) -> String? {
func handleDynamicLink(_ dynamicLink: DynamicLink?) -> (courseType: CourseType, courseId: Int)? {
if let dynamicLink = dynamicLink, let url = dynamicLink.url,
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems {
var courseId: Int?
var courseType: CourseType?

for item in queryItems {
if item.name == "courseId", let courseId = item.value {
// 동적링크 핸들링 하여 courseId 추출

return courseId
if item.name == "courseId", let id = item.value, let idInt = Int(id) {
courseId = idInt
courseType = .publicCourse
} else if item.name == "privateCourseId", let id = item.value, let idInt = Int(id) {
courseId = idInt
courseType = .privateCourse
}
}

if let courseId = courseId, let courseType = courseType {
return (courseType, courseId)
}
}
return nil
}
Expand Down
6 changes: 4 additions & 2 deletions Runnect-iOS/Runnect-iOS/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>2.0.0</string>
<string>2.0.1</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand All @@ -44,7 +46,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>2024.0208.0315</string>
<string>2024.0312.0041</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>kakaokompassauth</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct PrivateCourseResponseDto: Codable {

struct PrivateCourse: Codable {
let id: Int
let isNowUser: Bool?
let title: String
let image, createdAt: String
let distance: Float?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// CourseDetailVC.swift
// Runnect-iOS
//
// Created by 몽이 누나 on 2023/01/05.
// Created by 이명진 on 2023/10/09.
//

import UIKit
Expand All @@ -13,9 +13,6 @@ import NMapsMap
import Moya
import SafariServices
import KakaoSDKCommon
import FirebaseCore
import FirebaseDynamicLinks
import KakaoSDKShare
import KakaoSDKTemplate
import DropDown

Expand Down Expand Up @@ -167,68 +164,26 @@ extension CourseDetailVC {

scrapCourse(scrapTF: !sender.isSelected)
delegate?.didUpdateScrapState(publicCourseId: publicCourseId, isScrapped: !sender.isSelected) /// 코스 발견 UI Update 부분
marathonDelegate?.didUpdateMarathonScrapState(publicCourseId: publicCourseId, isScrapped: !sender.isSelected) // 마라톤 코스 UI Update 부분

/// print("CourseDetailVC 스크랩 탭🔥publicCourseId=\(publicCourseId), isScrapped은 \(!sender.isSelected) 요렇게 변경 ")
marathonDelegate?.didUpdateMarathonScrapState(publicCourseId: publicCourseId, isScrapped: !sender.isSelected) // 마라톤 코스 UI
}

@objc private func shareButtonTapped() {
guard let model = self.uploadedCourseDetailModel else {
return
}

analyze(buttonName: GAEvent.Button.clickShare)

let publicCourse = model.publicCourse
let title = publicCourse.title
let courseId = publicCourse.id // primaryKey
let description = publicCourse.description
let courseImage = publicCourse.image

let dynamicLinksDomainURIPrefix = "https://rnnt.page.link"
guard let link = URL(string: "\(dynamicLinksDomainURIPrefix)/?courseId=\(courseId)") else {
return
}

print("‼️link= \(link)")

guard let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: dynamicLinksDomainURIPrefix) else {
return
}

linkBuilder.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.runnect.Runnect-iOS")
linkBuilder.iOSParameters?.appStoreID = "1663884202"
linkBuilder.iOSParameters?.minimumAppVersion = "1.0.4"

linkBuilder.androidParameters = DynamicLinkAndroidParameters(packageName: "com.runnect.runnect")

linkBuilder.socialMetaTagParameters = DynamicLinkSocialMetaTagParameters()
linkBuilder.socialMetaTagParameters?.imageURL = URL(string: courseImage)
linkBuilder.socialMetaTagParameters?.title = title
linkBuilder.socialMetaTagParameters?.descriptionText = description

guard let longDynamicLink = linkBuilder.url else {
return
}
print("The long URL is: \(longDynamicLink)")
analyze(buttonName: GAEvent.Button.clickShare)

/// 짧은 Dynamic Link로 변환하는 부분 입니다.
linkBuilder.shorten { [weak self] url, _, error in // warning 파라미터 와일드 카드
guard let shortDynamicLink = url?.absoluteString else {
if let error = error {
print("❌Error shortening dynamic link: \(error)")
}
return
}

print("🔥The short URL is: \(shortDynamicLink)")

DispatchQueue.main.async {
let activityVC = UIActivityViewController(activityItems: [shortDynamicLink], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = self?.view
self?.present(activityVC, animated: true, completion: nil)
}
}
self.shareCourse(
courseTitle: publicCourse.title,
courseId: publicCourse.id,
courseImageURL: publicCourse.image,
minimumAppVersion: "1.0.4",
descriptionText: publicCourse.description,
parameter: "courseId"
)
}

@objc private func pushToUserProfileVC() {
Expand Down Expand Up @@ -479,10 +434,6 @@ extension CourseDetailVC {
$0.width.height.equalTo(37)
}

// profileNameLabel.snp.makeConstraints {
// $0.leading.equalTo(profileImageView.snp.trailing).offset(12)
// }

firstHorizontalDivideLine.snp.makeConstraints {
$0.top.equalTo(mapImageView.snp.bottom).offset(62)
$0.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(14)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,8 @@ extension CourseStorageVC {
guard let self = self else { return }
analyze(buttonName: GAEvent.Button.clickScrapPageStartCourse) // 코스 발견_스크랩코스 상세페이지 시작하기 Event

let title = self.privateCourseList[index].title
let runningWaitingVC = RunningWaitingVC()
runningWaitingVC.setData(courseId: self.privateCourseList[index].id, publicCourseId: nil, courseTitle: title)
runningWaitingVC.setData(courseId: self.privateCourseList[index].id, publicCourseId: nil)

/// 코스 이름을 여기서 가져오는 로직
runningWaitingVC.hidesBottomBarWhenPushed = true
Expand Down
Loading

0 comments on commit 80707e5

Please sign in to comment.