[ios] 내 앱에 AppStore에 새 버전이 있는지 확인

사용자가 앱에있는 동안 내 앱에 대한 새 업데이트가 있는지 수동으로 확인하고 새 버전을 다운로드하라는 메시지를 표시하고 싶습니다. 앱 스토어에서 내 앱의 버전을 프로그래밍 방식으로 확인하여이를 수행 할 수 있습니까?



답변

다음은 현재 버전이 다른지 알려주는 간단한 코드 스 니펫입니다.

-(BOOL) needsUpdate{
    NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString* appID = infoDictionary[@"CFBundleIdentifier"];
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/lookup?bundleId=%@", appID]];
    NSData* data = [NSData dataWithContentsOfURL:url];
    NSDictionary* lookup = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

    if ([lookup[@"resultCount"] integerValue] == 1){
        NSString* appStoreVersion = lookup[@"results"][0][@"version"];
        NSString* currentVersion = infoDictionary[@"CFBundleShortVersionString"];
        if (![appStoreVersion isEqualToString:currentVersion]){
            NSLog(@"Need to update [%@ != %@]", appStoreVersion, currentVersion);
            return YES;
        }
    }
    return NO;
}

참고 : iTunes에 새 버전을 입력 할 때이 버전이 출시중인 앱의 버전과 일치하는지 확인하십시오. 그렇지 않은 경우 위의 코드는 사용자가 업데이트하더라도 항상 YES를 반환합니다.


답변

Swift 3 버전 :

func isUpdateAvailable() throws -> Bool {
    guard let info = Bundle.main.infoDictionary,
        let currentVersion = info["CFBundleShortVersionString"] as? String,
        let identifier = info["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
        throw VersionError.invalidBundleInfo
    }
    let data = try Data(contentsOf: url)
    guard let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any] else {
        throw VersionError.invalidResponse
    }
    if let result = (json["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String {
        return version != currentVersion
    }
    throw VersionError.invalidResponse
}

나는 false를 반환하는 대신 오류를 던지는 것이 더 낫다고 생각합니다.

enum VersionError: Error {
    case invalidResponse, invalidBundleInfo
}

또한 연결이 느리면 현재 스레드를 차단할 수 있으므로 다른 스레드에서이 함수를 호출하는 것을 고려하십시오.

DispatchQueue.global().async {
    do {
        let update = try self.isUpdateAvailable()
        DispatchQueue.main.async {
            // show alert
        }
    } catch {
        print(error)
    }
}

최신 정보

URLSession 사용 :

Data(contentsOf: url)스레드 를 사용 하고 차단하는 대신 다음을 사용할 수 있습니다 URLSession.

func isUpdateAvailable(completion: @escaping (Bool?, Error?) -> Void) throws -> URLSessionDataTask {
    guard let info = Bundle.main.infoDictionary,
        let currentVersion = info["CFBundleShortVersionString"] as? String,
        let identifier = info["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
            throw VersionError.invalidBundleInfo
    }
    Log.debug(currentVersion)
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        do {
            if let error = error { throw error }
            guard let data = data else { throw VersionError.invalidResponse }
            let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any]
            guard let result = (json?["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String else {
                throw VersionError.invalidResponse
            }
            completion(version != currentVersion, nil)
        } catch {
            completion(nil, error)
        }
    }
    task.resume()
    return task
}

예:

_ = try? isUpdateAvailable { (update, error) in
    if let error = error {
        print(error)
    } else if let update = update {
        print(update)
    }
}


답변

그의 링크에 대한 Steve Moser에게 감사드립니다. 여기 내 코드가 있습니다.

NSString *appInfoUrl = @"http://itunes.apple.com/en/lookup?bundleId=XXXXXXXXX";

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:appInfoUrl]];
[request setHTTPMethod:@"GET"];

NSURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection  sendSynchronousRequest:request returningResponse: &response error: &error];
NSString *output = [NSString stringWithCString:[data bytes] length:[data length]];

NSError *e = nil;
NSData *jsonData = [output dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error: &e];

NSString *version = [[[jsonDict objectForKey:@"results"] objectAtIndex:0] objectForKey:@"version"];


답변

같은 문제에 직면했기 때문에 Mario Hendricks가 제공 한 답변을 찾았습니다 . 내 프로젝트에 그의 코드를 적용하려고 할 때 XCode는 “MDLMaterialProperty에 첨자 멤버가 없습니다”라고 말하는 캐스팅 문제에 대해 불평했습니다. 그의 코드는이 MDLMaterial을 상수 “lookupResult”의 유형으로 설정하려고했기 때문에 “Int”로의 캐스팅이 매번 실패하게되었습니다. 내 솔루션은 내 변수에 대한 유형 주석을 NSDictionary 에 제공하는 것입니다 필요한 값의 종류를 명확하게하는 것이 었습니다. 이를 통해 필요한 “버전”값에 액세스 할 수 있습니다.

Obs :이 YOURBUNDLEID의 경우 Xcode 프로젝트에서 가져올 수 있습니다 …. ” Targets> General> Identity> Bundle Identifier

그래서 여기에 몇 가지 단순화 된 코드가 있습니다.

  func appUpdateAvailable() -> Bool
{
    let storeInfoURL: String = "http://itunes.apple.com/lookup?bundleId=YOURBUNDLEID"
    var upgradeAvailable = false
    // Get the main bundle of the app so that we can determine the app's version number
    let bundle = NSBundle.mainBundle()
    if let infoDictionary = bundle.infoDictionary {
        // The URL for this app on the iTunes store uses the Apple ID for the  This never changes, so it is a constant
        let urlOnAppStore = NSURL(string: storeInfoURL)
        if let dataInJSON = NSData(contentsOfURL: urlOnAppStore!) {
            // Try to deserialize the JSON that we got
            if let dict: NSDictionary = try? NSJSONSerialization.JSONObjectWithData(dataInJSON, options: NSJSONReadingOptions.AllowFragments) as! [String: AnyObject] {
                if let results:NSArray = dict["results"] as? NSArray {
                    if let version = results[0].valueForKey("version") as? String {
                        // Get the version number of the current version installed on device
                        if let currentVersion = infoDictionary["CFBundleShortVersionString"] as? String {
                            // Check if they are the same. If not, an upgrade is available.
                            print("\(version)")
                            if version != currentVersion {
                                upgradeAvailable = true
                            }
                        }
                    }
                }
            }
        }
    }
    return upgradeAvailable
}

이 코드의 개선을위한 모든 제안을 환영합니다!


답변

ATAppUpdater를 사용 하십시오 . 스레드로부터 안전하고 빠르며 1 줄입니다. 사용자 작업을 추적하려는 경우 위임 메서드도 있습니다.

다음은 예입니다.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[ATAppUpdater sharedUpdater] showUpdateWithConfirmation]; // 1 line of code
    // or
    [[ATAppUpdater sharedUpdater] showUpdateWithForce]; // 1 line of code

   return YES;
}

선택적 대리자 메서드 :

- (void)appUpdaterDidShowUpdateDialog;
- (void)appUpdaterUserDidLaunchAppStore;
- (void)appUpdaterUserDidCancel;


답변

이 스레드에 게시 된 훌륭한 답변을 단순화 했습니다 . 사용 Swift 4하고 Alamofire.

import Alamofire

class VersionCheck {

  public static let shared = VersionCheck()

  func isUpdateAvailable(callback: @escaping (Bool)->Void) {
    let bundleId = Bundle.main.infoDictionary!["CFBundleIdentifier"] as! String
    Alamofire.request("https://itunes.apple.com/lookup?bundleId=\(bundleId)").responseJSON { response in
      if let json = response.result.value as? NSDictionary, let results = json["results"] as? NSArray, let entry = results.firstObject as? NSDictionary, let versionStore = entry["version"] as? String, let versionLocal = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
        let arrayStore = versionStore.split(separator: ".")
        let arrayLocal = versionLocal.split(separator: ".")

        if arrayLocal.count != arrayStore.count {
          callback(true) // different versioning system
        }

        // check each segment of the version
        for (key, value) in arrayLocal.enumerated() {
          if Int(value)! < Int(arrayStore[key])! {
            callback(true)
          }
        }
      }
      callback(false) // no new version or failed to fetch app store version
    }
  }

}

그리고 그것을 사용하려면 :

VersionCheck.shared.isUpdateAvailable() { hasUpdates in
  print("is update available: \(hasUpdates)")
}


답변

에서 신속한 4 코드를 업데이트했습니다.Anup Gupta

이 코드 를 약간 변경했습니다. . 이제 연결이 느려서 메인 스레드를 차단할 수 있으므로 백그라운드 큐에서 함수가 호출됩니다.

또한 제시된 버전에 “CFBundleDisplayName”이 있었기 때문에 CFBundleName을 선택적으로 만들었습니다.이 버전은 아마도 제 버전에서 작동하지 않았을 것입니다. 이제 존재하지 않으면 충돌하지 않지만 경고에 앱 이름이 표시되지 않습니다.

import UIKit

enum VersionError: Error {
    case invalidBundleInfo, invalidResponse
}

class LookupResult: Decodable {
    var results: [AppInfo]
}

class AppInfo: Decodable {
    var version: String
    var trackViewUrl: String
}

class AppUpdater: NSObject {

    private override init() {}
    static let shared = AppUpdater()

    func showUpdate(withConfirmation: Bool) {
        DispatchQueue.global().async {
            self.checkVersion(force : !withConfirmation)
        }
    }

    private  func checkVersion(force: Bool) {
        let info = Bundle.main.infoDictionary
        if let currentVersion = info?["CFBundleShortVersionString"] as? String {
            _ = getAppInfo { (info, error) in
                if let appStoreAppVersion = info?.version{
                    if let error = error {
                        print("error getting app store version: ", error)
                    } else if appStoreAppVersion == currentVersion {
                        print("Already on the last app version: ",currentVersion)
                    } else {
                        print("Needs update: AppStore Version: \(appStoreAppVersion) > Current version: ",currentVersion)
                        DispatchQueue.main.async {
                            let topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
                            topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
                        }
                    }
                }
            }
        }
    }

    private func getAppInfo(completion: @escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
        guard let identifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
            let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
                DispatchQueue.main.async {
                    completion(nil, VersionError.invalidBundleInfo)
                }
                return nil
        }
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            do {
                if let error = error { throw error }
                guard let data = data else { throw VersionError.invalidResponse }
                let result = try JSONDecoder().decode(LookupResult.self, from: data)
                guard let info = result.results.first else { throw VersionError.invalidResponse }

                completion(info, nil)
            } catch {
                completion(nil, error)
            }
        }
        task.resume()
        return task
    }
}

extension UIViewController {
    @objc fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
        let appName = Bundle.appName()

        let alertTitle = "New Version"
        let alertMessage = "\(appName) Version \(Version) is available on AppStore."

        let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)

        if !Force {
            let notNowButton = UIAlertAction(title: "Not Now", style: .default)
            alertController.addAction(notNowButton)
        }

        let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
            guard let url = URL(string: AppURL) else {
                return
            }
            if #available(iOS 10.0, *) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            } else {
                UIApplication.shared.openURL(url)
            }
        }

        alertController.addAction(updateButton)
        self.present(alertController, animated: true, completion: nil)
    }
}
extension Bundle {
    static func appName() -> String {
        guard let dictionary = Bundle.main.infoDictionary else {
            return ""
        }
        if let version : String = dictionary["CFBundleName"] as? String {
            return version
        } else {
            return ""
        }
    }
}

확인 버튼도 추가해야합니다.

AppUpdater.shared.showUpdate(withConfirmation: true)

또는 다음과 같이 호출하여 강제 업데이트 옵션을 설정하십시오.

AppUpdater.shared.showUpdate(withConfirmation: false)