174 Stimmen

Prüfen, ob meine App eine neue Version im AppStore hat

Ich möchte manuell prüfen, ob es neue Updates für meine App gibt, während der Benutzer sich in ihr befindet, und ihn auffordern, die neue Version herunterzuladen. Kann ich dies tun, indem ich die Version meiner App im App Store - programmatisch - überprüfe?

112voto

datinc Punkte 3304

Hier ist ein einfaches Codeschnipsel, das Ihnen mitteilt, ob die aktuelle Version anders ist

-(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;
}

Stellen Sie sicher, dass die neue Version, die Sie in iTunes eingeben, mit der Version der App übereinstimmt, die Sie veröffentlichen. I

4 Stimmen

Super Lösung, die ich je gefunden habe +1

0 Stimmen

Diese Lösung wird nicht als am besten oder Super betrachtet, da es einen FLAW darin hat. z.B. wenn Sie Live-Version 1.0 auf Speicher haben und Sie neue Version aktualisieren möchten, haben Sie 1.1 eingereicht, also wenn es genehmigt wird, wird es gut funktionieren, aber im Fall, wenn Sie sehen, dass 1.1 irgendeinen Fehler oder Absturz hat oder Apple es abgelehnt hat, dann müssen Sie auf 1.2 aktualisieren, so dass nach 1.0 Ihre Version auf Speicher 1.2 ist, auf diese Weise gibt es immer TRUE zurück und zeigt an, dass sogar der Benutzer die neueste Version herunterlädt.

1 Stimmen

@MobeenAfzal, ich glaube, Sie haben die Frage und die Lösung nicht verstanden. Die obige Lösung vergleicht die aktuelle Version mit der Version im Store. Wenn sie nicht übereinstimmen, wird JA gewählt, andernfalls wird NEIN zurückgegeben. Unabhängig von der Historie im App Store gibt die obige Methode JA zurück, wenn die aktuelle Version sich von der Version im App Store unterscheidet. Sobald der Benutzer ein Update durchführt, ist die aktuelle Version gleich der Version im App Store. Die obige Methode sollte immer JA zurückgeben, wenn die Version des Benutzers 1.0 und die Version des App Stores 1.2 ist.

91voto

juanjo Punkte 3517

Swift 3-Version:

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: "https://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
}

Ich denke, es ist besser, einen Fehler auszulösen, anstatt false zurückzugeben, in diesem Fall habe ich eine VersionError erstellt, aber es kann eine andere Sie definieren oder NSError sein

enum VersionError: Error {
    case invalidResponse, invalidBundleInfo
}

Denken Sie auch daran, diese Funktion von einem anderen Thread aus aufzurufen, denn wenn die Verbindung langsam ist, kann sie den aktuellen Thread blockieren.

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

Update

URLSession verwenden:

Anstelle der Verwendung von Data(contentsOf: url) und einen Thread blockieren, können wir 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: "https://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
}

Beispiel:

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

1 Stimmen

Diese Antwort stellt ihre Anfrage synchron. Das bedeutet, dass Ihre Anwendung bei einer schlechten Verbindung minutenlang unbrauchbar sein kann, bis die Anfrage zurückkommt.

7 Stimmen

Da bin ich anderer Meinung, DispatchQueue.global() eine Hintergrund-Warteschlange einrichtet, werden die Daten in diese Warteschlange geladen und erst dann wieder in die Haupt-Warteschlange gestellt, wenn die Daten geladen sind.

0 Stimmen

Huch. Irgendwie habe ich den zweiten Codeschnipsel übersehen. Leider scheint es, ich kann nicht die downvote entfernen, bis Ihre Antwort wieder bearbeitet wird :-( BTW - Angesichts dataWithContentsOfURL: geht tatsächlich durch NSURLConnection's synchrone Anrufe, die wiederum nur einen asynchronen Thread starten und blockieren, wäre es wahrscheinlich weniger Overhead, nur die asynchrone NSURLSession Aufrufe verwenden. Sie würden sogar rufen Sie zurück auf den Haupt-Thread, sobald Sie fertig sind.

30voto

budiDino Punkte 12048

Vereinfacht eine tolle Antwort in diesem Thread gepostet. U Swift 4 y 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: ".").compactMap { Int($0) }
        let arrayLocal = versionLocal.split(separator: ".").compactMap { Int($0) }

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

        // check each segment of the version
        for (localSegment, storeSegment) in zip(arrayLocal, arrayStore) {
          if localSegment < storeSegment {
            callback(true)
            return
          }
        }
      }
      callback(false) // no new version or failed to fetch app store version
    }
  }

}

Und sie dann zu benutzen:

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

5 Stimmen

Meine Anwendung ist live im Store, aber dieselbe API liefert keine Versionsinformationen. Antwort : { "resultCount":0, "results": [] }

0 Stimmen

Nur eine Anmerkung zum Versionsvergleich hinzufügen, ich würde es vorziehen, let serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare(localVersion, options: .numeric) == .orderedDescending anstatt das . durch leer zu ersetzen.

0 Stimmen

@Chaitu danke für den Vorschlag. Am Ende habe ich den Vergleichsteil des Codes umgeschrieben

25voto

Vasco Punkte 767

Aktualisiert die Mauersegler 4 Code von Anup Gupta

Ich habe einige Änderungen vorgenommen an dieser Code . Jetzt werden die Funktionen von einer Hintergrund-Warteschlange aufgerufen, da die Verbindung langsam sein und daher den Haupt-Thread blockieren kann.

Ich habe auch den CFBundleName optional gemacht, da die vorgestellte Version "CFBundleDisplayName" hatte, was in meiner Version wahrscheinlich nicht funktionierte. Wenn es also nicht vorhanden ist, stürzt es nicht ab, sondern es wird nur der App-Name in der Meldung nicht angezeigt.

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 ""
        }
    }
}

Ich plädiere dafür, auch die Bestätigungsschaltfläche hinzuzufügen:

AppUpdater.shared.showUpdate(withConfirmation: true)

Oder rufen Sie es wie folgt auf, um die Option "Aktualisierung erzwingen" zu aktivieren:

AppUpdater.shared.showUpdate(withConfirmation: false)

0 Stimmen

Haben Sie eine Idee, wie man das testen kann? Wenn es nicht richtig funktioniert, ist die einzige Möglichkeit, es zu debuggen, irgendwie eine ältere Version zu debuggen, als im App Store ist.

2 Stimmen

Ach, vergessen Sie die Frage. Ich kann meine lokale Version einfach so ändern, dass sie "älter" ist.

1 Stimmen

Ich bin von Ihrem Code beeindruckt, @Vasco. Nur eine einfache Frage, warum haben Sie "http" statt https in dieser URL verwendet?

16voto

Yago Zardo Punkte 421

Da ich mit demselben Problem konfrontiert war, habe ich die Antwort zur Verfügung gestellt von Mario Hendricks . Unfornatelly, wenn ich versuchte, aply seinen Code auf mein Projekt, XCode hat über Casting Probleme beschweren, sagen "MDLMaterialProperty hat keine subscript Mitglieder". Sein Code versuchte, dieses MDLMaterial... als Typ der Konstante "lookupResult" festzulegen, wodurch das Casting auf "Int" jedes Mal fehlschlug. Meine Lösung bestand darin, eine Typ-Annotation für meine Variable in NSDictionary um mir darüber im Klaren zu sein, welche Art von Wert ich brauche. Damit konnte ich auf den Wert "Version" zugreifen, den ich brauchte.

Beobachten: Für diese YOURBUNDLEID die Sie aus Ihrem Xcode-Projekt abrufen können: .... " Ziele > Allgemein > Identität > Bündelbezeichner "

Hier ist also mein Code mit einigen Vereinfachungen:

  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
}

Alle Vorschläge zur Verbesserung dieses Codes sind willkommen!

1 Stimmen

Diese Antwort stellt ihre Anfrage synchron. Das bedeutet, dass Ihre Anwendung bei einer schlechten Verbindung minutenlang unbrauchbar sein kann, bis die Anfrage zurückkommt.

0 Stimmen

@Yago Zardo bitte verwenden Sie die Vergleichsfunktion, sonst wenn Benutzer app.apple getestet Zeit Anzeige aktualisieren alertview oder Apple ablehnen Ihre App hochladen

0 Stimmen

Hallo @Jigar, danke für den Rat. Ich verwende diese Methode derzeit nicht mehr in meiner Anwendung, weil wir jetzt alles auf unserem Server versionieren. Wie auch immer, könntest du besser erklären, was du gesagt hast? Ich habe es nicht verstanden und es sieht wirklich gut aus, das zu wissen. Vielen Dank im Voraus.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X