Verifique se meu aplicativo tem uma nova versão na AppStore

111

Gostaria de verificar manualmente se há novas atualizações para meu aplicativo enquanto o usuário está nele e solicitar que ele baixe a nova versão. Posso fazer isso verificando a versão do meu aplicativo na app store - programaticamente?

user542584
fonte
6
Você pode colocar uma página aleatória em um servidor web que retorna apenas uma representação de string da versão mais recente. Baixe-o e compare-o na inicialização do aplicativo e notifique o usuário. (
Maneira
1
obrigado, mas eu estava esperando por uma solução melhor como algum tipo de API com a qual eu possa chamar as funcionalidades da app store, como pesquisar o número do meu aplicativo e obter os dados da versão. Economiza tempo para manter um servidor web apenas para essa finalidade, mas obrigado pelo ponteiro de qualquer maneira!
user542584
Eu faço a mesma coisa que o primeiro comentário. Escrevi um plist com uma entrada: um NSNumbernúmero de versão. Então eu carreguei no meu site. O mesmo site que uso para o suporte do meu aplicativo e páginas da web do aplicativo, em seguida viewDidLoad, verifico o número da versão do site e verifico a versão atual no meu aplicativo. Então eu tenho um premade alertViewque solicita automaticamente a atualização do aplicativo. Posso fornecer o código, se desejar.
Andrew
obrigado, acho que devo tentar isso também ..
user542584

Respostas:

88

Aqui está um trecho de código simples que permite saber se a versão atual é 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: Certifique-se de que, ao inserir a nova versão no iTunes, ela corresponda à versão do aplicativo que você está lançando. Caso contrário, o código acima sempre retornará SIM, independentemente de o usuário atualizar.

Datinc
fonte
4
super solução que eu já encontrei +1
Sanjay Changani
1
@MobeenAfzal, acho que você não entendeu a pergunta e a solução. A solução acima compara a versão atual com a versão na loja. Se eles não corresponderem, ele reajusta SIM, caso contrário, retorna NÃO. Não importa o histórico na app store, o método acima retornará SIM se a versão atual for diferente da versão da app store. Depois que o usuário atualiza ... a versão atual é igual à versão da app store. O método acima sempre deve retornar SIM se a versão do usuário for 1.0 e a versão da app store for 1.2.
datinc de
1
@MobeenAfzal Acho que entendi o que você está vendo. No código, sua versão é 1.7, mas no iTunes você carregou a versão 1.6 para que seus usuários não saibam que você pulou uma versão. É esse o caso? Se sim, então ... o que você precisa é de um servidor (o DropBox faria) para servir o número da versão do seu aplicativo e modificar o seu código para acessar esse endpoint. Deixe-me saber se é isso que você está vendo e adicionarei uma nota de advertência à postagem.
datinc de
1
@MobeenAfzal, seu comentário é enganoso. Se a versão no dispositivo do usuário for separada por qualquer uma da versão no appstore, o código retornará SIM conforme o esperado. Mesmo se você lançar a versão 1.0 seguida pela versão 1.111, ela ainda funcionará perfeitamente.
datinc
1
Devemos mostrar update apenas quando a versão do appstore for maior que a versão atual da seguinte maneira. if ([appStoreVersion compare: opções currentVersion: NSNumericSearch] == NSOrderedDescending) {NSLog (@ "\ n \ nPrecisa atualizar. A versão da Appstore% @ é maior que% @", appStoreVersion, currentVersion); }
Nitesh Borad
52

Versão 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
}

Acho melhor lançar um erro ao invés de retornar falso, neste caso eu criei um VersionError mas pode ser algum outro que você definir ou NSError

enum VersionError: Error {
    case invalidResponse, invalidBundleInfo
}

Considere também chamar esta função de outro thread, se a conexão for lenta, ela pode bloquear o thread atual.

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

Atualizar

Usando URLSession:

Em vez de usar Data(contentsOf: url)e bloquear um thread, 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
}

exemplo:

_ = try? isUpdateAvailable { (update, error) in
    if let error = error {
        print(error)
    } else if let update = update {
        print(update)
    }
}
Juanjo
fonte
1
Essa resposta faz sua solicitação de forma síncrona. Isso significa que, com uma conexão ruim, seu aplicativo pode ficar inutilizável por minutos até que a solicitação retorne.
uliwitness
4
Eu discordo, DispatchQueue.global()dá uma fila de background, os dados são carregados nessa fila e só voltam para a fila principal quando os dados são carregados.
Juanjo
Opa. De alguma forma, esqueci aquele segundo trecho de código. Infelizmente, parece que não posso remover o downvote até que sua resposta seja editada novamente :-( BTW - Dado dataWithContentsOfURL: na verdade, passa por chamadas síncronas de NSURLConnection, que por sua vez apenas iniciam um thread assíncrono e bloqueiam, provavelmente seria menos sobrecarga para usar apenas as chamadas NSURLSession assíncronas. Eles até ligariam de volta para o thread principal assim que você terminar.
uliwitness
@juanjo ,,,, não está funcionando para o swift 3.0.1, por favor, pode fazer upload atualizado para o swift ??
Kiran jadhav
2
Observe que se você estiver listado apenas em uma loja específica, descobri que você precisa adicionar um código de país ao URL - por exemplo, GB itunes.apple.com/(countryCode)/… )
Ryan Heitner
13

Agradecimentos a Steve Moser por seu link, aqui está meu 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"];
Roozbeh Zabihollahi
fonte
1
solução muito boa e correta, apenas uma pequena atualização em relação ao url é itunes.apple.com/en/lookup?bundleId=xxxxxxxxxx
SJ
Obrigado, o seu comentário foi aplicado
Roozbeh Zabihollahi
4
Na verdade, não funcionou para mim com o /en/subcaminho. Depois de removê-lo, funcionou
gasparuff
Essa resposta faz sua solicitação de forma síncrona. Isso significa que, com uma conexão ruim, seu aplicativo pode ficar inutilizável por minutos até que a solicitação retorne.
uliwitness
1
Tive que usar com o / en / itunes.apple.com/lookup?bundleId=xxxxxxx , obrigado @gasparuff
Fernando Perez
13

Como estava enfrentando o mesmo problema, encontrei a resposta fornecida por Mario Hendricks . Quando tentei aplicar seu código ao meu projeto, o XCode reclamou de problemas de Casting dizendo "MDLMaterialProperty não tem membros subscritos". Seu código estava tentando definir este MDLMaterial ... como o tipo da constante "lookupResult", fazendo com que a conversão para "Int" falhasse todas as vezes. Minha solução foi fornecer uma anotação de tipo para minha variável para NSDictionary para ser claro sobre o tipo de valor que eu precisava. Com isso, pude acessar o valor "versão" de que precisava.

Obs: Para este YOURBUNDLEID , você pode obter em seu projeto Xcode .... " Targets> General> Identity> Bundle Identifier "

Então, aqui está o meu código com algumas simplificações também:

  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 as sugestões para melhoria deste código são bem-vindas!

Yago Zardo
fonte
Essa resposta faz sua solicitação de forma síncrona. Isso significa que, com uma conexão ruim, seu aplicativo pode ficar inutilizável por minutos até que a solicitação retorne.
uliwitness
@Yago Zardo, use a função de comparação caso contrário, quando o usuário enviar app.apple testou atualização de exibição de tempo alertview ou apple rejeitou seu aplicativo
Jigar Darji
Ei @Jigar, obrigado pelo conselho. No momento, não estou usando mais esse método no meu aplicativo porque agora estamos controlando a versão de tudo em nosso servidor. Enfim, você poderia explicar melhor o que disse? Não entendi e parece mesmo uma boa coisa saber. Desde já, obrigado.
Yago Zardo de
Obrigado @uliwitness pela dica, realmente me ajudou a melhorar meu código em geral para aprender sobre solicitações assíncronas e síncronas.
Yago Zardo de
Esse link é uma jóia!
B3none
13

Basta usar ATAppUpdater . Tem 1 linha, thread-safe e rápido. Ele também tem métodos de delegação se você quiser rastrear a ação do usuário.

Aqui está um exemplo:

- (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 delegação opcionais:

- (void)appUpdaterDidShowUpdateDialog;
- (void)appUpdaterUserDidLaunchAppStore;
- (void)appUpdaterUserDidCancel;
emotalidade
fonte
1
Isso funcionará para versões beta no Testflight? Se não, existe alguma ferramenta que o faça?
Lukasz Czerwinski
Não, não vai, apenas compara a versão atual com a versão mais recente que está na AppStore.
emotality
Podemos usar isso com o Swift?
Zorayr
11

Simplificou uma ótima resposta postada neste tópico. Usando 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 então para usá-lo:

VersionCheck.shared.isUpdateAvailable() { hasUpdates in
  print("is update available: \(hasUpdates)")
}
budidino
fonte
2
Meu aplicativo está ativo na loja, mas a mesma API não retorna informações de versão. Resposta:{ "resultCount":0, "results": [] }
technerd
Apenas adicionando uma nota à comparação de versão, eu preferiria, let serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare (localVersion, options: .numeric) == .orderedDescending em vez de substituir o. com vazio.
Chaitu
@Chaitu obrigado pela sugestão. Acabei reescrevendo a parte de comparação do código
budidino
9

Atualizado o código do swift 4 de Anup Gupta

Eu fiz algumas alterações neste código . Agora as funções são chamadas de uma fila de segundo plano, já que a conexão pode ser lenta e, portanto, bloquear o thread principal.

Também tornei o CFBundleName opcional, já que a versão apresentada tinha "CFBundleDisplayName" que provavelmente não funcionou na minha versão. Portanto, agora, se não estiver presente, ele não travará, mas apenas não exibirá o nome do aplicativo no 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 ""
        }
    }
}

Faço esta chamada para também adicionar o botão de confirmação:

AppUpdater.shared.showUpdate(withConfirmation: true)

Ou chame-o para ser chamado assim para ter a opção de forçar atualização em:

AppUpdater.shared.showUpdate(withConfirmation: false)
Vasco
fonte
Alguma ideia de como testar isso? Se não funcionar direito, a única maneira de depurar é depurar de alguma forma uma versão mais antiga do que a da app store.
David Rector
2
Ah, não importa a pergunta. Posso simplesmente alterar minha versão local para "mais antiga".
David Rector
Estou impressionado com seu código @Vasco. Apenas uma pergunta simples, por que você usou 'http' em vez de https nesse url?
Master AgentX
Muito obrigado por compartilhar esta solução @Vasco! Eu gosto :) Por que você não usa: let config = URLSessionConfiguration.background (withIdentifier: "com.example.MyExample.background") para o URLSession atingir a solicitação de plano de fundo?
mc_plectrum
Você também pode se livrar do desdobramento forçado, já que verifica se se deixa appStoreAppVersion = info? .Version e o mesmo para trackURL.
mc_plectrum
7

Aqui está minha versão usando Swift 4 e a popular biblioteca Alamofire (eu a uso em meus aplicativos mesmo assim). A solicitação é assíncrona e você pode passar um retorno de chamada para ser notificado quando terminar.

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

O uso é simples assim:

VersionCheck.shared.checkAppStore() { isNew, version in
        print("IS NEW VERSION AVAILABLE: \(isNew), APP STORE VERSION: \(version)")
    }
Capitão do norte
fonte
1
O problema de usar ourVersion! = appVersion é que ele dispara quando a equipe de revisão da App Store verifica a nova versão do aplicativo. Convertemos essas strings de versão em números e, em seguida, isNew = appVersion> ourVersion.
budidino
@budidino você está certo, acabei de mostrar a abordagem comum usando o Alamofire. A forma como você interpreta a versão depende totalmente do seu aplicativo e da estrutura da versão.
Capitão do Norte
Apenas adicionando uma nota à comparação de versão, eu preferiria, deixe serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare (localVersion, options: .numeric) == .orderedDescending em vez de comparar com igual
Chaitu,
6

Posso sugerir esta pequena biblioteca: https://github.com/nicklockwood/iVersion

Seu objetivo é simplificar o manuseio de plists remotos para acionar notificações.

Andrea
fonte
3
Você pode verificar o número da versão na App Store diretamente em vez de hospedar um arquivo plist em algum lugar. Confira esta resposta: stackoverflow.com/a/6569307/142358
Steve Moser
1
iVersion agora usa a versão da app store automaticamente - o Plist é opcional se você deseja especificar notas de lançamento diferentes das do iTunes, mas não precisa usá-lo.
Nick Lockwood
1
Este código pode ter algumas melhorias, mas é muito melhor do que as outras respostas que enviam uma solicitação síncrona. Ainda assim, a maneira como ele faz o threading é um estilo ruim. Vou registrar problemas no Github.
uliwitness
O projeto está obsoleto agora 😢
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
}
Kassem Itani
fonte
Isso trava quando você não tem conexão com a Internet. deixe os dados = tentar? Data (contentsOf: url!) Retornará nil, e na próxima linha você fará data!
Joris Mans
thx @JorisMans irei atualizá-lo para que não haja falha de conectividade com a Internet
Kassem Itani
Não faça isso. Use URLSession.
JAL
4

Esta resposta é uma modificação da resposta da datinc https://stackoverflow.com/a/25210143/2735358 .

função de datinc compara versão por comparação de string. Portanto, não vai comparar a versão para maior ou menor que.

Mas, esta função modificada compara a versão por NSNumericSearch (comparação 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];
}

Usar:

[self checkForUpdateWithHandler:^(BOOL isUpdateAvailable) {
    if (isUpdateAvailable) {
        // show alert
    }
}];
Nitesh Borad
fonte
3
Essa resposta faz sua solicitação de forma síncrona. Isso significa que, com uma conexão ruim, seu aplicativo pode ficar inutilizável por minutos até que a solicitação retorne.
uliwitness
NSURLSession funciona em threads de fundo automaticamente, a menos que especifique o contrário.
Sebastian Dwornik
4

Eu vi muitas maneiras de verificar a atualização do aplicativo. então, com base em muitas respostas, eu as misturo e crio minha solução que está disponível no GitHub. Se alguma atualização for necessária, por favor me avise. Este código para Swift 4

Link do GitHub para 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)
    }
}

Atualizar: https://stackoverflow.com/a/48810541/5855888 E https://github.com/emotality/ATAppUpdater

Happy Coding 👍 😊

Anup Gupta
fonte
@Rob Verifique o link do GitHub github.com/anupgupta-arg/iOS-Swift-ArgAppUpdater
Anup Gupta
2

Aqui está um método rápido que faz o que algumas das respostas do Objective-C sugerem. Obviamente, depois de obter as informações do JSON da loja de aplicativos, você pode extrair as notas de lançamento, se quiser.

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
}
Mario Hendricks
fonte
storeInfoURL é o url do aplicativo na appstore?
iamthevoid
@Mario Hendricks isso não está funcionando no swift 3. Ele lança alguns erros. Você pode atualizar para o Swift 3?
George Asda
Essa resposta faz sua solicitação de forma síncrona. Isso significa que, com uma conexão ruim, seu aplicativo pode ficar inutilizável por minutos até que a solicitação retorne.
uliwitness
2

Se você não estiver definindo o tipo de conteúdo em NSUrlRequest, com certeza não obterá resposta, então tente o código abaixo, ele funciona bem para mim. Espero que ajude....

-(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;
}
ganka
fonte
Essa resposta faz sua solicitação de forma síncrona. Isso significa que, com uma conexão ruim, seu aplicativo pode ficar inutilizável por minutos até que a solicitação retorne.
uliwitness
2

Vindo de um POV de aplicativo híbrido, este é um exemplo de javascript, eu tenho um rodapé de atualização disponível no meu menu principal. Se uma atualização estiver disponível (ou seja, meu número de versão no arquivo de configuração é menor do que a versão recuperada, exiba o rodapé) Isso direcionará o usuário para a loja de aplicativos, onde o usuário pode clicar no botão atualizar.

Eu também pego os novos dados (ou seja, notas de versão) e os exibo em um modal no login se for a primeira vez nesta versão.

O método Update Available pode ser executado com a freqüência que você desejar. O meu é executado sempre que o usuário navega para a tela inicial.

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

}

Callback: a Apple tem uma API, muito fácil de obter

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);
    }
}
tyler_mitchell
fonte
2

Aviso: a maioria das respostas fornecidas recupera o URL de forma síncrona (usando -dataWithContentsOfURL:ou -sendSynchronousRequest:. Isso é ruim, pois significa que seu aplicativo não responderá por vários minutos se a conexão móvel cair enquanto a solicitação estiver em andamento. Nunca acesse a Internet de forma síncrona no thread principal.

A resposta correta é usar API assíncrona:

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

O tempo limite padrão para conexões de rede é de vários minutos e, mesmo que a solicitação seja concluída, ela pode ser lenta o suficiente em uma conexão EDGE ruim para demorar tanto. Nesse caso, você não quer que seu aplicativo fique inutilizável. Para testar coisas como essa, é útil executar seu código de rede com o Network Link Conditioner da Apple.

uliwitness
fonte
Obrigado por manter esta questão viva :-)
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
}

para comparar a string de versão:

https://github.com/eure/AppVersionMonitor

Lova
fonte
2

PARA SWIFT 4 e 3.2:

Primeiro, precisamos obter o id do bundle do dicionário de informações do bundle, definir isUpdaet como 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
       }

Em seguida, precisamos chamar uma chamada urlSession para obter a versão do 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()

O CÓDIGO COMPLETO SERÁ ASSIM:

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

Então, podemos chamar a função de qualquer software de que precisarmos.

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

Equivalência C # de @datinc, na medida em que se obtém a versão da Apple App Store. Código incluído para obter a versão do pacote ou do arquivo AssemblyInfo.

EDIT :: Observe a região, "/ us /", incluída na urlString. Este código de país precisará ser manipulado / alterado de acordo.

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();
}
jtth
fonte
2

Tente fazer isso com uma única chamada de função:

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

Função de alerta para abrir o URL da 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)

}

Como chamar a função acima:

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

Para obter mais detalhes, tente o link abaixo com o código completo:

AppStoreUpdate.swift

ItunesAppInfoResult.swift

ItunesAppInfoItunes.swift

Espero que isso ajude!

CodeChanger
fonte
1

Esta pergunta foi feita em 2011, eu a encontrei em 2018 enquanto procurava uma maneira não apenas de verificar a nova versão do aplicativo na App Store, mas também de notificar o usuário sobre ela.

Após uma pequena pesquisa, cheguei à conclusão de que a resposta de juanjo (relacionada ao Swift 3) https://stackoverflow.com/a/40939740/1218405 é a solução ideal se você quiser fazer isso em código por conta própria

Além disso, posso sugerir dois grandes projetos no GitHub (mais de 2300 estrelas cada)

Exemplo para Siren (AppDelegate.swift)

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

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

      return true
    }
  • Você também pode mostrar diferentes tipos de alertas sobre a nova versão (permitindo pular a versão ou forçando o usuário a atualizar)
  • Você pode especificar a frequência com que a verificação de versão deve ocorrer (diariamente / semanalmente / imediatamente)
  • Você pode especificar quantos dias após o lançamento da nova versão para o alerta da app store deve aparecer
moonvader
fonte
Links para uma resposta existente não são respostas. Além disso, os links para bibliotecas também não são respostas, a menos que você adicione explicitamente como o link responde à pergunta à sua resposta (adicione exemplos de código etc.).
JAL de
1

Swift 4

Podemos usar o novo JSONDecoderpara analisar a resposta de itunes.apple.com/lookup e representá-la com classes ou estruturas decodificáveis:

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

class AppInfo: Decodable {
    var version: String
}

Também podemos adicionar outras propriedades AppInfono caso de precisarmos da releaseNotesou alguma outra propriedade.

Agora podemos fazer uma solicitação assíncrona 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 função recebe um fechamento de conclusão que será chamado quando a solicitação for concluída e retorna um URLSessionDataTaskcaso seja necessário cancelar a solicitação, e pode ser chamada assim:

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")
        }
    }
}
Juanjo
fonte
Onde você colocou esse código? Vejo que você configurou LookupResult e AppInfo como decodificáveis, mas não os vejo salvos em lugar nenhum. O que estou perdendo aqui?
Jessi
Você declara as classes LookupResulte AppInfoem algum lugar do seu projeto, preferencialmente em um arquivo separado: Elas são usadas quando você decodifica a resposta: JSONDecoder().decode(LookupResult.self, from: data)e contêm a string de versão
juanjo
Com base na sua resposta, eu crio um arquivo usando o seu código Verifique se iOS-Swift-ArgAppUpdater
Anup Gupta
@jessi verifique Meu código no GitHub. Postei lá sua solução
Anup Gupta
0

Minha proposta de código. Com base nas respostas de @datinc e @ Mario-Hendricks

Você deve, é claro, substituir dlog_Errorpor sua chamada de função de registro.

Esse tipo de estrutura de código deve evitar que seu aplicativo falhe no caso de um erro. Buscar o appStoreAppVersionnão é obrigatório e não deve levar a erros fatais. E ainda, com este tipo de estrutura de código, você ainda terá seu erro não fatal registrado.

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())
    }
}
SirEnder
fonte
1
Essa resposta faz sua solicitação de forma síncrona. Isso significa que, com uma conexão ruim, seu aplicativo pode ficar inutilizável por minutos até que a solicitação retorne.
uliwitness
-1

Atualizado para swift 3:

se você deseja verificar a versão atual do seu aplicativo, usado abaixo do código simples:

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

  let version = object as! String
  print("version: \(version)")
Kiran Jadhav
fonte