diff --git a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj index fa313a0b..f1cf6ca9 100644 --- a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj +++ b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj @@ -25,7 +25,7 @@ A456657C27CC66FD007CF70A /* DefaultButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A817430027C112D00016C921 /* DefaultButton.swift */; }; A456657E27CC77A9007CF70A /* Date+Formatted.swift in Sources */ = {isa = PBXBuildFile; fileRef = A456657D27CC77A9007CF70A /* Date+Formatted.swift */; }; A4569CB8280FB979001E3FD6 /* Presenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4569CB7280FB979001E3FD6 /* Presenter.swift */; }; - A4569CBA280FBA23001E3FD6 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4569CB9280FBA23001E3FD6 /* Result.swift */; }; + A4569CBA280FBA23001E3FD6 /* CustomResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4569CB9280FBA23001E3FD6 /* CustomResult.swift */; }; A4569CBC2810455B001E3FD6 /* CustomerServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4569CBB2810455B001E3FD6 /* CustomerServiceViewController.swift */; }; A4569CBE28105052001E3FD6 /* CustomerServiceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4569CBD28105051001E3FD6 /* CustomerServiceViewModel.swift */; }; A4569CC0281118DE001E3FD6 /* InformationTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4569CBF281118DE001E3FD6 /* InformationTextViewController.swift */; }; @@ -101,6 +101,14 @@ A4CF2C8227C73B42001B01B1 /* UIColor+AssetColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4CF2C8127C73B42001B01B1 /* UIColor+AssetColors.swift */; }; A4CF2C8E27CBA49D001B01B1 /* UIViewController+NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4CF2C8D27CBA49D001B01B1 /* UIViewController+NotificationCenter.swift */; }; A4CF2C9027CBA66F001B01B1 /* NSNotificaion.Name+CustomNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4CF2C8F27CBA66F001B01B1 /* NSNotificaion.Name+CustomNotifications.swift */; }; + A4D357DE283A3E01007819E3 /* VersionUpdating.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D357DD283A3E01007819E3 /* VersionUpdating.swift */; }; + A4D357E0283A43C7007819E3 /* UIViewController+VersionUpdating.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D357DF283A43C7007819E3 /* UIViewController+VersionUpdating.swift */; }; + A4D357E2283A4429007819E3 /* URL+Open.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D357E1283A4429007819E3 /* URL+Open.swift */; }; + A4D6EB75282E2E6700553E43 /* VersionChecking.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D6EB74282E2E6700553E43 /* VersionChecking.swift */; }; + A4D6EB77282E307900553E43 /* VersionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D6EB76282E307900553E43 /* VersionManager.swift */; }; + A4D6EB79282E432400553E43 /* OptionalBool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D6EB78282E432400553E43 /* OptionalBool.swift */; }; + A4D6EB7B2837825500553E43 /* LookUpResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D6EB7A2837825500553E43 /* LookUpResult.swift */; }; + A4D6EB7D2837901F00553E43 /* URL+AppStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D6EB7C2837901F00553E43 /* URL+AppStore.swift */; }; A4F5714727DA467900E7DF9B /* DateFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4F5714627DA467900E7DF9B /* DateFormat.swift */; }; A4F5714927DA589100E7DF9B /* NoteDatePickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4F5714827DA589100E7DF9B /* NoteDatePickerData.swift */; }; A4F5715227DB715100E7DF9B /* ColorButton.xib in Resources */ = {isa = PBXBuildFile; fileRef = A4F5715127DB715100E7DF9B /* ColorButton.xib */; }; @@ -158,7 +166,7 @@ A44E901627D5EB130053AC57 /* Happigy-bank.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Happigy-bank.xcdatamodel"; sourceTree = ""; }; A456657D27CC77A9007CF70A /* Date+Formatted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Formatted.swift"; sourceTree = ""; }; A4569CB7280FB979001E3FD6 /* Presenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presenter.swift; sourceTree = ""; }; - A4569CB9280FBA23001E3FD6 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + A4569CB9280FBA23001E3FD6 /* CustomResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomResult.swift; sourceTree = ""; }; A4569CBB2810455B001E3FD6 /* CustomerServiceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerServiceViewController.swift; sourceTree = ""; }; A4569CBD28105051001E3FD6 /* CustomerServiceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerServiceViewModel.swift; sourceTree = ""; }; A4569CBF281118DE001E3FD6 /* InformationTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InformationTextViewController.swift; sourceTree = ""; }; @@ -234,6 +242,14 @@ A4CF2C8127C73B42001B01B1 /* UIColor+AssetColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AssetColors.swift"; sourceTree = ""; }; A4CF2C8D27CBA49D001B01B1 /* UIViewController+NotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+NotificationCenter.swift"; sourceTree = ""; }; A4CF2C8F27CBA66F001B01B1 /* NSNotificaion.Name+CustomNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNotificaion.Name+CustomNotifications.swift"; sourceTree = ""; }; + A4D357DD283A3E01007819E3 /* VersionUpdating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdating.swift; sourceTree = ""; }; + A4D357DF283A43C7007819E3 /* UIViewController+VersionUpdating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+VersionUpdating.swift"; sourceTree = ""; }; + A4D357E1283A4429007819E3 /* URL+Open.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Open.swift"; sourceTree = ""; }; + A4D6EB74282E2E6700553E43 /* VersionChecking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionChecking.swift; sourceTree = ""; }; + A4D6EB76282E307900553E43 /* VersionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionManager.swift; sourceTree = ""; }; + A4D6EB78282E432400553E43 /* OptionalBool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalBool.swift; sourceTree = ""; }; + A4D6EB7A2837825500553E43 /* LookUpResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LookUpResult.swift; sourceTree = ""; }; + A4D6EB7C2837901F00553E43 /* URL+AppStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+AppStore.swift"; sourceTree = ""; }; A4F5714627DA467900E7DF9B /* DateFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormat.swift; sourceTree = ""; }; A4F5714827DA589100E7DF9B /* NoteDatePickerData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDatePickerData.swift; sourceTree = ""; }; A4F5715127DB715100E7DF9B /* ColorButton.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ColorButton.xib; sourceTree = ""; }; @@ -339,8 +355,9 @@ A4F5714627DA467900E7DF9B /* DateFormat.swift */, A4F5715727DC459B00E7DF9B /* NoteColor.swift */, A46BC1EE2800626A00C2E5B4 /* TabItem.swift */, - A4569CB9280FBA23001E3FD6 /* Result.swift */, + A4569CB9280FBA23001E3FD6 /* CustomResult.swift */, A49B25EC2812B5A400399630 /* CustomFont.swift */, + A4D6EB78282E432400553E43 /* OptionalBool.swift */, ); path = Enum; sourceTree = ""; @@ -351,6 +368,7 @@ A4F5714827DA589100E7DF9B /* NoteDatePickerData.swift */, A4F5715327DB8B6400E7DF9B /* NewNote.swift */, A819CFA027DE034F00DE8E72 /* NewBottle.swift */, + A4D6EB7A2837825500553E43 /* LookUpResult.swift */, ); path = Model; sourceTree = ""; @@ -362,6 +380,8 @@ D2AB663D27FFFCF50003AC8C /* UpdateSender.swift */, A4569CB7280FB979001E3FD6 /* Presenter.swift */, A4569CC528111EFA001E3FD6 /* InformationTextViewDataSource.swift */, + A4D6EB74282E2E6700553E43 /* VersionChecking.swift */, + A4D357DD283A3E01007819E3 /* VersionUpdating.swift */, ); path = Protocol; sourceTree = ""; @@ -429,6 +449,7 @@ A425F8B527EDF4FB00A005AB /* TagViewFlowLayout.swift */, A41DE62327F34ABB002A7669 /* UPCarouselFlowLayout.swift */, A4569CC728112657001E3FD6 /* LicenseText.swift */, + A4D6EB76282E307900553E43 /* VersionManager.swift */, ); path = Utils; sourceTree = ""; @@ -498,6 +519,9 @@ A46B10F128142F26004AB185 /* UIViewController+ObserveCustomFontChange.swift */, A46B10F32814362A004AB185 /* UIView+AllFontUsingSubviews.swift */, A46B10F52814458C004AB185 /* UILabel+ChangeOnlyFontFamily.swift */, + A4D6EB7C2837901F00553E43 /* URL+AppStore.swift */, + A4D357DF283A43C7007819E3 /* UIViewController+VersionUpdating.swift */, + A4D357E1283A4429007819E3 /* URL+Open.swift */, ); path = Extensions; sourceTree = ""; @@ -692,10 +716,12 @@ D236DB8827FDD66900D7B8F0 /* NewBottleMessageFieldViewController.swift in Sources */, A4569CCA28113F0F001E3FD6 /* NSMutableAttributedString+Hyperlink.swift in Sources */, A490AC4E27DD945E00B04CE1 /* NoteListViewController.swift in Sources */, + A4D357E0283A43C7007819E3 /* UIViewController+VersionUpdating.swift in Sources */, A8BD834727BE337900E0DE41 /* HomeView.swift in Sources */, A46B10FC28144D76004AB185 /* CustomTabBarController.swift in Sources */, A4CF2C7C27C71FF5001B01B1 /* CATransition+PopupAnimation.swift in Sources */, A49B25F42812FFB400399630 /* UILabel+BoldAndColor.swift in Sources */, + A4D6EB7D2837901F00553E43 /* URL+AppStore.swift in Sources */, A8FC07C827B3ECF00077A758 /* AppDelegate.swift in Sources */, A459003827E8D107003010A0 /* SettingsViewController.swift in Sources */, A4C1AFCE27E4F8150096CD3E /* UIView+FadeInOut.swift in Sources */, @@ -713,6 +739,7 @@ A4B2860927D9A56A008769EB /* NewNoteTextViewController.swift in Sources */, A48E183C27E7379100B44477 /* CapsuleButton.swift in Sources */, A843332127DA026D00A12A54 /* NewBottleDatePickerViewController.swift in Sources */, + A4D6EB75282E2E6700553E43 /* VersionChecking.swift in Sources */, A8FC07CA27B3ECF00077A758 /* SceneDelegate.swift in Sources */, A4F5715427DB8B6500E7DF9B /* NewNote.swift in Sources */, A466A31028018CD800D655F4 /* UIWindowScene+TopMostViewController.swift in Sources */, @@ -733,6 +760,7 @@ A46B10F028142DB1004AB185 /* UIFont+Weight.swift in Sources */, A491018D27D7358B0012DFDD /* Bottle+CoreDataClass.swift in Sources */, A41DE62E27F5F2C1002A7669 /* UIView+ZoomAnimation.swift in Sources */, + A4D6EB7B2837825500553E43 /* LookUpResult.swift in Sources */, A439F54527EF0698002851F4 /* SettingsLabelButtonCell.swift in Sources */, A41A506F27FF2B2E005381EF /* CustomTabBar.swift in Sources */, A490AC5027DD9D2E00B04CE1 /* NoteListViewModel.swift in Sources */, @@ -741,12 +769,14 @@ A4F5714927DA589100E7DF9B /* NoteDatePickerData.swift in Sources */, D2C48BFF27E9D60A006FC59E /* Gravity.swift in Sources */, A8EB5E8B27C8B087005704F2 /* UIButton+Extension.swift in Sources */, + A4D357DE283A3E01007819E3 /* VersionUpdating.swift in Sources */, A843331F27DA013800A12A54 /* NewBottleNameFieldViewController.swift in Sources */, A459003C27E9C5C9003010A0 /* CGSize+Area.swift in Sources */, A425F8B627EDF4FB00A005AB /* TagViewFlowLayout.swift in Sources */, A4C1AFC427E47DC50096CD3E /* String+NSMutableAttributedStringify.swift in Sources */, A46B10EC2813E73F004AB185 /* UserDefaults+Keys.swift in Sources */, A4C1AFC027E477180096CD3E /* NSMutableAttributedString+ColorBold.swift in Sources */, + A4D6EB79282E432400553E43 /* OptionalBool.swift in Sources */, A491018F27D735920012DFDD /* Note+CoreDataClass.swift in Sources */, A439F53D27EEFCF6002851F4 /* SettingsToggleButtonCell.swift in Sources */, A46B10EE281429AC004AB185 /* UIFont+OverrideSystemFont.swift in Sources */, @@ -754,11 +784,13 @@ A46B10FE281450F6004AB185 /* CustomNavigationController.swift in Sources */, A8BD833327BE104900E0DE41 /* HomeViewController.swift in Sources */, A499318127BF3A38009FF5A8 /* BottleViewModel.swift in Sources */, + A4D6EB77282E307900553E43 /* VersionManager.swift in Sources */, A89CBEDB27CBDD99005549F6 /* BottleCell.swift in Sources */, A44E901727D5EB130053AC57 /* Happiggy-bank.xcdatamodeld in Sources */, A4C1AFD427E5E6120096CD3E /* UITextView+ParagraphStyle.swift in Sources */, A46B10F228142F26004AB185 /* UIViewController+ObserveCustomFontChange.swift in Sources */, A466A31A2802987700D655F4 /* UIAlertAction+ConfirmAndCancel.swift in Sources */, + A4D357E2283A4429007819E3 /* URL+Open.swift in Sources */, A49B25ED2812B5A400399630 /* CustomFont.swift in Sources */, A4569CC828112657001E3FD6 /* LicenseText.swift in Sources */, A49931A527BFD20E009FF5A8 /* UIImage+AssetImages.swift in Sources */, @@ -776,7 +808,7 @@ A89CBEDD27CBDEA2005549F6 /* BottleListViewController.swift in Sources */, A439F53A27EEFC28002851F4 /* SettingsViewCell.swift in Sources */, A46B10F62814458C004AB185 /* UILabel+ChangeOnlyFontFamily.swift in Sources */, - A4569CBA280FBA23001E3FD6 /* Result.swift in Sources */, + A4569CBA280FBA23001E3FD6 /* CustomResult.swift in Sources */, A466A30C2801861100D655F4 /* ErrorViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Happiggy-bank/Happiggy-bank/Enum/Result.swift b/Happiggy-bank/Happiggy-bank/Enum/CustomResult.swift similarity index 83% rename from Happiggy-bank/Happiggy-bank/Enum/Result.swift rename to Happiggy-bank/Happiggy-bank/Enum/CustomResult.swift index 32b18af2..b93bf8e6 100644 --- a/Happiggy-bank/Happiggy-bank/Enum/Result.swift +++ b/Happiggy-bank/Happiggy-bank/Enum/CustomResult.swift @@ -1,5 +1,5 @@ // -// Result.swift +// CustomResult.swift // Happiggy-bank // // Created by sun on 2022/04/20. @@ -8,7 +8,7 @@ import Foundation /// 작업 결과를 나타냄 -enum Result { +enum CustomResult { /// 성공 case success diff --git a/Happiggy-bank/Happiggy-bank/Enum/OptionalBool.swift b/Happiggy-bank/Happiggy-bank/Enum/OptionalBool.swift new file mode 100644 index 00000000..4b326cc9 --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Enum/OptionalBool.swift @@ -0,0 +1,21 @@ +// +// OptionalBool.swift +// Happiggy-bank +// +// Created by sun on 2022/05/13. +// + +import Foundation + +/// 옵셔널 불 +enum OptionalBool { + + /// 참 + case `true` + + /// 거짓 + case `false` + + /// 닐 + case `nil` +} diff --git a/Happiggy-bank/Happiggy-bank/Extensions/NSNotificaion.Name+CustomNotifications.swift b/Happiggy-bank/Happiggy-bank/Extensions/NSNotificaion.Name+CustomNotifications.swift index 8941c621..655231ee 100644 --- a/Happiggy-bank/Happiggy-bank/Extensions/NSNotificaion.Name+CustomNotifications.swift +++ b/Happiggy-bank/Happiggy-bank/Extensions/NSNotificaion.Name+CustomNotifications.swift @@ -14,4 +14,7 @@ extension NSNotification.Name { /// 유저가 폰트 변경 시 보내는 알림 이름 static let customFontDidChange = NSNotification.Name("custom-font-did-change") + + /// 앱스토어 정보를 불러왔을 때 보내는 알림 이름 + static let appStoreInfoDidLoad = NSNotification.Name("app-store-info-did-load") } diff --git a/Happiggy-bank/Happiggy-bank/Extensions/UIViewController+VersionUpdating.swift b/Happiggy-bank/Happiggy-bank/Extensions/UIViewController+VersionUpdating.swift new file mode 100644 index 00000000..a174da37 --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Extensions/UIViewController+VersionUpdating.swift @@ -0,0 +1,80 @@ +// +// UIViewController+VersionUpdating.swift +// Happiggy-bank +// +// Created by sun on 2022/05/22. +// + +import UIKit + +extension UIViewController: VersionUpdating { + + // MARK: - Enums + + private enum StringLiteral { + /// 알림 제목 + static let appStoreOpenErrorAlertTitle = "앱스토어를 열 수 없습니다." + static let appStoreOpenErrorAlertMessage = "앱스토어에서 행복저금통을 직접 업데이트 해 주세요." + } + + + // MARK: - Properties + + /// 강제 업데이트 알림 + private var forceUpdateAlert: UIAlertController { + let confirmAction = UIAlertAction.confirmAction(title: "업데이트") { _ in + self.openAppStore() + } + let alert = UIAlertController.basic( + alertTitle: "필수 업데이트가 있습니다", + confirmAction: confirmAction + ) + + return alert + } + + /// 강제 업데이트 오류 시 앱 종료를 확인하는 알림 + private var closeAppAlert: UIAlertController { + let confirmAction = UIAlertAction.confirmAction(title: "앱 종료", style: .default) { _ in + exit(.zero) + } + + return UIAlertController.basic( + alertTitle: StringLiteral.appStoreOpenErrorAlertTitle, + alertMessage: StringLiteral.appStoreOpenErrorAlertMessage, + confirmAction: confirmAction + ) + } + + /// 앱스토어를 열 수 없을 때 나타나는 알림 + private var selfUpdateAlert: UIAlertController { + let confirmAction = UIAlertAction.confirmAction() + return UIAlertController.basic( + alertTitle: StringLiteral.appStoreOpenErrorAlertTitle, + alertMessage: StringLiteral.appStoreOpenErrorAlertMessage, + confirmAction: confirmAction + ) + } + + + // MARK: - Functions + + /// 앱스토어를 여는 메서드 + func openAppStore() { + guard let urlString = VersionManager.shared.appStoreVersionInfo?.trackViewUrl, + let url = URL(string: urlString), + UIApplication.shared.canOpenURL(url) + else { + let alertController = (VersionManager.shared.needsForcedUpdate) ? + self.closeAppAlert : self.selfUpdateAlert + return self.present(alertController, animated: true) + } + + url.open() + } + + /// 강제 업데이트 알림을 띄우는 메서드 + func presentForceUpdateAlert() { + self.present(self.forceUpdateAlert, animated: true) + } +} diff --git a/Happiggy-bank/Happiggy-bank/Extensions/URL+AppStore.swift b/Happiggy-bank/Happiggy-bank/Extensions/URL+AppStore.swift new file mode 100644 index 00000000..8dc60803 --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Extensions/URL+AppStore.swift @@ -0,0 +1,17 @@ +// +// URL+AppStore.swift +// Happiggy-bank +// +// Created by sun on 2022/05/20. +// + +import Foundation + +extension URL { + + /// 앱스토어 URL + enum AppStore { + /// 앱스토어 앱 정보 url + static let appInfo = URL(string: StringLiteral.appInfo) + } +} diff --git a/Happiggy-bank/Happiggy-bank/Extensions/URL+Open.swift b/Happiggy-bank/Happiggy-bank/Extensions/URL+Open.swift new file mode 100644 index 00000000..23126dac --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Extensions/URL+Open.swift @@ -0,0 +1,16 @@ +// +// URL+Open.swift +// Happiggy-bank +// +// Created by sun on 2022/05/22. +// + +import UIKit + +extension URL { + + /// url을 엶 + func open(completionHandler: ((Bool) -> Void)? = nil) { + UIApplication.shared.open(self, options: [:], completionHandler: completionHandler) + } +} diff --git a/Happiggy-bank/Happiggy-bank/Model/LookUpResult.swift b/Happiggy-bank/Happiggy-bank/Model/LookUpResult.swift new file mode 100644 index 00000000..0069d519 --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Model/LookUpResult.swift @@ -0,0 +1,27 @@ +// +// LookUpResult.swift +// Happiggy-bank +// +// Created by sun on 2022/05/20. +// + +import Foundation + +/// 앱스토어에서 가져온 앱 정보 +struct AppStoreVersionInfo: Decodable { + /// 배포중인 버전 + var version: String + + /// 앱 릴리즈 노트 + var releaseNotes: String + + /// 다운로드 url + var trackViewUrl: String +} + + +/// 앱스토어에서 가져온 앱 정보 배열 +struct LookUpResult: Decodable { + /// 결과 배열 + var results: [AppStoreVersionInfo] +} diff --git a/Happiggy-bank/Happiggy-bank/Protocol/Presenter.swift b/Happiggy-bank/Happiggy-bank/Protocol/Presenter.swift index 5b2b2c32..9d322e50 100644 --- a/Happiggy-bank/Happiggy-bank/Protocol/Presenter.swift +++ b/Happiggy-bank/Happiggy-bank/Protocol/Presenter.swift @@ -11,5 +11,5 @@ import Foundation protocol Presenter: AnyObject { /// 자식 뷰 컨트롤러가 종료되었음을 알리는 메서드 - func presentedViewControllerDidDismiss(withResult: Result) + func presentedViewControllerDidDismiss(withResult: CustomResult) } diff --git a/Happiggy-bank/Happiggy-bank/Protocol/VersionChecking.swift b/Happiggy-bank/Happiggy-bank/Protocol/VersionChecking.swift new file mode 100644 index 00000000..062f022a --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Protocol/VersionChecking.swift @@ -0,0 +1,29 @@ +// +// VersionChecking.swift +// Happiggy-bank +// +// Created by sun on 2022/05/13. +// + +import Foundation + +/// 앱 설치된 버전, 앱스토어 최신 버전 확인 기능 +protocol VersionChecking { + + // MARK: - Properties + + /// 업데이트 필요 여부 + var needsUpdate: OptionalBool { get } + + /// 강제 업데이트 필요 여부 + var needsForcedUpdate: Bool { get } + + /// 설치된 버전 + var installedVersion: String? { get } + + /// 최신 버전 + var latestVersion: String? { get } + + /// 앱스토어 앱 버전 확인 후 필요한 작업을 수행 + func checkVersionOnAppStore(completionHandler: ((Bool) -> Void)?) +} diff --git a/Happiggy-bank/Happiggy-bank/Protocol/VersionUpdating.swift b/Happiggy-bank/Happiggy-bank/Protocol/VersionUpdating.swift new file mode 100644 index 00000000..6e37a042 --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Protocol/VersionUpdating.swift @@ -0,0 +1,15 @@ +// +// VersionUpdating.swift +// Happiggy-bank +// +// Created by sun on 2022/05/22. +// + +import Foundation + +// 버전 업데이트 기능 +protocol VersionUpdating { + + /// 앱스토어 연결 + func openAppStore() +} diff --git a/Happiggy-bank/Happiggy-bank/SceneDelegate.swift b/Happiggy-bank/Happiggy-bank/SceneDelegate.swift index 5e976eff..8fa064c5 100644 --- a/Happiggy-bank/Happiggy-bank/SceneDelegate.swift +++ b/Happiggy-bank/Happiggy-bank/SceneDelegate.swift @@ -8,10 +8,14 @@ import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - + + // MARK: - Properties + var window: UIWindow? - - + + + // MARK: - Functions + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. @@ -28,29 +32,39 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { /// 코어데이터 에러 발생 scene.windows.first?.rootViewController = ErrorViewController(errorMessage: errorMessage) } - + func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } - + func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } - + func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } - + func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } + VersionManager.shared.checkVersionOnAppStore { [weak self] forcedUpdateIsNeeded in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .appStoreInfoDidLoad, object: nil) + + guard forcedUpdateIsNeeded + else { return } + + self?.window?.rootViewController?.topMostViewController()? + .presentForceUpdateAlert() + } + } + } + func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information @@ -58,7 +72,5 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Save changes in the application's managed object context when the application transitions to the background. PersistenceStore.shared.save() - } - - + } } diff --git a/Happiggy-bank/Happiggy-bank/Utils/Constants.swift b/Happiggy-bank/Happiggy-bank/Utils/Constants.swift index 5792fea5..635cda87 100644 --- a/Happiggy-bank/Happiggy-bank/Utils/Constants.swift +++ b/Happiggy-bank/Happiggy-bank/Utils/Constants.swift @@ -12,6 +12,9 @@ import UIKit /// 팀 메일 주소 let teamMail = "happynyamy@gmail.com" +/// 앱 번들 아이디 +private let bundleID = "Happiggy.HappiggyBank" + extension HomeViewController { /// HomeViewController 에서 설정하는 layout 에 적용할 상수값들을 모아놓은 enum @@ -795,11 +798,6 @@ extension SettingsViewController { fontSelection.rawValue: "폰트 바꾸기" ] - /// 추가 정보 딕셔너리 - static let informationText: [Int: String] = [ - appVersion.rawValue: "최신 버전을 사용 중 입니다" - ] - /// 세그웨이 아이디 딕셔너리 static let segueIdentifier: [Int: String] = [ customerService.rawValue: segueIdentifier(for: customerService), @@ -809,6 +807,19 @@ extension SettingsViewController { // MARK: - Functions + /// 버전 정보 셀에 나타낼 문자열 + static func versionString(forStatus updateIsNeeded: OptionalBool) -> String { + if updateIsNeeded == .true { + return "업데이트가 필요합니다" + } + + if updateIsNeeded == .false { + return "최신 버전을 사용 중 입니다" + } + + return .empty + } + /// 케이스 이름을 카멜케이스로 변환 private static func nameInCamelCase(_ contentCase: Content) -> String { var contentCase = "\(contentCase.self)" @@ -1281,3 +1292,23 @@ extension CustomTabBarController { static let tabBarItemSize = UIFont.smallSystemFontSize - 2 } } + +extension VersionManager { + + /// info 딕셔너리 키 + enum InfoDictionaryKey { + + static let CFBundleShortVersionString = "CFBundleShortVersionString" + + } +} + +extension URL { + + /// 문자열 + enum StringLiteral { + + /// 앱스토어 앱 정보 url + static let appInfo = "https://itunes.apple.com/kr/lookup?bundleId=\(bundleID)" + } +} diff --git a/Happiggy-bank/Happiggy-bank/Utils/VersionManager.swift b/Happiggy-bank/Happiggy-bank/Utils/VersionManager.swift new file mode 100644 index 00000000..0438db6c --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Utils/VersionManager.swift @@ -0,0 +1,110 @@ +// +// VersionManager.swift +// Happiggy-bank +// +// Created by sun on 2022/05/13. +// + + +import UIKit + +/// 앱의 버전을 관리 +final class VersionManager: VersionChecking { + + // MARK: - Properties + + /// 싱글턴 + static let shared = VersionManager() + + var needsUpdate: OptionalBool { + guard let installedVersion = self.installedVersion, + let latestVersion = self.latestVersion + else { return .nil } + + return (installedVersion == latestVersion) ? .false : .true + } + + var needsForcedUpdate: Bool { + guard var installedVersion = self.installedVersion?.compactMap({ Int(String($0)) }), + let minimumRequiredVersion = self.parseMinimumRequiredVersion(), + !minimumRequiredVersion.isEmpty + else { return false } + + installedVersion.append(.zero) + + for (installed, minimumRequired) in zip(installedVersion, minimumRequiredVersion) { + guard installed != minimumRequired + else { continue } + + return installed < minimumRequired ? true : false + } + + return false + } + + var installedVersion: String? { + Bundle.main.infoDictionary?[InfoDictionaryKey.CFBundleShortVersionString] as? String + } + + var latestVersion: String? + + /// 앱스토어에서 가져온 앱 정보 + private(set) var appStoreVersionInfo: AppStoreVersionInfo? { + didSet { + self.latestVersion = self.appStoreVersionInfo?.version + } + } + + + // MARK: - Inits + + /// 싱글턴 패턴 사용을 위해 초기화 프라이빗으로 변경 + private init() {} + + + // MARK: - Functions + + func checkVersionOnAppStore(completionHandler: ((Bool) -> Void)?) { + guard let url = URL.AppStore.appInfo + else { return completionHandler?(false) ?? () } + + let task = URLSession.shared.dataTask(with: url) { data, _, _ in + self.processAppInfoRequest(data: data) + completionHandler?(self.needsForcedUpdate) + } + task.resume() + } + + /// 앱스토어 엔드포인트로부터 받은 데이터를 디코딩 + private func processAppInfoRequest(data: Data?) { + guard let jsonData = data, + let lookUpResult = try? JSONDecoder().decode(LookUpResult.self, from: jsonData), + let appStoreVersionInfo = lookUpResult.results.first + else { return self.appStoreVersionInfo = nil } + + self.appStoreVersionInfo = appStoreVersionInfo + } + + // MARK: 강제 업데이트가 발생하는 경우 *해당 버전부터 이후 모든 버전에 반드시* + // 1. 릴리즈 노트 "맨 마지막"에 + // 2. "버전 x.x.x" 를 포함한 안내 문구(e.g. 버전 x.x.x 이상을 유지해주새오)가 포함되어야 함 + // - "버전"이랑 "x.x.x" 사이에 공백 필수 + // - 이 뒤로는 버전이라는 단어 사용 금지 + // "버전" 이라는 단어를 기준으로 스플릿해서 맨 마지막 문자열을 가져오고, + // 해당 문자열을 "." 을 기준으로 다시 스플릿해서 파싱해서 숫자만 가져오는 방식이기 때문에 + // 릴리즈 노트 맨 마지막에 2의 문구를 적어주고, 해당 문구의 "버전" 단어 뒤에 최초 숫자는 반드시 지원 버전 숫자여야 함 + // 안되는 예시 (다 막줄이라고 가정) + // - 버전 1.2.3 이상을 유지해주새오! 버전 업 필수!!! -> 마지막 "버전" 문자를 기준으로 파싱하기 때문에 금지 + // - 버전 근데 제 생일은 2월 5일이에요 그리고 1.2.3 이상 유지해주새오! -> "버전" 다음에 쓸데없는 숫자 들어가서 안됨 + /// 릴리즈 노트로부터 최소 지원 버전 파싱 + private func parseMinimumRequiredVersion() -> [Int]? { + self.appStoreVersionInfo?.releaseNotes + .components(separatedBy: "버전") + .last? + .components(separatedBy: " ") + .filter { $0.first?.isNumber == true} + .first? + .split(separator: ".") + .compactMap { Int(String($0)) } + } +} diff --git a/Happiggy-bank/Happiggy-bank/ViewController/BottleNameEditViewController.swift b/Happiggy-bank/Happiggy-bank/ViewController/BottleNameEditViewController.swift index 9ca066e3..fac31a09 100644 --- a/Happiggy-bank/Happiggy-bank/ViewController/BottleNameEditViewController.swift +++ b/Happiggy-bank/Happiggy-bank/ViewController/BottleNameEditViewController.swift @@ -179,7 +179,7 @@ final class BottleNameEditViewController: UIViewController { } /// 종료 시 호출하는 메서드 - private func dismiss(withResult result: Result) { + private func dismiss(withResult result: CustomResult) { self.resignFirstResponder() self.dismiss(animated: true) self.delegate.presentedViewControllerDidDismiss(withResult: result) diff --git a/Happiggy-bank/Happiggy-bank/ViewController/HomeViewController.swift b/Happiggy-bank/Happiggy-bank/ViewController/HomeViewController.swift index 9747fdbb..ec92601d 100644 --- a/Happiggy-bank/Happiggy-bank/ViewController/HomeViewController.swift +++ b/Happiggy-bank/Happiggy-bank/ViewController/HomeViewController.swift @@ -372,7 +372,7 @@ final class HomeViewController: UIViewController { // MARK: - Presenter extension HomeViewController: Presenter { - func presentedViewControllerDidDismiss(withResult: Result) { + func presentedViewControllerDidDismiss(withResult: CustomResult) { self.bottleViewController.restoreStateBeforeAlertOrModalDidAppear() } } diff --git a/Happiggy-bank/Happiggy-bank/ViewController/SettingsViewController.swift b/Happiggy-bank/Happiggy-bank/ViewController/SettingsViewController.swift index f46accf4..e0eae43a 100644 --- a/Happiggy-bank/Happiggy-bank/ViewController/SettingsViewController.swift +++ b/Happiggy-bank/Happiggy-bank/ViewController/SettingsViewController.swift @@ -32,7 +32,7 @@ final class SettingsViewController: UIViewController { self.registerCells() self.configureNavigationBar() - self.observeCustomFontChange(selector: #selector(customFontDidChange(_:))) + self.addObservers() } @@ -48,6 +48,21 @@ final class SettingsViewController: UIViewController { self.updateFontSelectionCellFontName(to: customFont) } + /// 백그라운드에서 포어그라운드로 돌아올 때 버전 셀 업데이트 + @objc private func updateVersionCell() { + + let indexPath = IndexPath(row: Content.appVersion.rawValue, section: .zero) + guard let versionCell = self.tableView.cellForRow( + at: indexPath + ) as? SettingsLabelButtonCell + else { return } + + versionCell.informationLabel.attributedText = self.viewModel.informationText( + forContentAt: indexPath + ) + versionCell.buttonImageView.isHidden = (VersionManager.shared.needsUpdate != .true) + } + // MARK: - Functions @@ -77,6 +92,12 @@ final class SettingsViewController: UIViewController { cell.informationLabel.text = customFont.displayName } + + /// 옵저버들 추가 + private func addObservers() { + self.observeCustomFontChange(selector: #selector(customFontDidChange(_:))) + self.observe(selector: #selector(self.updateVersionCell), name: .appStoreInfoDidLoad) + } } @@ -103,14 +124,20 @@ extension SettingsViewController: UITableViewDataSource { return cell } - if indexPath.row == Content.appVersion.rawValue || - indexPath.row == Content.customerService.rawValue || - indexPath.row == Content.fontSelection.rawValue { + if indexPath.row == Content.appVersion.rawValue { + return self.labelButtonCell( + inTableView: tableView, + indexPath: indexPath, + navigationButtonIsHidden: VersionManager.shared.needsUpdate != .true + ) + } + if indexPath.row == Content.customerService.rawValue || + indexPath.row == Content.fontSelection.rawValue { return self.labelButtonCell( inTableView: tableView, indexPath: indexPath, - navigationButtonIsHidden: indexPath.row == Content.appVersion.rawValue + navigationButtonIsHidden: false ) } @@ -186,6 +213,11 @@ extension SettingsViewController: UITableViewDelegate { tableView.deselectRow(at: indexPath, animated: false) + if indexPath.row == Content.appVersion.rawValue, + VersionManager.shared.needsUpdate == .true { + return self.openAppStore() + } + guard let segueIdentifier = self.viewModel.segueIdentifier(forContentAt: indexPath) else { return } diff --git a/Happiggy-bank/Happiggy-bank/ViewModel/SettingsViewModel.swift b/Happiggy-bank/Happiggy-bank/ViewModel/SettingsViewModel.swift index 13244422..2b9688f8 100644 --- a/Happiggy-bank/Happiggy-bank/ViewModel/SettingsViewModel.swift +++ b/Happiggy-bank/Happiggy-bank/ViewModel/SettingsViewModel.swift @@ -12,6 +12,7 @@ final class SettingsViewModel { typealias Content = SettingsViewController.Content + // MARK: - Properties /// 현재 폰트 @@ -39,21 +40,31 @@ final class SettingsViewModel { /// 해당 칸의 설명 리턴 func informationText(forContentAt indexPath: IndexPath) -> NSMutableAttributedString? { - let text = Content.informationText[indexPath.row]?.nsMutableAttributedStringify() if indexPath.row == Content.appVersion.rawValue { - return text?.color(color: .customTint) + return self.appVersionInformation() } if indexPath.row == Content.fontSelection.rawValue { return self.customFont.displayName.nsMutableAttributedStringify() } - return text + return nil } /// 세그웨이가 있다면 해당 세그웨이의 아이디 리턴 func segueIdentifier(forContentAt indexPath: IndexPath) -> String? { Content.segueIdentifier[indexPath.row] } + + /// 업데이트 필요 여부에 따른 적절한 문자열 리턴 + private func appVersionInformation() -> NSMutableAttributedString? { + var text = VersionManager.shared.installedVersion + + if VersionManager.shared.needsUpdate != .nil { + text = Content.versionString(forStatus: VersionManager.shared.needsUpdate) + } + + return text?.nsMutableAttributedStringify().color(color: .customTint) + } }