Verifique si mi aplicación tiene una nueva versión en AppStore


112

Me gustaría verificar manualmente si hay nuevas actualizaciones para mi aplicación mientras el usuario está en ella y pedirle que descargue la nueva versión. ¿Puedo hacer esto comprobando la versión de mi aplicación en la tienda de aplicaciones, mediante programación?


6
Podría poner una página aleatoria en un servidor web que solo devuelva una representación de cadena de la última versión. Descárguelo y compárelo al iniciar la aplicación y notifique al usuario. (Manera rápida y fácil)
LouwHopley

1
gracias, pero esperaba una solución mejor como algún tipo de API con la que pueda llamar a las funcionalidades de la tienda de aplicaciones, como buscar mi número de aplicación y obtener los datos de la versión. Ahorra tiempo para mantener un servidor web solo para este propósito, pero ¡gracias por el puntero de todos modos!
user542584

Hago lo mismo que el primer comentario. Escribí una lista con una entrada: un NSNumbernúmero de versión. Luego lo subí a mi sitio web. El mismo sitio web que utilizo para el soporte de mi aplicación y las páginas web de la aplicación, luego en viewDidLoad, reviso el sitio web para ver el número de versión allí y verifico la versión actual en mi aplicación. Luego tengo un prefabricado alertViewque solicita automáticamente actualizar la aplicación. Puedo proporcionar el código si lo desea.
Andrew

gracias, supongo que debería intentarlo también ..
user542584

Respuestas:


88

Aquí hay un fragmento de código simple que le permite saber si la versión actual es diferente

-(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: asegúrese de que cuando ingrese la nueva versión en iTunes, esta coincida con la versión de la aplicación que está lanzando. De lo contrario, el código anterior siempre devolverá SÍ independientemente de si el usuario actualiza.


4
super solución que he encontrado +1
Sanjay Changani

1
@MobeenAfzal, creo que extrañaste entendiste la pregunta y la solución. La solución anterior compara la versión actual con la versión en la tienda. Si no coinciden, vuelve a sintonizar SÍ, de lo contrario, devuelve NO. Independientemente del historial en la tienda de aplicaciones, el método anterior devolverá SÍ si la versión actual es diferente a la versión de la tienda de aplicaciones. Una vez que el usuario actualiza ... la versión actual es igual a la versión de la tienda de aplicaciones. El método anterior siempre debe devolver SÍ si la versión del usuario es 1.0 y la versión de la tienda de aplicaciones es 1.2.
fecha

1
@MobeenAfzal Creo que entiendo lo que estás viendo. En código, su versión es 1.7, pero en iTunes subió la versión como 1.6 para que sus usuarios no sepan que omitió una versión. ¿Es ese el caso? Si es así, entonces ... lo que necesita es un servidor (DropBox lo haría) para proporcionar el número de versión de su aplicación y modificar su código para acceder a ese punto final. Hágame saber si esto es lo que está viendo y agregaré una nota de advertencia a la publicación.
2015

1
@MobeenAfzal tu comentario es engañoso. Si la versión en el dispositivo del usuario está separada por cualquiera de la versión en la tienda de aplicaciones, el código devolverá SÍ como se esperaba. Incluso si lanza la versión 1.0 seguida de la versión 1.111, funcionaría perfectamente.
fecha

1
Deberíamos mostrar la actualización solo cuando la versión de la tienda de aplicaciones sea mayor que la versión actual de la siguiente manera. if ([appStoreVersion compare: opciones currentVersion: NSNumericSearch] == NSOrderedDescending) {NSLog (@ "\ n \ nNeed to update. Appstore versión% @ is mayor% @", appStoreVersion, currentVersion); }
Nitesh Borad

52

Versión 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
}

Creo que es mejor lanzar un error en lugar de devolver falso, en este caso creé un VersionError pero puede ser otro que defina o NSError

enum VersionError: Error {
    case invalidResponse, invalidBundleInfo
}

También considere llamar a esta función desde otro hilo, si la conexión es lenta, puede bloquear el hilo actual.

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

Actualizar

Usando URLSession:

En lugar de usar Data(contentsOf: url)y bloquear un hilo, podemos usar 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
}

ejemplo:

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

1
Esta respuesta realiza su solicitud de forma sincrónica. Esto significa que con una mala conexión, su aplicación podría quedar inutilizable durante minutos hasta que vuelva la solicitud.
uliwitness

4
No estoy de acuerdo, DispatchQueue.global()le da una cola en segundo plano, los datos se cargan en esa cola y solo vuelven a la cola principal cuando se cargan los datos.
juanjo

¡Ups! De alguna manera, pasé por alto ese segundo fragmento de código. Lamentablemente, parece que no puedo eliminar el voto negativo hasta que su respuesta se edite nuevamente :-( BTW - Given dataWithContentsOfURL: en realidad pasa por las llamadas sincrónicas de NSURLConnection, que a su vez solo inician un hilo asíncrono y se bloquean, probablemente sería menos sobrecarga para usar las llamadas asincrónicas NSURLSession. Incluso te llamarían de nuevo en el hilo principal una vez que hayas terminado.
uliwitness

@juanjo ,,,, no funciona para swift 3.0.1, por favor, ¿puedes subir actualizado para swift?
Kiran jadhav

2
Tenga en cuenta que si solo aparece en una tienda específica, he descubierto que debe agregar un código de país a la URL, por ejemplo, GB itunes.apple.com/(countryCode)/… )
Ryan Heitner

13

Gracias a Steve Moser por su enlace, aquí está mi código:

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
solución muy buena y correcta, solo una pequeña actualización con respecto a la URL es itunes.apple.com/en/lookup?bundleId=xxxxxxxxxx
SJ

Gracias, se aplicó su comentario
Roozbeh Zabihollahi

4
En realidad, no funcionó para mí con la /en/subruta. Después de quitarlo, funcionó
gasparuff

Esta respuesta realiza su solicitud de forma sincrónica. Esto significa que con una mala conexión, su aplicación podría quedar inutilizable durante minutos hasta que vuelva la solicitud.
uliwitness

1
Tuve que usar con / en / itunes.apple.com/lookup?bundleId=xxxxxxx , gracias @gasparuff
Fernando Perez

13

Como estaba enfrentando el mismo problema, encontré la respuesta proporcionada por Mario Hendricks . Lamentablemente, cuando intenté aplicar su código en mi proyecto, XCode se quejó de los problemas de Casting diciendo "MDLMaterialProperty no tiene miembros de subíndice". Su código intentaba establecer este MDLMaterial ... como el tipo de "lookupResult" constante, haciendo que la conversión a "Int" fallara cada vez. Mi solución fue proporcionar una anotación de tipo para mi variable en NSDictionary para tener claro el tipo de valor que necesitaba. Con eso, pude acceder al valor "versión" que necesitaba.

Obs: Para este YOURBUNDLEID , puede obtenerlo de su proyecto Xcode ... " Objetivos> General> Identidad> Identificador de paquete "

Así que aquí está mi código con algunas simplificaciones también:

  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
}

¡Todas las sugerencias para mejorar este código son bienvenidas!


Esta respuesta realiza su solicitud de forma sincrónica. Esto significa que con una mala conexión, su aplicación podría quedar inutilizable durante minutos hasta que vuelva la solicitud.
uliwitness

@Yago Zardo por favor utilice función de comparación de otro modo cuando se probó app.apple carga usuario actualización de visualización de la hora o de manzana alertview rechazan su aplicación
Jigar Darji

Hola @Jigar, gracias por el consejo. Actualmente ya no uso este método en mi aplicación porque ahora estamos versionando todo en nuestro servidor. De todos modos, ¿podrías explicar mejor lo que dijiste? No entendí y realmente parece bueno saberlo. Gracias por adelantado.
Yago Zardo

Gracias @uliwitness por el consejo, realmente me ayudó a mejorar mi código en general para aprender sobre solicitudes asíncronas y sincrónicas.
Yago Zardo

¡Ese enlace es una joya!
B3none

13

Simplemente use ATAppUpdater . Es de 1 línea, seguro para subprocesos y rápido. También tiene métodos delegados si desea realizar un seguimiento de la acción del usuario.

Aquí hay un ejemplo:

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

   return YES;
}

Métodos de delegado opcionales:

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

1
¿Funcionará esto para las versiones beta en Testflight? Si no es así, ¿hay alguna herramienta que lo haga?
Lukasz Czerwinski

No, no lo hará, solo compara la versión actual con la última versión que está en la AppStore.
emotality

¿Podríamos usar esto con Swift?
Zorayr

11

Simplificado una gran respuesta publicada en este hilo. Usando Swift 4y 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
    }
  }

}

Y luego para usarlo:

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

2
Mi aplicación está activa en la tienda, pero la misma API no devuelve información de la versión. Respuesta:{ "resultCount":0, "results": [] }
technerd

Simplemente agregando una nota a la comparación de versiones, preferiría dejar serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare (localVersion, options: .numeric) == .orderedDescending en lugar de reemplazar el. con vacío.
Chaitu

@Chaitu gracias por la sugerencia. Terminé reescribiendo la parte de comparación del código
budidino

9

Se actualizó el código swift 4 de Anup Gupta

He realizado algunas modificaciones en este código . Ahora las funciones se llaman desde una cola en segundo plano, ya que la conexión puede ser lenta y por tanto bloquear el hilo principal.

También hice que CFBundleName sea opcional, ya que la versión presentada tenía "CFBundleDisplayName" que probablemente no funcionó en mi versión. Entonces, si no está presente, no se bloqueará, pero no mostrará el nombre de la aplicación en la alerta.

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

Hago esta llamada para agregar también el botón de confirmación:

AppUpdater.shared.showUpdate(withConfirmation: true)

O llámelo para que se llame así para tener la opción de forzar actualización en:

AppUpdater.shared.showUpdate(withConfirmation: false)

¿Alguna idea sobre cómo probar esto? Si no funciona correctamente, la única forma de depurarlo es depurar de alguna manera una versión anterior a la que se encuentra en la tienda de aplicaciones.
David Rector

2
Ah, no importa la pregunta. Simplemente puedo cambiar mi versión local para que sea "más antigua".
David Rector

Estoy impresionado con tu código @Vasco. Solo una simple pregunta, ¿por qué ha usado 'http' en lugar de https en esa URL?
Master AgentX

¡Muchas gracias por compartir esta solución @Vasco! Me gusta :) ¿Por qué no usas: let config = URLSessionConfiguration.background (withIdentifier: "com.example.MyExample.background") para que URLSession logre la solicitud en segundo plano?
mc_plectrum

También puede deshacerse de la fuerza de desenvolver, como ya verificó si deja appStoreAppVersion = info? .Version y lo mismo para trackURL.
mc_plectrum

7

Aquí está mi versión que usa Swift 4 y la popular biblioteca Alamofire (de todos modos la uso en mis aplicaciones). La solicitud es asincrónica y puede pasar una devolución de llamada para recibir una notificación cuando haya terminado.

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

El uso es tan simple como esto:

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

1
El problema con el uso de ourVersion! = appVersion es que se activa cuando el equipo de revisión de la App Store verifica la nueva versión de la aplicación. Convertimos esas cadenas de versión en números y luego isNew = appVersion> ourVersion.
Budidino

@budidino, tienes razón, acabo de mostrar el enfoque común usando Alamofire. La forma de interpretar la versión depende totalmente de la estructura de la aplicación y la versión.
Capitán del Norte

Simplemente agregando una nota a la comparación de versiones, preferiría dejar serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare (localVersion, options: .numeric) == .orderedDescending en lugar de comparar con igual
Chaitu

6

¿Puedo sugerir esta pequeña biblioteca: https://github.com/nicklockwood/iVersion

Su propósito es simplificar el manejo de plists remotas para activar notificaciones.


3
Puede consultar la App Store directamente para el número de versión en lugar de alojar un archivo plist en algún lugar. Consulte esta respuesta: stackoverflow.com/a/6569307/142358
Steve Moser

1
iVersion ahora usa la versión de la tienda de aplicaciones automáticamente; la lista Plist es opcional si desea especificar notas de la versión diferentes a las de iTunes, pero no necesita usarla.
Nick Lockwood

1
Este código podría necesitar algunas mejoras, pero es mucho mejor que las otras respuestas que envían una solicitud sincrónica. Aún así, la forma en que se enhebra es de mal estilo. Presentaré problemas en Github.
uliwitness

El proyecto ahora está obsoleto 😢
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
}

Esto falla cuando no tienes conexión a Internet. dejar data = probar? Data (contentsOf: url!) Devolverá nil, y en la siguiente línea, ¡haga datos!
Joris Mans

gracias @JorisMans Lo actualizaré para que no se bloquee la conectividad a Internet
Kassem Itani

No hagas esto. Utilice URLSession.
JAL

4

Esta respuesta es una modificación de la respuesta de datinc https://stackoverflow.com/a/25210143/2735358 .

La función de datinc compara versión por comparación de cadena. Por lo tanto, no comparará la versión por mayor o menor que.

Pero esta función modificada compara la versión por NSNumericSearch (comparación numérica) .

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

Utilizar:

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

3
Esta respuesta realiza su solicitud de forma sincrónica. Esto significa que con una mala conexión, su aplicación podría quedar inutilizable durante minutos hasta que vuelva la solicitud.
uliwitness

NSURLSession funciona en subprocesos en segundo plano automáticamente a menos que especifiquemos lo contrario.
Sebastian Dwornik

4

Vi muchas formas de verificar la actualización de la aplicación. así que en base a muchas respuestas, las mezclo y creo mi solución que está disponible en GitHub. Si se requiere alguna actualización, hágamelo saber. Este código para Swift 4

Enlace de GitHub a este código. 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)
    }
}

Refrence: https://stackoverflow.com/a/48810541/5855888 y https://github.com/emotality/ATAppUpdater

Codificación feliz 👍 😊


@Rob Por favor revise el enlace de GitHub github.com/anupgupta-arg/iOS-Swift-ArgAppUpdater
Anup Gupta

3

Intente esto con una sola llamada de función:

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

Función de alerta para abrir la URL de la 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)

}

Cómo llamar a la función anterior:

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

Para obtener más detalles, pruebe el siguiente enlace con el código completo:

AppStoreUpdate.swift

ItunesAppInfoResult.swift

ItunesAppInfoItunes.swift

¡Espero que esto ayude!


2

Aquí hay un método rápido que hace lo que sugieren algunas de las respuestas de Objective-C. Obviamente, una vez que obtenga la información de la tienda de aplicaciones JSON, puede extraer las notas de la versión, si las desea.

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 es la URL de la aplicación en la tienda de aplicaciones?
iamthevoid

@Mario Hendricks esto no funciona en Swift 3. Arroja algunos errores. ¿Puedes actualizar para swift 3?
George Asda

Esta respuesta realiza su solicitud de forma sincrónica. Esto significa que con una mala conexión, su aplicación podría quedar inutilizable durante minutos hasta que vuelva la solicitud.
uliwitness

2

Si no está configurando el tipo de contenido en NSUrlRequest, entonces seguro que no obtendrá respuesta, así que pruebe el siguiente código, funciona bien para mí. Espero eso ayude....

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

Esta respuesta realiza su solicitud de forma sincrónica. Esto significa que con una mala conexión, su aplicación podría quedar inutilizable durante minutos hasta que vuelva la solicitud.
uliwitness

2

Viniendo de un punto de vista de aplicación híbrida, este es un ejemplo de javascript, tengo un pie de página de actualización disponible en mi menú principal. Si hay una actualización disponible (es decir, mi número de versión dentro del archivo de configuración es menor que la versión recuperada, muestre el pie de página) Esto dirigirá al usuario a la tienda de aplicaciones, donde el usuario puede hacer clic en el botón actualizar.

También obtengo los datos nuevos (es decir, notas de la versión) y los muestro en un modal al iniciar sesión si es la primera vez en esta versión.

El método Actualizar disponible se puede ejecutar con la frecuencia que desee. El mío se ejecuta cada vez que el usuario navega a la pantalla de inicio.

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

}

Devolución de llamada: Apple tiene una API, por lo que es muy fácil de obtener

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

Advertencia: la mayoría de las respuestas dadas recuperan la URL sincrónicamente (usando -dataWithContentsOfURL:o -sendSynchronousRequest:. Esto es malo, ya que significa que su aplicación no responderá durante varios minutos si la conexión móvil se cae mientras la solicitud está en curso. Nunca acceda a Internet sincrónicamente en el Hilo principal.

La respuesta correcta es usar API asincrónica:

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

El tiempo de espera predeterminado para las conexiones de red es de varios minutos, e incluso si la solicitud se realiza, puede ser lo suficientemente lento en una conexión EDGE defectuosa como para tardar tanto. En ese caso, no desea que su aplicación quede inutilizable. Para probar cosas como esta, es útil ejecutar su código de red con Network Link Conditioner de Apple.


Gracias por mantener viva esta pregunta :-)
porJeevan

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
}

para comparar la cadena de versión:

https://github.com/eure/AppVersionMonitor


2

PARA SWIFT 4 y 3.2:

Primero, necesitamos obtener el ID del paquete del diccionario de información del paquete, establecer isUpdaet como falso.

    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
       }

Luego, debemos llamar a una llamada urlSession para obtener la versión de 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()

EL CÓDIGO COMPLETO SERÁ ASÍ:

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

Entonces podemos llamar a la función cuando necesitemos.

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

2

Equivalencia C # de @datinc, en cuanto a obtener la versión de la App Store de Apple. Código incluido para obtener la versión del paquete o del archivo AssemblyInfo.

EDITAR :: Tenga en cuenta la región, "/ us /", incluida en urlString. Este código de país deberá manipularse / cambiarse en consecuencia.

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

Esta pregunta se hizo en 2011, la encontré en 2018 mientras buscaba alguna forma no solo para verificar la nueva versión de la aplicación en la App Store, sino también para notificar al usuario al respecto.

Después de una pequeña investigación, llegué a la conclusión de que la respuesta de juanjo (relacionada con Swift 3) https://stackoverflow.com/a/40939740/1218405 es la solución óptima si quieres hacer esto en código por ti mismo

También puedo sugerir dos grandes proyectos en GitHub (más de 2300 estrellas cada uno)

Ejemplo de Siren (AppDelegate.swift)

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

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

      return true
    }
  • También puede mostrar diferentes tipos de alertas sobre la nueva versión (lo que permite omitir la versión o obliga al usuario a actualizar)
  • Puede especificar la frecuencia con la que debe realizarse la verificación de la versión (diaria / semanal / inmediata)
  • Puede especificar cuántos días después del lanzamiento de la nueva versión en la tienda de aplicaciones debe aparecer la alerta

Los enlaces a una respuesta existente no son respuestas. Además, los enlaces a las bibliotecas tampoco son respuestas a menos que agregue explícitamente cómo el enlace responde la pregunta a su respuesta (agregue ejemplos de código, etc.).
JAL

1

Rápido 4

Podemos usar el nuevo JSONDecoderpara analizar la respuesta de itunes.apple.com/lookup y representarla con clases o estructuras decodificables:

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

class AppInfo: Decodable {
    var version: String
}

También podemos agregar otras propiedades AppInfoen caso de que necesitemos elreleaseNotes o alguna otra propiedad.

Ahora podemos hacer una solicitud asincrónica 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
}

esta función recibe un cierre de finalización que se llamará cuando se complete la solicitud y devuelve un URLSessionDataTasken caso de que necesitemos cancelar la solicitud, y se puede llamar así:

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

¿Dónde pusiste este código? Veo que configuró LookupResult y AppInfo como decodificables, pero no los veo guardados en ninguna parte. ¿Que me estoy perdiendo aqui?
jessi

Declaras las clases LookupResulty AppInfoen algún lugar de tu proyecto, preferiblemente en un archivo separado: se usan cuando decodificas la respuesta: JSONDecoder().decode(LookupResult.self, from: data)y contienen la cadena de versión
juanjo

Basado en su respuesta, creo un archivo usando su código. Por favor verifique que iOS-Swift-ArgAppUpdater
Anup Gupta

@jessi por favor verifique Mi código en GitHub Publiqué allí su solución
Anup Gupta

0

Mi propuesta de código. Basado en las respuestas de @datinc y @ Mario-Hendricks

Por supuesto, debe reemplazarlo dlog_Errorcon su llamada de función de registro.

Este tipo de estructura de código debería evitar que su aplicación se bloquee en caso de error. Para obtener el appStoreAppVersionno es imperativo y no debe conducir a errores fatales. Y, sin embargo, con este tipo de estructura de código, seguirá registrando su error no fatal.

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
Esta respuesta realiza su solicitud de forma sincrónica. Esto significa que con una mala conexión, su aplicación podría quedar inutilizable durante minutos hasta que vuelva la solicitud.
uliwitness

-1

Actualizado para swift 3:

si desea verificar la versión actual de su aplicación, use el siguiente código simple:

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

  let version = object as! String
  print("version: \(version)")
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.