Controlla se la mia app ha una nuova versione su AppStore


112

Vorrei controllare manualmente se ci sono nuovi aggiornamenti per la mia app mentre l'utente è in essa e chiedergli di scaricare la nuova versione. Posso farlo controllando la versione della mia app nell'app store, a livello di programmazione?


6
È possibile inserire una pagina casuale su un server Web che restituisce solo una rappresentazione di stringa dell'ultima versione. Scaricalo e confrontalo all'avvio dell'app e avvisa l'utente. (Modo semplice e veloce)
LouwHopley

1
grazie, ma speravo in una soluzione migliore come una sorta di API con cui posso chiamare le funzionalità dell'app store, come cercare il numero della mia app e ottenere i dati sulla versione. Risparmia tempo per mantenere un server web solo per questo scopo, ma grazie comunque per il puntatore!
user542584

Faccio la stessa cosa del primo commento. Ho scritto un plist con una voce: un NSNumbernumero di versione. Poi l'ho caricato sul mio sito web. Lo stesso sito Web che utilizzo per il supporto dell'app e le pagine Web dell'app, quindi in viewDidLoad, cerco il numero di versione del sito Web lì e controllo la versione corrente nella mia app. Quindi ho un premade alertViewche richiede automaticamente di aggiornare l'app. Posso fornire il codice se lo desideri.
Andrew

grazie, immagino che dovrei provare anch'io ..
user542584

Risposte:


88

Ecco un semplice frammento di codice che ti consente di sapere se la versione corrente è diversa

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

Nota: assicurati che quando inserisci la nuova versione in iTunes, questa corrisponda alla versione nell'app che stai rilasciando. In caso contrario, il codice sopra riportato restituirà sempre YES indipendentemente dall'aggiornamento dell'utente.


4
super soluzione che abbia mai trovato +1
Sanjay Changani

1
@MobeenAfzal, penso che ti manchi capito la domanda e la soluzione. La soluzione di cui sopra confronta la versione corrente con la versione sullo store. Se non corrispondono, risintonizza SÌ, altrimenti restituisce NO. Indipendentemente dalla cronologia sull'app store, il metodo sopra riportato restituirà SÌ se la versione corrente è diversa dalla versione dell'app store. Una volta che l'utente aggiorna ... la versione corrente è uguale alla versione dell'app store. Il metodo sopra dovrebbe sempre restituire SÌ se la versione dell'utente è 1.0 e la versione dell'app store è 1.2.
datinc

1
@MobeenAfzal Penso di aver capito quello che stai vedendo. Nel codice la tua versione è 1.7, ma in iTunes hai caricato la versione come 1.6 in modo che i tuoi utenti non sappiano che hai saltato una versione. È così? Se è così allora ... ciò di cui hai bisogno è un server (DropBox farebbe) per fornire il numero di versione delle tue app e modificare il tuo codice per accedere a quell'endpoint. Fammi sapere se questo è ciò che stai vedendo e aggiungerò una nota di avvertimento al post.
datinc

1
@MobeenAfzal il tuo commento è fuorviante. Se la versione sul dispositivo dell'utente è separata da una qualsiasi dalla versione sull'appstore, il codice restituirà YES come previsto. Anche se rilasci la versione 1.0 seguita dalla versione 1.111, funzionerebbe comunque perfettamente.
datinc

1
Dovremmo mostrare l'aggiornamento solo quando la versione dell'appstore è maggiore della versione corrente come segue. if ([appStoreVersion compare: currentVersion options: NSNumericSearch] == NSOrderedDescending) {NSLog (@ "\ n \ nNecessità di aggiornamento. La versione dell'Appstore% @ è maggiore di% @", appStoreVersion, currentVersion); }
Nitesh Borad

52

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

Penso che sia meglio lanciare un errore invece di restituire false, in questo caso ho creato un VersionError ma può essere qualche altro che definisci o NSError

enum VersionError: Error {
    case invalidResponse, invalidBundleInfo
}

Considera anche di chiamare questa funzione da un altro thread, se la connessione è lenta può bloccare il thread corrente.

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

Aggiornare

Utilizzando URLSession:

Invece di usare Data(contentsOf: url)e bloccare un thread, possiamo usare 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
}

esempio:

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

1
Questa risposta fa la sua richiesta in modo sincrono. Ciò significa che con una cattiva connessione, la tua app potrebbe essere inutilizzabile per minuti fino al ritorno della richiesta.
testimone

4
Non sono d'accordo, DispatchQueue.global()ti dà una coda in background, i dati vengono caricati in quella coda e torna alla coda principale solo quando i dati vengono caricati.
juanjo

Ops. In qualche modo ho trascurato quel secondo snippet di codice. Purtroppo, sembra che non possa rimuovere il voto negativo fino a quando la tua risposta non viene modificata di nuovo :-( BTW - Dato dataWithContentsOfURL: in realtà passa attraverso le chiamate sincrone di NSURLConnection, che a loro volta avviano un thread asincrono e bloccano, probabilmente sarebbe meno sovraccarico per utilizzare solo le chiamate asincrone NSURLSession. Ti richiamerebbero anche sul thread principale una volta che hai finito.
uliwitness

@juanjo ,,,, non funziona per swift 3.0.1, per favore puoi caricare l'aggiornamento per swift ???
Kiran jadhav

2
Nota se sei elencato solo in un negozio specifico, ho scoperto che devi aggiungere un codice paese all'URL - ad esempio GB itunes.apple.com/(countryCode)/… )
Ryan Heitner

13

Grazie a Steve Moser per il suo link, ecco il mio codice:

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"];

1
soluzione molto buona e corretta, solo un piccolo aggiornamento per quanto riguarda l'url è itunes.apple.com/en/lookup?bundleId=xxxxxxxxxx
SJ

Grazie, il tuo commento è stato applicato
Roozbeh Zabihollahi

4
In realtà non ha funzionato per me con il /en/sottopercorso. Dopo averlo rimosso, ha funzionato
gasparuff

Questa risposta fa la sua richiesta in modo sincrono. Ciò significa che con una cattiva connessione, la tua app potrebbe essere inutilizzabile per minuti fino al ritorno della richiesta.
testimone

1
Ho dovuto usare con / en / itunes.apple.com/lookup?bundleId=xxxxxxx , grazie @gasparuff
Fernando Perez

13

Dato che stavo affrontando lo stesso problema, ho trovato la risposta fornita da Mario Hendricks . Unfornatelly quando ho provato ad applicare il suo codice al mio progetto, XCode si è lamentato dei problemi di Casting dicendo "MDLMaterialProperty non ha membri in pedice". Il suo codice stava cercando di impostare questo MDLMaterial ... come il tipo della costante "lookupResult", rendendo il casting a "Int" fallendo ogni volta. La mia soluzione era fornire un'annotazione di tipo per la mia variabile a NSDictionary per essere chiari sul tipo di valore di cui avevo bisogno. Con ciò, ho potuto accedere al valore "versione" di cui avevo bisogno.

Obs: Per questo YOURBUNDLEID , puoi ottenere dal tuo progetto Xcode .... " Targets> General> Identity> Bundle Identifier "

Quindi ecco il mio codice con alcune semplificazioni:

  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
}

Tutti i suggerimenti per migliorare questo codice sono i benvenuti!


Questa risposta fa la sua richiesta in modo sincrono. Ciò significa che con una cattiva connessione, la tua app potrebbe essere inutilizzabile per minuti fino al ritorno della richiesta.
testimone

@Yago Zardo si prega di utilizzare la funzione di confronto altrimenti quando l'utente carica l'app.apple ha testato l'aggiornamento del display del tempo alertview o la mela rifiuta la tua app
Jigar Darji

Ehi @ Jigar, grazie per il consiglio. Al momento non sto più usando questo metodo sulla mia app perché ora stiamo controllando le versioni di tutto nel nostro server. Ad ogni modo, potresti spiegare meglio quello che hai detto? Non ho capito e sembra davvero una buona cosa da sapere. Grazie in anticipo.
Yago Zardo

Grazie @uliwitness per il suggerimento, mi ha davvero aiutato a migliorare il mio codice in generale per conoscere le richieste asincrone e sincrone.
Yago Zardo

Quel collegamento è un gioiello!
B3none

13

Basta usare ATAppUpdater . È 1 linea, thread-safe e veloce. Dispone anche di metodi delegati se si desidera tenere traccia dell'azione dell'utente.

Ecco un esempio:

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

   return YES;
}

Metodi delegati facoltativi:

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

1
Funzionerà per le versioni beta in Testflight? In caso contrario, c'è qualche strumento che lo farà?
Lukasz Czerwinski

No, non lo farà, confronta solo la versione corrente con l'ultima versione disponibile su AppStore.
emotality

Potremmo usarlo con Swift?
Zorayr

11

Semplificato un'ottima risposta pubblicata su questo thread. Utilizzando Swift 4e 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
    }
  }

}

E poi per usarlo:

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

2
La mia applicazione è disponibile in negozio ma la stessa API non restituisce le informazioni sulla versione. Risposta:{ "resultCount":0, "results": [] }
technerd

Aggiungendo solo una nota al confronto delle versioni, preferirei, let serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare (localVersion, opzioni: .numeric) == .orderedDescending piuttosto che sostituire il file. con vuoto.
Chaitu

@Chaitu grazie per il suggerimento. Ho finito per riscrivere la parte di confronto del codice
budidino

9

Aggiornato il codice swift 4 di Anup Gupta

Ho apportato alcune modifiche a questo codice . Ora le funzioni vengono chiamate da una coda in background, poiché la connessione può essere lenta e quindi bloccare il thread principale.

Ho anche reso il CFBundleName opzionale, poiché la versione presentata aveva "CFBundleDisplayName" che probabilmente non funzionava nella mia versione. Quindi ora se non è presente non si bloccherà ma semplicemente non visualizzerà il nome dell'app nell'avviso.

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

Faccio questa chiamata per aggiungere anche il pulsante di conferma:

AppUpdater.shared.showUpdate(withConfirmation: true)

Oppure chiamalo per essere chiamato in questo modo per avere l'opzione di aggiornamento forzato su:

AppUpdater.shared.showUpdate(withConfirmation: false)

Qualche idea su come testarlo? Se non funziona correttamente, l'unico modo per eseguire il debug è in qualche modo eseguire il debug di una versione precedente rispetto a quella dell'app store.
David Rector,

2
Ah, lasciamo perdere la domanda. Posso semplicemente cambiare la mia versione locale per renderla "più vecchia".
David Rector,

Sono impressionato dal tuo codice @Vasco. Solo una semplice domanda, perché hai usato "http" invece di https in quell'URL?
Master AgentX

Grazie mille per aver condiviso questa soluzione @Vasco! Mi piace :) Perché non usi: let config = URLSessionConfiguration.background (withIdentifier: "com.example.MyExample.background") per URLSession per ottenere la richiesta in background?
mc_plectrum

Puoi anche sbarazzarti dello scartamento forzato, poiché controlli già se let appStoreAppVersion = info? .Version e lo stesso per trackURL.
mc_plectrum

7

Ecco la mia versione che utilizza Swift 4 e la popolare libreria Alamofire (la uso comunque nelle mie app). La richiesta è asincrona e puoi passare una richiamata per ricevere una notifica al termine.

import Alamofire

class VersionCheck {

    public static let shared = VersionCheck()

    var newVersionAvailable: Bool?
    var appStoreVersion: String?

    func checkAppStore(callback: ((_ versionAvailable: Bool?, _ version: String?)->Void)? = nil) {
        let ourBundleId = Bundle.main.infoDictionary!["CFBundleIdentifier"] as! String
        Alamofire.request("https://itunes.apple.com/lookup?bundleId=\(ourBundleId)").responseJSON { response in
            var isNew: Bool?
            var versionStr: String?

            if let json = response.result.value as? NSDictionary,
               let results = json["results"] as? NSArray,
               let entry = results.firstObject as? NSDictionary,
               let appVersion = entry["version"] as? String,
               let ourVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
            {
                isNew = ourVersion != appVersion
                versionStr = appVersion
            }

            self.appStoreVersion = versionStr
            self.newVersionAvailable = isNew
            callback?(isNew, versionStr)
        }
    }
}

L'utilizzo è semplice in questo modo:

VersionCheck.shared.checkAppStore() { isNew, version in
        print("IS NEW VERSION AVAILABLE: \(isNew), APP STORE VERSION: \(version)")
    }

1
problema con l'utilizzo di ourVersion! = appVersion è che si attiva quando il team di revisione dell'App Store controlla la nuova versione dell'app. Convertiamo quelle stringhe di versione in numeri e quindi isNew = appVersion> ourVersion.
budidino

@budidino hai ragione, ho appena mostrato l'approccio comune usando Alamofire. Il modo in cui interpreti la versione dipende totalmente dalla tua app e dalla struttura della versione.
Capitano del Nord,

Aggiungendo solo una nota al confronto delle versioni, preferirei, let serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare (localVersion, opzioni: .numeric) == .orderedDescending piuttosto che confrontare con uguale
Chaitu

6

Posso suggerire questa piccola libreria: https://github.com/nicklockwood/iVersion

Il suo scopo è semplificare la gestione dei plist remoti per attivare le notifiche.


3
Puoi controllare direttamente l'App Store per il numero di versione invece di ospitare un file plist da qualche parte. Dai un'occhiata a questa risposta: stackoverflow.com/a/6569307/142358
Steve Moser

1
iVersion ora utilizza automaticamente la versione dell'app store: il Plist è facoltativo se desideri specificare note di rilascio diverse da quelle su iTunes, ma non è necessario utilizzarlo.
Nick Lockwood

1
Questo codice potrebbe utilizzare alcuni miglioramenti, ma è molto meglio delle altre risposte che inviano una richiesta sincrona. Tuttavia, il modo in cui esegue il threading è di cattivo stile. File i problemi su Github.
testimone

Il progetto è ora deprecato 😢
Zorayr

5

Swift 3.1

func needsUpdate() -> Bool {
    let infoDictionary = Bundle.main.infoDictionary
    let appID = infoDictionary!["CFBundleIdentifier"] as! String
    let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(appID)")
    guard let data = try? Data(contentsOf: url) else {
      print("There is an error!")
      return false;
    }
    let lookup = (try? JSONSerialization.jsonObject(with: data! , options: [])) as? [String: Any]
    if let resultCount = lookup!["resultCount"] as? Int, resultCount == 1 {
        if let results = lookup!["results"] as? [[String:Any]] {
            if let appStoreVersion = results[0]["version"] as? String{
                let currentVersion = infoDictionary!["CFBundleShortVersionString"] as? String
                if !(appStoreVersion == currentVersion) {
                    print("Need to update [\(appStoreVersion) != \(currentVersion)]")
                    return true
                }
            }
        }
    }
    return false
}

Questo si blocca quando non si dispone di una connessione Internet. lasciare dati = provare? Data (contentOf: url!) Restituirà zero, e nella riga successiva fai i dati!
Joris Mans

grazie @JorisMans Lo aggiornerò per nessun crash di connettività Internet
Kassem Itani

Non farlo. Usa URLSession.
JAL

4

Questa risposta è una modifica alla risposta di datinc https://stackoverflow.com/a/25210143/2735358 .

La funzione di datinc confronta la versione per confronto di stringhe. Quindi, non confronterà la versione per maggiore o minore di.

Ma questa funzione modificata confronta la versione di NSNumericSearch (confronto numerico) .

- (void)checkForUpdateWithHandler:(void(^)(BOOL isUpdateAvailable))updateHandler {

    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString *appID = infoDictionary[@"CFBundleIdentifier"];
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/lookup?bundleId=%@", appID]];
    NSLog(@"iTunes Lookup URL for the app: %@", url.absoluteString);

    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *theTask = [session dataTaskWithRequest:[NSURLRequest requestWithURL:url]
                                               completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

                                                   NSDictionary *lookup = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                                                   NSLog(@"iTunes Lookup Data: %@", lookup);
                                                   if (lookup && [lookup[@"resultCount"] integerValue] == 1){
                                                       NSString *appStoreVersion = lookup[@"results"][0][@"version"];
                                                       NSString *currentVersion = infoDictionary[@"CFBundleShortVersionString"];

                                                       BOOL isUpdateAvailable = [appStoreVersion compare:currentVersion options:NSNumericSearch] == NSOrderedDescending;
                                                       if (isUpdateAvailable) {
                                                           NSLog(@"\n\nNeed to update. Appstore version %@ is greater than %@",appStoreVersion, currentVersion);
                                                       }
                                                       if (updateHandler) {
                                                           updateHandler(isUpdateAvailable);
                                                       }
                                                   }
                                               }];
    [theTask resume];
}

Uso:

[self checkForUpdateWithHandler:^(BOOL isUpdateAvailable) {
    if (isUpdateAvailable) {
        // show alert
    }
}];

3
Questa risposta fa la sua richiesta in modo sincrono. Ciò significa che con una cattiva connessione, la tua app potrebbe essere inutilizzabile per minuti fino al ritorno della richiesta.
Testimone

NSURLSession funziona automaticamente sui thread in background a meno che non specifichiamo diversamente.
Sebastian Dwornik

4

Ho visto molti modi per controllare l'aggiornamento dell'app. quindi sulla base di molte risposte le mescolo e creo la mia soluzione che è disponibile su GitHub Se è necessario un aggiornamento, fammelo sapere. Questo codice per Swift 4

Collegamento GitHub a questo codice. https://github.com/anupgupta-arg/iOS-Swift-ArgAppUpdater

   import UIKit

enum VersionError: Error {
    case invalidBundleInfo, invalidResponse
}

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

class AppInfo: Decodable {
    var version: String
    var trackViewUrl: String
    //let identifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
    // You can add many thing based on "http://itunes.apple.com/lookup?bundleId=\(identifier)"  response
    // here version and trackViewUrl are key of URL response
    // so you can add all key beased on your requirement.

}

class ArgAppUpdater: NSObject {
    private static var _instance: ArgAppUpdater?;

    private override init() {

    }

    public static func getSingleton() -> ArgAppUpdater {
        if (ArgAppUpdater._instance == nil) {
            ArgAppUpdater._instance = ArgAppUpdater.init();
        }
        return ArgAppUpdater._instance!;
    }

    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 }

                print("Data:::",data)
                print("response###",response!)

                let result = try JSONDecoder().decode(LookupResult.self, from: data)

                let dictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves)

                print("dictionary",dictionary!)


                guard let info = result.results.first else { throw VersionError.invalidResponse }
                print("result:::",result)
                completion(info, nil)
            } catch {
                completion(nil, error)
            }
        }
        task.resume()

        print("task ******", task)
        return task
    }
    private  func checkVersion(force: Bool) {
        let info = Bundle.main.infoDictionary
        let currentVersion = info?["CFBundleShortVersionString"] as? String
        _ = getAppInfo { (info, error) in

            let appStoreAppVersion = info?.version

            if let error = error {
                print(error)



            }else if appStoreAppVersion!.compare(currentVersion!, options: .numeric) == .orderedDescending {
                //                print("needs update")
               // print("hiiii")
                DispatchQueue.main.async {
                    let topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!

                    topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
            }

            }
        }


    }

    func showUpdateWithConfirmation() {
        checkVersion(force : false)


    }

    func showUpdateWithForce() {
        checkVersion(force : true)
    }



}

extension UIViewController {


    fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
        print("AppURL:::::",AppURL)

        let bundleName = Bundle.main.infoDictionary!["CFBundleDisplayName"] as! String;
        let alertMessage = "\(bundleName) Version \(Version) is available on AppStore."
        let alertTitle = "New Version"


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


        if !Force {
            let notNowButton = UIAlertAction(title: "Not Now", style: .default) { (action:UIAlertAction) in
                print("Don't Call API");


            }
            alertController.addAction(notNowButton)
        }

        let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
            print("Call API");
            print("No update")
            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)
    }
}

Aggiornamento: https://stackoverflow.com/a/48810541/5855888 e https://github.com/emotality/ATAppUpdater

Happy Coding 👍 😊


@Rob Si prega di controllare il collegamento GitHub github.com/anupgupta-arg/iOS-Swift-ArgAppUpdater
Anup Gupta

3

Prova questo con una singola chiamata di funzione:

func showAppStoreVersionUpdateAlert(isForceUpdate: Bool) {

    do {
        //Get Bundle Identifire from Info.plist
        guard let bundleIdentifire = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String else {
            print("No Bundle Info found.")
            throw CustomError.invalidIdentifires
        }

        // Build App Store URL
        guard let url = URL(string:"http://itunes.apple.com/lookup?bundleId=" + bundleIdentifire) else {
            print("Isse with generating URL.")
            throw CustomError.invalidURL
        }

        let serviceTask = URLSession.shared.dataTask(with: url) { (responseData, response, error) in

            do {
                // Check error
                if let error = error { throw error }
                //Parse response
                guard let data = responseData else { throw CustomError.jsonReading }
                let result = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
                let itunes = ItunesAppInfoItunes.init(fromDictionary: result as! [String : Any])
                print(itunes.results)
                if let itunesResult = itunes.results.first {
                    print("App Store Varsion: ",itunesResult.version)

                    //Get Bundle Version from Info.plist
                    guard let appShortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
                        print("No Short Version Info found.")
                        throw CustomError.invalidVersion
                    }

                    if appShortVersion == itunesResult.version {
                        //App Store & Local App Have same Version.
                        print("Same Version at both side")
                    } else {
                        //Show Update alert
                        var message = ""
                        //Get Bundle Version from Info.plist
                        if let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String {
                            message = "\(appName) has new version(\(itunesResult.version!)) available on App Store."
                        } else {
                            message = "This app has new version(\(itunesResult.version!)) available on App Store."
                        }

                        //Show Alert on the main thread
                        DispatchQueue.main.async {
                            self.showUpdateAlert(message: message, appStoreURL: itunesResult.trackViewUrl, isForceUpdate: isForceUpdate)
                        }
                    }
                }
            } catch {
                print(error)
            }
        }
        serviceTask.resume()
    } catch {
        print(error)
    }
}

Funzione di avviso per aprire l'URL dell'AppStore:

func showUpdateAlert(message : String, appStoreURL: String, isForceUpdate: Bool) {

    let controller = UIAlertController(title: "New Version", message: message, preferredStyle: .alert)

    //Optional Button
    if !isForceUpdate {
        controller.addAction(UIAlertAction(title: "Later", style: .cancel, handler: { (_) in }))
    }

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

    }))

    let applicationDelegate = UIApplication.shared.delegate as? AppDelegate
    applicationDelegate?.window?.rootViewController?.present(controller, animated: true)

}

Come chiamare la funzione sopra:

AppStoreUpdate.shared.showAppStoreVersionUpdateAlert(isForceUpdate: false/true)

Per maggiori dettagli prova il link sottostante con il codice completo:

AppStoreUpdate.swift

ItunesAppInfoResult.swift

ItunesAppInfoItunes.swift

Spero che questo ti aiuti!


2

Ecco un metodo rapido che fa ciò che suggeriscono alcune delle risposte di Objective-C. Ovviamente, una volta ottenute le informazioni dall'app store JSON, puoi estrarre le note di rilascio, se le desideri.

func appUpdateAvailable(storeInfoURL: String) -> Bool
{
    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 lookupResults = try? NSJSONSerialization.JSONObjectWithData(dataInJSON, options: NSJSONReadingOptions()) {
                // Determine how many results we got. There should be exactly one, but will be zero if the URL was wrong
                if let resultCount = lookupResults["resultCount"] as? Int {
                    if resultCount == 1 {
                        // Get the version number of the version in the App Store
                        if let appStoreVersion = lookupResults["results"]!![0]["version"] as? String {
                            // Get the version number of the current version
                            if let currentVersion = infoDictionary["CFBundleShortVersionString"] as? String {
                                // Check if they are the same. If not, an upgrade is available.
                                if appStoreVersion != currentVersion {
                                    upgradeAvailable = true                      
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return upgradeAvailable
}

storeInfoURL è l'URL dell'app in appstore?
iamthevoid

@Mario Hendricks questo non funziona in swift 3. Genera alcuni errori. Puoi aggiornare per Swift 3?
George Asda

Questa risposta fa la sua richiesta in modo sincrono. Ciò significa che con una cattiva connessione, la tua app potrebbe essere inutilizzabile per minuti fino al ritorno della richiesta.
testimone

2

Se non stai impostando il tipo di contenuto in NSUrlRequest, sicuramente non riceverai risposta, quindi prova il codice seguente che funziona bene per me. Spero che sia d'aiuto....

-(BOOL) isUpdateAvailable{
    NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString* appID = infoDictionary[@"CFBundleIdentifier"];
    NSString *urlString = [NSString stringWithFormat:@"https://itunes.apple.com/lookup?bundleId=%@",appID];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:[NSURL URLWithString:urlString]];
    [request setHTTPMethod:@"GET"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    NSURLResponse *response;
    NSError *error;
    NSData *data = [NSURLConnection  sendSynchronousRequest:request returningResponse: &response error: &error];
    NSError *e = nil;
    NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error: &e];

    self.versionInAppStore = [[[jsonDict objectForKey:@"results"] objectAtIndex:0] objectForKey:@"version"];

    self.localAppVersion = infoDictionary[@"CFBundleShortVersionString"];

    if ([self.versionInAppStore compare:self.localAppVersion options:NSNumericSearch] == NSOrderedDescending) {
        // currentVersion is lower than the version
        return YES;
    }
    return NO;
}

Questa risposta fa la sua richiesta in modo sincrono. Ciò significa che con una cattiva connessione, la tua app potrebbe essere inutilizzabile per minuti fino al ritorno della richiesta.
testimone

2

Proveniente da un POV di un'applicazione ibrida, questo è un esempio javascript, ho un piè di pagina Aggiornamento disponibile nel mio menu principale. Se è disponibile un aggiornamento (ad es. Il mio numero di versione all'interno del file di configurazione è inferiore alla versione recuperata, visualizza il piè di pagina) Questo indirizzerà l'utente all'app store, dove l'utente può quindi fare clic sul pulsante di aggiornamento.

Ricevo anche i nuovi dati (cioè le note di rilascio) e li visualizzo in modo modale all'accesso se è la prima volta su questa versione.

Il metodo Aggiorna disponibile può essere eseguito tutte le volte che vuoi. Il mio viene eseguito ogni volta che l'utente accede alla schermata principale.

function isUpdateAvailable() {
        $.ajax('https://itunes.apple.com/lookup?bundleId=BUNDLEID', {
            type: "GET",
            cache: false,
            dataType: 'json'
        }).done(function (data) {
            _isUpdateAvailable(data.results[0]);
        }).fail(function (jqXHR, textStatus, errorThrown) {
            commsErrorHandler(jqXHR, textStatus, false);
        });

}

Richiamata: Apple ha un'API, quindi molto facile da ottenere

function isUpdateAvailable_iOS (data) {
    var storeVersion = data.version;
    var releaseNotes = data.releaseNotes;
    // Check store Version Against My App Version ('1.14.3' -> 1143)
    var _storeV = parseInt(storeVersion.replace(/\./g, ''));
    var _appV = parseInt(appVersion.substring(1).replace(/\./g, ''));
    $('#ft-main-menu-btn').off();
    if (_storeV > _appV) {
        // Update Available
        $('#ft-main-menu-btn').text('Update Available');
        $('#ft-main-menu-btn').click(function () {
           // Open Store      
           window.open('https://itunes.apple.com/us/app/appname/idUniqueID', '_system');
        });

    } else {
        $('#ft-main-menu-btn').html('&nbsp;');
        // Release Notes
        settings.updateReleaseNotes('v' + storeVersion, releaseNotes);
    }
}

2

Avviso: la maggior parte delle risposte fornite recupera l'URL in modo sincrono (utilizzando -dataWithContentsOfURL:o -sendSynchronousRequest:. Questo non è valido, poiché significa che la tua applicazione non risponderà per diversi minuti se la connessione mobile si interrompe mentre la richiesta è in corso. Non accedere mai a Internet in modo sincrono sul filo principale.

La risposta corretta è utilizzare l'API asincrona:

    NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString* appID = infoDictionary[@"CFBundleIdentifier"];
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/lookup?bundleId=%@", appID]];
    NSURLSession         *  session = [NSURLSession sharedSession];
    NSURLSessionDataTask *  theTask = [session dataTaskWithRequest: [NSURLRequest requestWithURL: url] completionHandler:
    ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error)
    {
        NSDictionary<NSString*,NSArray*>* lookup = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        if ([lookup[@"resultCount"] integerValue] == 1)
        {
            NSString* appStoreVersion = lookup[@"results"].firstObject[@"version"];
           NSString* currentVersion = infoDictionary[@"CFBundleShortVersionString"];

            if ([appStoreVersion compare:currentVersion options:NSNumericSearch] == NSOrderedDescending) {
                // *** Present alert about updating to user ***
            }
        }
    }];
    [theTask resume];

Il timeout predefinito per le connessioni di rete è di diversi minuti e, anche se la richiesta va a buon fine, può essere abbastanza lento su una cattiva connessione EDGE da richiedere così tanto tempo. Non vuoi che la tua app sia inutilizzabile in quel caso. Per testare cose come questa, è utile eseguire il codice di rete con Network Link Conditioner di Apple.


Grazie per aver tenuto viva questa domanda :-)
byJeevan

2
func isUpdateAvailable() -> Bool {
    guard
        let info = Bundle.main.infoDictionary,
        let identifier = info["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)"),
        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]],
        results.count > 0,
        let versionString = results[0]["version"] as? String
        else {
            return false
    }

    return AppVersion(versionString) > AppVersion.marketingVersion
}

per confrontare la stringa di versione:

https://github.com/eure/AppVersionMonitor


2

PER SWIFT 4 e 3.2:

Per prima cosa, dobbiamo ottenere l'ID del pacchetto dal dizionario delle informazioni sul pacchetto, impostare isUpdaet come false.

    var isUpdate = false
    guard let bundleInfo = Bundle.main.infoDictionary,
        let currentVersion = bundleInfo["CFBundleShortVersionString"] as? String,
        //let identifier = bundleInfo["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)")
        else{
        print("something wrong")
            completion(false)
        return
       }

Quindi dobbiamo chiamare una chiamata urlSession per ottenere la versione da itunes.

    let task = URLSession.shared.dataTask(with: url) {
        (data, resopnse, error) in
        if error != nil{
             completion(false)
            print("something went wrong")
        }else{
            do{
                guard let reponseJson = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any],
                let result = (reponseJson["results"] as? [Any])?.first as? [String: Any],
                let version = result["version"] as? String
                else{
                     completion(false)
                    return
                }
                print("Current Ver:\(currentVersion)")
                print("Prev version:\(version)")
                if currentVersion != version{
                    completion(true)
                }else{
                    completion(false)
                }
            }
            catch{
                 completion(false)
                print("Something went wrong")
            }
        }
    }
    task.resume()

IL CODICE COMPLETO SARÀ COME QUESTO:

func checkForUpdate(completion:@escaping(Bool)->()){

    guard let bundleInfo = Bundle.main.infoDictionary,
        let currentVersion = bundleInfo["CFBundleShortVersionString"] as? String,
        //let identifier = bundleInfo["CFBundleIdentifier"] as? String,
        let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)")
        else{
        print("some thing wrong")
            completion(false)
        return
       }

    let task = URLSession.shared.dataTask(with: url) {
        (data, resopnse, error) in
        if error != nil{
             completion(false)
            print("something went wrong")
        }else{
            do{
                guard let reponseJson = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any],
                let result = (reponseJson["results"] as? [Any])?.first as? [String: Any],
                let version = result["version"] as? String
                else{
                     completion(false)
                    return
                }
                print("Current Ver:\(currentVersion)")
                print("Prev version:\(version)")
                if currentVersion != version{
                    completion(true)
                }else{
                    completion(false)
                }
            }
            catch{
                 completion(false)
                print("Something went wrong")
            }
        }
    }
    task.resume()
}

Quindi possiamo chiamare la funzione anyware di cui abbiamo bisogno.

    checkForUpdate { (isUpdate) in
        print("Update needed:\(isUpdate)")
        if isUpdate{
            DispatchQueue.main.async {
                print("new update Available")
            }
        }
    }

2

Equivalenza C # di @datinc, tanto quanto ottenere la versione dell'Apple App Store. Codice incluso per ottenere la versione sia per il bundle che per il file AssemblyInfo.

EDIT :: Si prega di notare la regione, "/ us /", inclusa in urlString. Questo codice paese dovrà essere gestito / modificato di conseguenza.

string GetAppStoreVersion()
{
    string version = "";

    NSDictionary infoDictionary = NSBundle
        .MainBundle
        .InfoDictionary;

    String appID = infoDictionary["CFBundleIdentifier"].ToString();

    NSString urlString = 
        new NSString(@"http://itunes.apple.com/us/lookup?bundleId=" + appID);
    NSUrl url = new NSUrl(new System.Uri(urlString).AbsoluteUri);

    NSData data = NSData.FromUrl(url);

    if (data == null)
    {
        /* <-- error obtaining data from url --> */
        return "";
    }

    NSError e = null;
    NSDictionary lookup = (NSDictionary)NSJsonSerialization
        .Deserialize(data, NSJsonReadingOptions.AllowFragments, out e);

    if (lookup == null)
    {
        /* <-- error, most probably no internet or bad connectivity --> */
        return "";
    }

    if (lookup["resultCount"].Description.Equals("1"))
    {
        NSObject nsObject = lookup["results"];
        NSString nsString = new NSString("version");
        String line = nsObject
            .ValueForKey(nsString)
            .Description;

        /* <-- format string --> */
        string[] digits = Regex.Split(line, @"\D+");
        for (int i = 0; i < digits.Length; i++)
        {
            if (int.TryParse(digits[i], out int intTest))
            {
                if (version.Length > 0)
                    version += "." + digits[i];
                else
                    version += digits[i];
            }
        }
    }

    return version;
}

string GetBundleVersion()
{
        return NSBundle
            .MainBundle
            .InfoDictionary["CFBundleShortVersionString"]
            .ToString();
}

string GetAssemblyInfoVersion()
{
        var assembly = typeof(App).GetTypeInfo().Assembly;
        var assemblyName = new AssemblyName(assembly.FullName);
        return assemblyName.Version.ToString();
}

1

Questa domanda è stata posta nel 2011, l'ho trovata nel 2018 mentre cercavo un modo non solo per controllare la nuova versione dell'app nell'App Store, ma anche per informarne l'utente.

Dopo piccole ricerche sono giunto alla conclusione che la risposta di juanjo (relativa a Swift 3) https://stackoverflow.com/a/40939740/1218405 è la soluzione ottimale se vuoi farlo in codice da solo

Inoltre posso suggerire due fantastici progetti su GitHub (oltre 2300 stelle ciascuno)

Esempio per Siren (AppDelegate.swift)

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

      let siren = Siren.shared
      siren.checkVersion(checkType: .immediately)

      return true
    }
  • Puoi anche mostrare diversi tipi di avvisi sulla nuova versione (consentendo di saltare la versione o costringendo l'utente ad aggiornare)
  • È possibile specificare la frequenza con cui deve essere eseguito il controllo della versione (giornaliero / settimanale / immediato)
  • È possibile specificare dopo quanti giorni deve essere visualizzato l'avviso della nuova versione rilasciata nell'app store

I collegamenti a una risposta esistente non sono risposte. Inoltre, anche i collegamenti alle librerie non sono risposte a meno che non si aggiunga esplicitamente come il collegamento risponde alla domanda alla risposta (aggiungere esempi di codice, ecc.).
JAL

1

Swift 4

Possiamo usare il nuovo JSONDecoderper analizzare la risposta da itunes.apple.com/lookup e rappresentarla con classi o strutture decodificabili:

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

class AppInfo: Decodable {
    var version: String
}

Possiamo anche aggiungere altre proprietà a AppInfonel caso in cui abbiamo bisogno della releaseNoteso qualche altra proprietà.

Ora possiamo fare una richiesta asincrona usando URLSession:

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
}

enum VersionError: Error {
    case invalidBundleInfo, invalidResponse
}

questa funzione riceve una chiusura di completamento che verrà chiamata quando la richiesta è completata e restituisce un URLSessionDataTasknel caso in cui sia necessario annullare la richiesta, e può essere chiamata in questo modo:

func checkVersion() {
    let info = Bundle.main.infoDictionary
    let currentVersion = info?["CFBundleShortVersionString"] as? String
    _ = getAppInfo { (info, error) in
        if let error = error {
            print(error)
        } else if info?.version == currentVersion {
            print("updated")
        } else {
            print("needs update")
        }
    }
}

Dove hai messo questo codice? Vedo che hai impostato LookupResult e AppInfo su decodificabili, ma non li vedo salvati da nessuna parte. Cosa mi manca qui?
jessi

Dichiari le classi LookupResulte AppInfoda qualche parte nel tuo progetto, preferibilmente in un file separato: sono usate quando decodifichi la risposta: JSONDecoder().decode(LookupResult.self, from: data)e contengono la stringa della versione
juanjo

In base alla tua risposta creo un file utilizzando il tuo codice Verifica che iOS-Swift-ArgAppUpdater
Anup Gupta

@jessi per favore controlla il mio codice su GitHub Ho pubblicato lì la tua soluzione
Anup Gupta

0

La mia proposta di codice. Basato sulle risposte di @datinc e @ Mario-Hendricks

Ovviamente dovresti sostituirlo dlog_Errorcon la tua chiamata alla funzione di registrazione.

Questo tipo di struttura del codice dovrebbe impedire alla tua app di bloccarsi in caso di errore. Il recupero di appStoreAppVersionnon è un imperativo e non dovrebbe portare a errori fatali. Eppure, con questo tipo di struttura del codice, continuerai a registrare il tuo errore non fatale.

class func appStoreAppVersion() -> String?
{
    guard let bundleInfo = NSBundle.mainBundle().infoDictionary else {
        dlog_Error("Counldn't fetch bundleInfo.")
        return nil
    }
    let bundleId = bundleInfo[kCFBundleIdentifierKey as String] as! String
    // dbug__print("bundleId = \(bundleId)")

    let address = "http://itunes.apple.com/lookup?bundleId=\(bundleId)"
    // dbug__print("address = \(address)")

    guard let url = NSURLComponents.init(string: address)?.URL else {
        dlog_Error("Malformed internet address: \(address)")
        return nil
    }
    guard let data = NSData.init(contentsOfURL: url) else {
        if Util.isInternetAvailable() {
            dlog_MajorWarning("Web server request failed. Yet internet is reachable. Url was: \(address)")
        }// else: internet is unreachable. All ok. It is of course impossible to fetch the appStoreAppVersion like this.
        return nil
    }
    // dbug__print("data.length = \(data.length)")

    if data.length < 100 { //: We got 42 for a wrong address. And aproximately 4684 for a good response
        dlog_MajorWarning("Web server message is unexpectedly short: \(data.length) bytes")
    }

    guard let response = try? NSJSONSerialization.JSONObjectWithData(data, options: []) else {
        dlog_Error("Failed to parse server response.")
        return nil
    }
    guard let responseDic = response as? [String: AnyObject] else {
        dlog_Error("Not a dictionary keyed with strings. Response with unexpected format.")
        return nil
    }
    guard let resultCount = responseDic["resultCount"] else {
        dlog_Error("No resultCount found.")
        return nil
    }
    guard let count = resultCount as? Int else { //: Swift will handle NSNumber.integerValue
        dlog_Error("Server response resultCount is not an NSNumber.integer.")
        return nil
    }
    //:~ Determine how many results we got. There should be exactly one, but will be zero if the URL was wrong
    guard count == 1 else {
        dlog_Error("Server response resultCount=\(count), but was expected to be 1. URL (\(address)) must be wrong or something.")
        return nil
    }
    guard let rawResults = responseDic["results"] else {
        dlog_Error("Response does not contain a field called results. Results with unexpected format.")
        return nil
    }
    guard let resultsArray = rawResults as? [AnyObject] else {
        dlog_Error("Not an array of results. Results with unexpected format.")
        return nil
    }
    guard let resultsDic = resultsArray[0] as? [String: AnyObject] else {
        dlog_Error("Not a dictionary keyed with strings. Results with unexpected format.")
        return nil
    }
    guard let rawVersion = resultsDic["version"] else {
        dlog_Error("The key version is not part of the results")
        return nil
    }
    guard let versionStr = rawVersion as? String else {
        dlog_Error("Version is not a String")
        return nil
    }
    return versionStr.e_trimmed()
}

extension String {
    func e_trimmed() -> String
    {
        return stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
    }
}

1
Questa risposta fa la sua richiesta in modo sincrono. Ciò significa che con una cattiva connessione, la tua app potrebbe essere inutilizzabile per minuti fino al ritorno della richiesta.
Testimone

-1

Aggiornato per swift 3:

se vuoi controllare la versione corrente della tua app, usa sotto il semplice codice:

 let object = Bundle.main.infoDictionary?["CFBundleShortVersionString"]

  let version = object as! String
  print("version: \(version)")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.