관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] app store로 이동 (버전 업데이트, 다시 돌아온 경우 처리, 현재 버전, 최신 버전 가져오는 방법) 본문

iOS 응용 (swift)

[iOS - swift] app store로 이동 (버전 업데이트, 다시 돌아온 경우 처리, 현재 버전, 최신 버전 가져오는 방법)

jake-kim 2021. 3. 15. 23:45

현재 버전과 앱스토어에 있는 최신버전 확인 방법

  • 현재 버전 정보 가져오기
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
  • 상수로 설정
    cf) bundle id와 buildNumber는 아래와 같이 획득
struct Constants {

    struct System {
        static let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
        static let bundleIdentifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String
        static let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
    }
}
  • 최신버전 확인 방법
    func latestVersion() -> String? {
        let appleID = "이곳에 Apple ID"
        guard let url = URL(string: "http://itunes.apple.com/lookup?id=\(appleID)"),
              let data = try? Data(contentsOf: url),
              let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any],
              let results = json["results"] as? [[String: Any]],
              let appStoreVersion = results[0]["version"] as? String else {
            return nil
        }
        return appStoreVersion
    }
  • 버전 비교 방법: String에 내장된 compare(:options:) 메소드 이용
let oldVersion = "0.12.0"
let latestVersion = "0.13.1"
let compareResult = oldVersion.compare(latestVersion, options: .numeric)
switch compareResult {
case .orderedAscending:
    print("<")
case .orderedDescending:
    print(">")
case .orderedSame:
    print("==")
}

appstore connect에 존재하는 Apple ID

  • 상수로 설정
    - 주의: let data = try? Data(contentsOf: url) 이 부분이 존재하므로 인터넷과 연결되지 않으면 nil 반환
struct Constants {

    struct System {
        static let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
        static let bundleIdentifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String
        static let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
        static func latestVersion() -> String? {
            let appleID = "이곳에 appleID"
            guard let url = URL(string: "http://itunes.apple.com/lookup?id=\(appleID)"),
                  let data = try? Data(contentsOf: url),
                  let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any],
                  let results = json["results"] as? [[String: Any]],
                  let appStoreVersion = results[0]["version"] as? String else {
                return nil
            }
            return appStoreVersion
        }
    }
}

App Store Connect에서 Apple ID 확인

  • Apple ID를 이용하여 앱스토어로 이동 가능 - 복사

Apple ID를 이용하여 앱스토어로 이동

  • AppStore를 open하지 못하는 error 케이스 정의
enum AppstoreOpenError: Error {
    case invalidAppStoreURL
    case cantOpenAppStoreURL
}
  • openAppStore 함수 정의: 실패 시 위에서 정의한 오류 표출
    func openAppStore(urlStr: String) -> Result<Void, AppstoreOpenError> {

        guard let url = URL(string: urlStr) else {
            print("invalid app store url")
            return .failure(.invalidAppStoreURL)
        }

        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
            return .success(())
        } else {
            print("can't open app store url")
            return .failure(.cantOpenAppStoreURL)
        }
    }
  • App store로 이동하는 url 정의
    static let appStoreOpenUrlString = "itms-apps://itunes.apple.com/app/apple-store/1548711244" // 1548711244는 Apple ID
  • openAppStore에 appStoreOpenurlString을 넣으면 이동 완료

* 주의사항: real device에서 동작하며, simulator로 할 경우 "This app is not allowed to query for scheme itms-apps" 에러메시지 표출


응용 - MVVM에서 앱스토어 이동 후 다시 화면으로 돌아올 때 처리

* RxSwift, RxCocoa 이용

  • ViewModel에 foreground로 진입할 경우 등록한 postTask를 처리하는 함수 정의 (최초 한번 바인딩할 함수)
    - PostTaskManager: 특정 task를 등록해 놓는 PostTaskManager 개념 참고
    private func registerNotification() {
        NotificationCenter.default.rx.notification(UIApplication.willEnterForegroundNotification)
            .asDriverOnErrorNever()
            .drive(onNext: { [weak self] _ in
                self?.processPostTask()
            }).disposed(by: bag)
    }
  • processPostTask() 함수 정의
private func processPostTask() {
    guard let tasks = dependencies.postTaskManager.postTasks(postTastTarget: .splash) else {
        return
    }
    for task in tasks {
        switch task {
        case .checkVersion:
            checkVersion()
        default:
            break
        }
    }
    dependencies.postTaskManager.remove(for: .splash)
}
  • ViewModel에 output 변수, 함수 정의
// output 변수
let openAppStore = PublishRelay<String>()

// 초기
func viewWillAppear() {
    checkVersion()
}

private func checkVersion() {
    // Something to do

    // require update
    updateVersion()
}

...

// 버전 업데인트
private func updateVersion() {
    openAppStore.accept(Constants.appStoreOpenUrlString)
}
  • VC에서 문자열을 받아서 appstore 오픈, result는 ViewModel에서 처리
    private func setupOutputBinding() {
        viewModel.openAppStore.asDriverOnErrorNever()
            .drive(onNext: { [weak self] in
                let result = self?.openAppStore(urlStr: $0)
                self?.viewModel.processAppstoreOpen(result: result)
            }).disposed(by: bag)
    }
  • ViewModel에서, 앱스토어를 여는데 성공했으면 task 등록하여 foreground로 돌아온 경우 version을 다시 체크하도록 정의
    func processAppstoreOpen(result: Result<Void, AppstoreOpenError>?) {
        guard let result = result else {
            return
        }
        switch result {
        case .success:
            dependencies.postTaskManager.register(
                postTask: .init(
                    target: .splash,
                    task: .checkVersion
                )
            )
        case .failure(let error):
            print(error.errorString)
            // 일시적인 오류
        }
    }

 

Comments