Skip to content

Commit 80707e5

Browse files
authored
Merge pull request #261 from thingineeer/#259---개인코스공유기능
[Feat] #259 - 개인 코스 공유 기능을 추가 하였습니다.
2 parents ff17e8b + 9d3a764 commit 80707e5

File tree

9 files changed

+187
-100
lines changed

9 files changed

+187
-100
lines changed

Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,7 +1703,7 @@
17031703
CODE_SIGN_IDENTITY = "Apple Development";
17041704
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
17051705
CODE_SIGN_STYLE = Manual;
1706-
CURRENT_PROJECT_VERSION = 2024.0208.0315;
1706+
CURRENT_PROJECT_VERSION = 2024.0312.0041;
17071707
DEVELOPMENT_TEAM = "";
17081708
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8Q4H7X3Q58;
17091709
GENERATE_INFOPLIST_FILE = NO;
@@ -1726,7 +1726,7 @@
17261726
PRODUCT_BUNDLE_IDENTIFIER = "com.runnect.Runnect-iOS";
17271727
PRODUCT_NAME = "$(TARGET_NAME)";
17281728
PROVISIONING_PROFILE_SPECIFIER = "";
1729-
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.runnect.Runnect-iOS";
1729+
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.runnect-Runnect-iOS";
17301730
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
17311731
SUPPORTS_MACCATALYST = NO;
17321732
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -1747,7 +1747,7 @@
17471747
CODE_SIGN_IDENTITY = "Apple Development";
17481748
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
17491749
CODE_SIGN_STYLE = Manual;
1750-
CURRENT_PROJECT_VERSION = 2024.0208.0315;
1750+
CURRENT_PROJECT_VERSION = 2024.0312.0041;
17511751
DEVELOPMENT_TEAM = "";
17521752
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8Q4H7X3Q58;
17531753
GENERATE_INFOPLIST_FILE = NO;
@@ -1770,7 +1770,7 @@
17701770
PRODUCT_BUNDLE_IDENTIFIER = "com.runnect.Runnect-iOS";
17711771
PRODUCT_NAME = "$(TARGET_NAME)";
17721772
PROVISIONING_PROFILE_SPECIFIER = "";
1773-
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.runnect.Runnect-iOS";
1773+
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.runnect-Runnect-iOS";
17741774
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
17751775
SUPPORTS_MACCATALYST = NO;
17761776
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;

Runnect-iOS/Runnect-iOS/Global/Extension/UIKit+/UIViewController+.swift

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import UIKit
99

1010
import SnapKit
11+
import FirebaseDynamicLinks
1112

1213
extension UIViewController {
1314

@@ -89,10 +90,84 @@ extension UIViewController {
8990
}
9091

9192
extension UIViewController {
93+
94+
/**
95+
### Description: 공유 기능에 해당하는 정보를 넣어줍니다.
96+
2025년 8월 25일에 동적링크는 만기 됩니다.
97+
98+
- courseTitle : 타이틀 이름
99+
- courseId : 코스 아이디
100+
- courseImageURL : 코스 사진
101+
- minimumAppVersion : 공유 기능을 사용할 수 있는 최소 버전
102+
- descriptionText : 내용
103+
- parameter : 공유 기능에 필요한 파라미터
104+
*/
105+
/// 공유 기능 메서드
106+
///
107+
func shareCourse(
108+
courseTitle: String,
109+
courseId: Int,
110+
courseImageURL: String,
111+
minimumAppVersion: String,
112+
descriptionText: String? = nil,
113+
parameter: String
114+
) {
115+
let dynamicLinksDomainURIPrefix = "https://rnnt.page.link"
116+
let courseParameter = parameter
117+
guard let link = URL(string: "\(dynamicLinksDomainURIPrefix)/?\(courseParameter)=\(courseId)") else {
118+
print("Invalid link.")
119+
return
120+
}
121+
122+
guard let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: dynamicLinksDomainURIPrefix) else {
123+
print("Failed to create link builder.")
124+
return
125+
}
126+
127+
linkBuilder.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.runnect.Runnect-iOS")
128+
linkBuilder.iOSParameters?.appStoreID = "1663884202"
129+
linkBuilder.iOSParameters?.minimumAppVersion = minimumAppVersion
130+
131+
linkBuilder.androidParameters = DynamicLinkAndroidParameters(packageName: "com.runnect.runnect")
132+
133+
linkBuilder.socialMetaTagParameters = DynamicLinkSocialMetaTagParameters()
134+
linkBuilder.socialMetaTagParameters?.imageURL = URL(string: courseImageURL)
135+
linkBuilder.socialMetaTagParameters?.title = courseTitle
136+
linkBuilder.socialMetaTagParameters?.descriptionText = descriptionText ?? ""
137+
138+
linkBuilder.shorten { [weak self] url, _, error in
139+
guard let shortDynamicLink = url?.absoluteString else {
140+
if let error = error {
141+
print("Error shortening dynamic link: \(error)")
142+
}
143+
return
144+
}
145+
146+
print("Short URL is: \(shortDynamicLink)")
147+
DispatchQueue.main.async {
148+
self?.presentShareActivity(with: shortDynamicLink)
149+
}
150+
}
151+
}
152+
153+
private func presentShareActivity(with url: String) {
154+
let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
155+
activityVC.popoverPresentationController?.sourceView = self.view
156+
self.present(activityVC, animated: true, completion: nil)
157+
}
158+
}
159+
160+
extension UIViewController {
161+
/**
162+
- Description: 뷰컨에서 GA(구글 애널리틱스) 스크린 , 버튼 이벤트 사용 사는 메서드 입니다.
163+
*/
164+
165+
/// 스크린 이벤트
92166
func analyze(screenName: String) {
93167
GAManager.shared.logEvent(eventType: .screen(screenName: screenName))
94168
}
95169

170+
/// 버튼 이벤트
96171
func analyze(buttonName: String) {
97172
GAManager.shared.logEvent(eventType: .button(buttonName: buttonName))
98173
}

Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ import FirebaseDynamicLinks
1212
import FirebaseCore
1313
import FirebaseCoreInternal
1414

15+
// 들어온 링크가 공유된 코스인지, 개인 보관함에 있는 코스인지 나타내기 위한 타입입니다.
16+
enum CourseType {
17+
case publicCourse, privateCourse
18+
}
19+
1520
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
1621

1722
var window: UIWindow?
1823

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

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

4145
if let incomingURL = userActivity.webpageURL {
42-
let linkHandled = DynamicLinks.dynamicLinks()
46+
DynamicLinks.dynamicLinks()
4347
.handleUniversalLink(incomingURL) { dynamicLink, error in
4448

45-
if let courseId = self.handleDynamicLink(dynamicLink) {
46-
guard let _ = (scene as? UIWindowScene) else { return }
49+
if let (courseType, courseId) = self.handleDynamicLink(dynamicLink) {
50+
guard let windowScene = scene as? UIWindowScene else { return }
51+
let window = UIWindow(windowScene: windowScene)
52+
let navigationController = UINavigationController()
4753

48-
if let windowScene = scene as? UIWindowScene {
49-
let window = UIWindow(windowScene: windowScene)
50-
54+
switch courseType {
55+
case .publicCourse:
5156
let courseDetailVC = CourseDetailVC()
52-
courseDetailVC.getUploadedCourseDetail(courseId: Int(courseId))
53-
54-
let tabBarController = TabBarController()
55-
let navigationController = UINavigationController(rootViewController: tabBarController)
56-
navigationController.navigationBar.isHidden = true
57+
courseDetailVC.getUploadedCourseDetail(courseId: courseId)
5758
navigationController.pushViewController(courseDetailVC, animated: false)
58-
59-
// 코스 발견 view 로 이동
60-
tabBarController.selectedIndex = 2
61-
window.rootViewController = navigationController
62-
window.makeKeyAndVisible()
63-
self.window = window
64-
59+
case .privateCourse:
60+
let privateCourseDetailVC = RunningWaitingVC()
61+
privateCourseDetailVC.setData(courseId: courseId, publicCourseId: nil)
62+
navigationController.pushViewController(privateCourseDetailVC, animated: false)
6563
}
64+
65+
let tabBarController = TabBarController()
66+
navigationController.navigationBar.isHidden = true
67+
navigationController.viewControllers = [tabBarController, navigationController.viewControllers.last].compactMap { $0 }
68+
69+
tabBarController.selectedIndex = 2
70+
window.rootViewController = navigationController
71+
window.makeKeyAndVisible()
72+
self.window = window
6673
}
6774
}
6875
}
@@ -106,17 +113,26 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
106113
// to restore the scene back to its current state.
107114
}
108115

109-
func handleDynamicLink(_ dynamicLink: DynamicLink?) -> String? {
116+
func handleDynamicLink(_ dynamicLink: DynamicLink?) -> (courseType: CourseType, courseId: Int)? {
110117
if let dynamicLink = dynamicLink, let url = dynamicLink.url,
111118
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
112119
let queryItems = components.queryItems {
120+
var courseId: Int?
121+
var courseType: CourseType?
122+
113123
for item in queryItems {
114-
if item.name == "courseId", let courseId = item.value {
115-
// 동적링크 핸들링 하여 courseId 추출
116-
117-
return courseId
124+
if item.name == "courseId", let id = item.value, let idInt = Int(id) {
125+
courseId = idInt
126+
courseType = .publicCourse
127+
} else if item.name == "privateCourseId", let id = item.value, let idInt = Int(id) {
128+
courseId = idInt
129+
courseType = .privateCourse
118130
}
119131
}
132+
133+
if let courseId = courseId, let courseType = courseType {
134+
return (courseType, courseId)
135+
}
120136
}
121137
return nil
122138
}

Runnect-iOS/Runnect-iOS/Info.plist

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
<key>CFBundlePackageType</key>
2020
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
2121
<key>CFBundleShortVersionString</key>
22-
<string>2.0.0</string>
22+
<string>2.0.1</string>
23+
<key>ITSAppUsesNonExemptEncryption</key>
24+
<false/>
2325
<key>CFBundleURLTypes</key>
2426
<array>
2527
<dict>
@@ -44,7 +46,7 @@
4446
</dict>
4547
</array>
4648
<key>CFBundleVersion</key>
47-
<string>2024.0208.0315</string>
49+
<string>2024.0312.0041</string>
4850
<key>LSApplicationQueriesSchemes</key>
4951
<array>
5052
<string>kakaokompassauth</string>

Runnect-iOS/Runnect-iOS/Network/Dto/CourseStorageDto/ResponseDto/PrivateCourseResponseDto.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ struct PrivateCourseResponseDto: Codable {
1717

1818
struct PrivateCourse: Codable {
1919
let id: Int
20+
let isNowUser: Bool?
2021
let title: String
2122
let image, createdAt: String
2223
let distance: Float?

Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift

Lines changed: 11 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// CourseDetailVC.swift
33
// Runnect-iOS
44
//
5-
// Created by 몽이 누나 on 2023/01/05.
5+
// Created by 이명진 on 2023/10/09.
66
//
77

88
import UIKit
@@ -13,9 +13,6 @@ import NMapsMap
1313
import Moya
1414
import SafariServices
1515
import KakaoSDKCommon
16-
import FirebaseCore
17-
import FirebaseDynamicLinks
18-
import KakaoSDKShare
1916
import KakaoSDKTemplate
2017
import DropDown
2118

@@ -167,68 +164,26 @@ extension CourseDetailVC {
167164

168165
scrapCourse(scrapTF: !sender.isSelected)
169166
delegate?.didUpdateScrapState(publicCourseId: publicCourseId, isScrapped: !sender.isSelected) /// 코스 발견 UI Update 부분
170-
marathonDelegate?.didUpdateMarathonScrapState(publicCourseId: publicCourseId, isScrapped: !sender.isSelected) // 마라톤 코스 UI Update 부분
171-
172-
/// print("CourseDetailVC 스크랩 탭🔥publicCourseId=\(publicCourseId), isScrapped은 \(!sender.isSelected) 요렇게 변경 ")
167+
marathonDelegate?.didUpdateMarathonScrapState(publicCourseId: publicCourseId, isScrapped: !sender.isSelected) // 마라톤 코스 UI
173168
}
174169

175170
@objc private func shareButtonTapped() {
176171
guard let model = self.uploadedCourseDetailModel else {
177172
return
178173
}
179174

180-
analyze(buttonName: GAEvent.Button.clickShare)
181-
182175
let publicCourse = model.publicCourse
183-
let title = publicCourse.title
184-
let courseId = publicCourse.id // primaryKey
185-
let description = publicCourse.description
186-
let courseImage = publicCourse.image
187-
188-
let dynamicLinksDomainURIPrefix = "https://rnnt.page.link"
189-
guard let link = URL(string: "\(dynamicLinksDomainURIPrefix)/?courseId=\(courseId)") else {
190-
return
191-
}
192-
193-
print("‼️link= \(link)")
194-
195-
guard let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: dynamicLinksDomainURIPrefix) else {
196-
return
197-
}
198-
199-
linkBuilder.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.runnect.Runnect-iOS")
200-
linkBuilder.iOSParameters?.appStoreID = "1663884202"
201-
linkBuilder.iOSParameters?.minimumAppVersion = "1.0.4"
202-
203-
linkBuilder.androidParameters = DynamicLinkAndroidParameters(packageName: "com.runnect.runnect")
204176

205-
linkBuilder.socialMetaTagParameters = DynamicLinkSocialMetaTagParameters()
206-
linkBuilder.socialMetaTagParameters?.imageURL = URL(string: courseImage)
207-
linkBuilder.socialMetaTagParameters?.title = title
208-
linkBuilder.socialMetaTagParameters?.descriptionText = description
209-
210-
guard let longDynamicLink = linkBuilder.url else {
211-
return
212-
}
213-
print("The long URL is: \(longDynamicLink)")
177+
analyze(buttonName: GAEvent.Button.clickShare)
214178

215-
/// 짧은 Dynamic Link로 변환하는 부분 입니다.
216-
linkBuilder.shorten { [weak self] url, _, error in // warning 파라미터 와일드 카드
217-
guard let shortDynamicLink = url?.absoluteString else {
218-
if let error = error {
219-
print("❌Error shortening dynamic link: \(error)")
220-
}
221-
return
222-
}
223-
224-
print("🔥The short URL is: \(shortDynamicLink)")
225-
226-
DispatchQueue.main.async {
227-
let activityVC = UIActivityViewController(activityItems: [shortDynamicLink], applicationActivities: nil)
228-
activityVC.popoverPresentationController?.sourceView = self?.view
229-
self?.present(activityVC, animated: true, completion: nil)
230-
}
231-
}
179+
self.shareCourse(
180+
courseTitle: publicCourse.title,
181+
courseId: publicCourse.id,
182+
courseImageURL: publicCourse.image,
183+
minimumAppVersion: "1.0.4",
184+
descriptionText: publicCourse.description,
185+
parameter: "courseId"
186+
)
232187
}
233188

234189
@objc private func pushToUserProfileVC() {
@@ -479,10 +434,6 @@ extension CourseDetailVC {
479434
$0.width.height.equalTo(37)
480435
}
481436

482-
// profileNameLabel.snp.makeConstraints {
483-
// $0.leading.equalTo(profileImageView.snp.trailing).offset(12)
484-
// }
485-
486437
firstHorizontalDivideLine.snp.makeConstraints {
487438
$0.top.equalTo(mapImageView.snp.bottom).offset(62)
488439
$0.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(14)

Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,8 @@ extension CourseStorageVC {
107107
guard let self = self else { return }
108108
analyze(buttonName: GAEvent.Button.clickScrapPageStartCourse) // 코스 발견_스크랩코스 상세페이지 시작하기 Event
109109

110-
let title = self.privateCourseList[index].title
111110
let runningWaitingVC = RunningWaitingVC()
112-
runningWaitingVC.setData(courseId: self.privateCourseList[index].id, publicCourseId: nil, courseTitle: title)
111+
runningWaitingVC.setData(courseId: self.privateCourseList[index].id, publicCourseId: nil)
113112

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

0 commit comments

Comments
 (0)