Skip to content

Commit 17d52b4

Browse files
committed
[Feat/#243] 앱 강제 업데이트 기능 구현
- 업데이트 판단: 현재 기기의 앱 버전과 앱스토어 버전을 비교해서, 앱스토어 버전이 크다면 업데이트 - 업데이트 체크 시점: 앱 시작될 경우, ScenePhase가 .active될 경우 - 알랏 틴트 색상 강제 적용
1 parent d204079 commit 17d52b4

File tree

3 files changed

+81
-3
lines changed

3 files changed

+81
-3
lines changed

ILSANG.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
607FCC332C01AA7300BB2D04 /* SubmitAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FCC322C01AA7300BB2D04 /* SubmitAlertView.swift */; };
7171
608EBF9F2C39C17F00B862CC /* Dictionary++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608EBF9E2C39C17F00B862CC /* Dictionary++.swift */; };
7272
608EBFA12C39C5BB00B862CC /* String++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608EBFA02C39C5BB00B862CC /* String++.swift */; };
73+
6090A5F52D53AC3300A01429 /* AppVersionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6090A5F42D53AC3300A01429 /* AppVersionManager.swift */; };
7374
60924DC92C48C00E00221072 /* Quest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60924DC82C48C00E00221072 /* Quest.swift */; };
7475
60943FE62C0A38FB00C8A714 /* ApprovalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60943FE52C0A38FB00C8A714 /* ApprovalViewModel.swift */; };
7576
609A76D92CFC50C7009F4567 /* QuestImageWithTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 609A76D82CFC50C7009F4567 /* QuestImageWithTagView.swift */; };
@@ -213,6 +214,7 @@
213214
607FCC322C01AA7300BB2D04 /* SubmitAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubmitAlertView.swift; sourceTree = "<group>"; };
214215
608EBF9E2C39C17F00B862CC /* Dictionary++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary++.swift"; sourceTree = "<group>"; };
215216
608EBFA02C39C5BB00B862CC /* String++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String++.swift"; sourceTree = "<group>"; };
217+
6090A5F42D53AC3300A01429 /* AppVersionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionManager.swift; sourceTree = "<group>"; };
216218
60924DC82C48C00E00221072 /* Quest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quest.swift; sourceTree = "<group>"; };
217219
60943FE52C0A38FB00C8A714 /* ApprovalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApprovalViewModel.swift; sourceTree = "<group>"; };
218220
609A76D82CFC50C7009F4567 /* QuestImageWithTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestImageWithTagView.swift; sourceTree = "<group>"; };
@@ -337,6 +339,7 @@
337339
600D83A22C7B1F7E005C93E2 /* Manager */ = {
338340
isa = PBXGroup;
339341
children = (
342+
6090A5F42D53AC3300A01429 /* AppVersionManager.swift */,
340343
600D83A32C7B1FA4005C93E2 /* PaginationManager.swift */,
341344
6053345B2D31182500599159 /* XpLevelCalculator.swift */,
342345
);
@@ -950,6 +953,7 @@
950953
A1868C442C09B6C50020BF16 /* ChallengeDetailView.swift in Sources */,
951954
6044B71D2C32B6A4002C13A0 /* UIImage++.swift in Sources */,
952955
607FCBD42BFA648000BB2D04 /* Tab.swift in Sources */,
956+
6090A5F52D53AC3300A01429 /* AppVersionManager.swift in Sources */,
953957
6011891F2C3E94380070F2AD /* AuthUser.swift in Sources */,
954958
60299E312CBFFF160039663F /* TransferableUIImage.swift in Sources */,
955959
A1FF91722C257E9A00F03E4B /* User.swift in Sources */,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// AppVersionManager.swift
3+
// ILSANG
4+
//
5+
// Created by Lee Jinhee on 2/5/25.
6+
//
7+
8+
import UIKit
9+
10+
final class AppVersionManager {
11+
static let shared = AppVersionManager()
12+
private let appStoreOpenUrlStr = "itms-apps://itunes.apple.com/app/apple-store/6504427618"
13+
14+
private init() { }
15+
16+
func isUpdateAvailable() async throws -> Bool {
17+
guard let info = Bundle.main.infoDictionary,
18+
let currentVersion = info["CFBundleShortVersionString"] as? String, // 현재 버전 가져오기
19+
let identifier = info["CFBundleIdentifier"] as? String, // 앱 번들아이디 가져오기
20+
let url = URL(string: "http://itunes.apple.com/kr/lookup?bundleId=\(identifier)") else {
21+
throw VersionError.invalidBundleInfo
22+
}
23+
24+
let (data, _) = try await URLSession.shared.data(from: url) // 비동기 네트워크 요청
25+
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], // 네트워크 응답 파싱
26+
let result = (json["results"] as? [[String: Any]])?.first,
27+
let appStoreVersion = result["version"] as? String else {
28+
throw VersionError.invalidResponse
29+
}
30+
31+
return currentVersion.compare(appStoreVersion, options: .numeric) == .orderedAscending // 현재 앱의 버전과 앱스토어 버전을 비교해서 업데이트 가능 여부 반환
32+
}
33+
34+
// 앱 스토어로 이동
35+
func openAppStore() {
36+
guard let url = URL(string: appStoreOpenUrlStr) else { return }
37+
if UIApplication.shared.canOpenURL(url) {
38+
UIApplication.shared.open(url, options: [:], completionHandler: nil)
39+
}
40+
}
41+
}
42+
43+
enum VersionError: Error {
44+
case invalidResponse, invalidBundleInfo
45+
}

ILSANG/Sources/Views/ILSANGApp.swift

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@ struct ILSANGApp: App {
1414
@State private var isTutorialVisible = Bool()
1515
@State private var isSplashScreenVisible = true
1616

17+
// 강제 업데이트 관련
18+
@State private var needUpdate = false
19+
@Environment(\.scenePhase) var scenePhase
20+
1721
init() {
18-
setTabBarAppearance()
22+
setAppearance()
1923
}
2024

2125
var body: some Scene {
@@ -32,6 +36,16 @@ struct ILSANGApp: App {
3236
})
3337
}
3438
}
39+
.alert("업데이트 알림", isPresented: $needUpdate, actions: {
40+
Button("업데이트") { AppVersionManager.shared.openAppStore() }
41+
}, message: {
42+
Text("일상이 새롭게 업데이트되었습니다!\n변화된 일상을 만나보세요.")
43+
})
44+
.onChange(of: scenePhase, { _, newValue in
45+
if newValue == .active {
46+
Task { await checkAndUpdateVersionIfNeeded() }
47+
}
48+
})
3549
.onChange(of: isLogin, { _, newValue in // 로그인 후 튜토리얼 UI 표시
3650
if newValue {
3751
isTutorialVisible = true
@@ -53,17 +67,32 @@ struct ILSANGApp: App {
5367
let _ = await UserService.shared.login()
5468
}
5569

56-
// 3. 스플래시 화면 종료
70+
// 3. 업데이트 확인
71+
await checkAndUpdateVersionIfNeeded()
72+
73+
// 4. 스플래시 화면 종료
5774
isSplashScreenVisible = false
5875
}
5976

60-
func setTabBarAppearance() {
77+
private func checkAndUpdateVersionIfNeeded() async {
78+
do {
79+
needUpdate = try await AppVersionManager.shared.isUpdateAvailable()
80+
} catch {
81+
Log("버전 확인 중 오류 발생: \(error)")
82+
}
83+
}
84+
85+
func setAppearance() {
86+
// 탭바
6187
let appearance = UITabBarAppearance()
6288
appearance.backgroundColor = UIColor(.white)
6389
appearance.shadowColor = UIColor(.grayDD)
6490
appearance.stackedItemPositioning = .centered
6591
UITabBar.appearance().standardAppearance = appearance
6692
UITabBar.appearance().scrollEdgeAppearance = appearance
93+
94+
// 틴트 컬러 적용
95+
UIView.appearance().tintColor = UIColor(named: "AccentColor") // 파란색으로 버튼이 보여지는 문제 방지 (Alert에서 문제 발생)
6796
}
6897
}
6998

0 commit comments

Comments
 (0)