Sprawdź, czy moja aplikacja ma nową wersję w AppStore


113

Chciałbym ręcznie sprawdzić, czy są dostępne nowe aktualizacje mojej aplikacji, gdy jest w niej użytkownik, i poprosić go o pobranie nowej wersji. Czy mogę to zrobić, sprawdzając wersję mojej aplikacji w sklepie z aplikacjami - programowo?


6
Możesz umieścić losową stronę na serwerze sieciowym, która zwraca jedynie ciąg znaków reprezentujący najnowszą wersję. Pobierz go i porównaj po uruchomieniu aplikacji i powiadom użytkownika. (Szybki i łatwy sposób)
LouwHopley

1
dzięki, ale liczyłem na lepsze rozwiązanie, takie jak jakiś rodzaj API, za pomocą którego mogę wywoływać funkcje sklepu z aplikacjami, takie jak wyszukiwanie numeru mojej aplikacji i pobieranie danych o wersji. Oszczędza czas na utrzymanie serwera WWW tylko w tym celu, ale mimo wszystko dzięki za wskaźnik!
user542584

Robię to samo, co pierwszy komentarz. Napisałem plistę z jednym wpisem: NSNumbernumerem wersji. Następnie umieściłem go na mojej stronie internetowej. Na tej samej stronie internetowej, której używam do obsługi aplikacji i stron internetowych aplikacji, następnie viewDidLoadsprawdzam tam numer wersji witryny i sprawdzam aktualną wersję w mojej aplikacji. Następnie mam gotowe, alertViewktóre automatycznie monituje o aktualizację aplikacji. Mogę podać kod, jeśli chcesz.
Andrew,

dzięki, chyba też powinienem spróbować ..
user542584,

Odpowiedzi:


89

Oto prosty fragment kodu, który informuje, czy aktualna wersja jest inna

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

Uwaga: upewnij się, że po wprowadzeniu nowej wersji w iTunes jest ona zgodna z wersją w wydawanej aplikacji. Jeśli nie, powyższy kod zawsze zwróci TAK, niezależnie od tego, czy użytkownik aktualizuje.


4
super rozwiązanie, jakie kiedykolwiek znalazłem +1
Sanjay Changani

1
@MobeenAfzal, myślę, że brakuje ci zrozumienia pytania i rozwiązania. Powyższe rozwiązanie porównuje aktualną wersję z wersją w sklepie. Jeśli nie pasują, ponownie dostraja TAK, w przeciwnym razie zwraca NIE. Bez względu na historię w sklepie z aplikacjami powyższa metoda zwróci TAK, jeśli bieżąca wersja jest inna niż wersja ze sklepu z aplikacjami. Gdy użytkownik zaktualizuje ... aktualna wersja jest równa wersji z App Store. Powyższa metoda powinna zawsze zwracać TAK, jeśli wersja użytkownika to 1.0, a wersja sklepu z aplikacjami to 1.2.
datinc

1
@MobeenAfzal Myślę, że rozumiem to, co widzisz. W kodzie Twoja wersja to 1.7, ale w iTunes przesłałeś wersję jako 1.6, aby Twoi użytkownicy nie wiedzieli, że pominąłeś wersję. Czy tak jest? Jeśli tak, to ... potrzebujesz serwera (zrobiłby to DropBox) do obsługi numeru wersji aplikacji i modyfikacji kodu, aby uzyskać dostęp do tego punktu końcowego. Daj mi znać, jeśli to jest to, co widzisz, a dodam notatkę z ostrzeżeniem do postu.
datinc

1
@MobeenAfzal you Twój komentarz jest mylący. Jeśli wersja na urządzeniu użytkownika jest oddzielona dowolną wersją od wersji w sklepie z aplikacjami, kod zwróci TAK zgodnie z oczekiwaniami. Nawet jeśli wydasz wersję 1.0, a następnie wersję 1.111, nadal będzie działać idealnie.
data

1
Powinniśmy wyświetlać aktualizację tylko wtedy, gdy wersja Appstore jest większa niż bieżąca wersja, jak poniżej. if ([porównanie appStoreVersion: opcje currentVersion: NSNumericSearch] == NSOrderedDescending) {NSLog (@ "\ n \ nPotrzebna aktualizacja. Wersja% @ sklepu Appstore jest większa niż% @", appStoreVersion, currentVersion); }
Nitesh Borad

52

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

Myślę, że lepiej jest rzucić błąd zamiast zwracać false, w tym przypadku utworzyłem VersionError ale może to być jakiś inny, który zdefiniujesz lub NSError

enum VersionError: Error {
    case invalidResponse, invalidBundleInfo
}

Rozważ również wywołanie tej funkcji z innego wątku, jeśli połączenie jest wolne, może zablokować bieżący wątek.

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

Aktualizacja

Korzystanie z sesji URL:

Zamiast używać Data(contentsOf: url)i blokować wątek, możemy użyć 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
}

przykład:

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

1
Ta odpowiedź sprawia, że ​​żądanie jest synchroniczne. Oznacza to, że w przypadku złego połączenia aplikacja może być bezużyteczna przez kilka minut, aż żądanie powróci.
uliwitness

4
Nie zgadzam się, DispatchQueue.global()daje kolejkę w tle, dane są ładowane w tej kolejce i wracają do głównej kolejki dopiero po załadowaniu danych.
juanjo

Ups. Jakoś przeoczyłem ten drugi fragment kodu. Niestety, wygląda na to, że nie mogę usunąć głosu przeciwnego, dopóki twoja odpowiedź nie zostanie ponownie edytowana :-( BTW - Given dataWithContentsOfURL: faktycznie przechodzi przez synchroniczne wywołania NSURLConnection, które z kolei po prostu uruchamiają wątek asynchroniczny i blokują, prawdopodobnie byłby mniejszy narzut po prostu używać asynchronicznych wywołań NSURLSession. Gdy skończysz, nawet oddzwonią do Ciebie w głównym wątku.
uliwitness

@juanjo ,,,, nie działa dla Swift 3.0.1, czy możesz przesłać zaktualizowany dla Swift ???
Kiran jadhav,

2
Uwaga, jeśli jesteś wymieniony tylko w określonym sklepie, odkryłem, że musisz dodać kod kraju do adresu URL - np. GB itunes.apple.com/(countryCode)/… )
Ryan Heitner

13

Dzięki Steve Moser za jego link, oto mój kod:

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
bardzo dobre i poprawne rozwiązanie, tylko niewielka aktualizacja dotycząca adresu URL to adresu itunes.apple.com/en/lookup?bundleId=xxxxxxxxxx
SJ

Dzięki, Twój komentarz został zastosowany
Roozbeh Zabihollahi

4
Właściwie to nie zadziałało dla mnie z /en/podścieżką. Po wyjęciu zadziałało
parkiet gazowy

Ta odpowiedź sprawia, że ​​żądanie jest synchroniczne. Oznacza to, że w przypadku złego połączenia aplikacja może być bezużyteczna przez kilka minut, aż żądanie powróci.
uliwitness

1
Musiałem użyć z / en / itunes.apple.com/lookup?bundleId=xxxxxxx , dzięki @gasparuff
Fernando Perez

13

Ponieważ miałem ten sam problem, znalazłem odpowiedź udzieloną przez Mario Hendricksa . Nieoczekiwanie, gdy próbowałem zastosować jego kod w moim projekcie, XCode narzekał na problemy z Castingiem, mówiąc: „MDLMaterialProperty nie ma elementów składowych indeksu”. Jego kod próbował ustawić ten MDLMaterial ... jako typ stałej „lookupResult”, przez co rzutowanie na „Int” za każdym razem kończyło się niepowodzeniem. Moim rozwiązaniem było dostarczenie adnotacji typu dla mojej zmiennej do NSDictionary aby jasno określić, jakiego rodzaju wartości potrzebuję. Dzięki temu mogłem uzyskać dostęp do wartości „wersja”, której potrzebowałem.

Obs: W przypadku tego YOURBUNDLEID możesz pobrać z projektu Xcode .... „ Cele> Ogólne> Tożsamość> Identyfikator pakietu

Oto mój kod z pewnymi uproszczeniami:

  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
}

Wszelkie sugestie dotyczące ulepszenia tego kodu są mile widziane!


Ta odpowiedź sprawia, że ​​żądanie jest synchroniczne. Oznacza to, że w przypadku złego połączenia aplikacja może być bezużyteczna przez kilka minut, aż żądanie powróci.
uliwitness

@Yago Zardo użyj funkcji porównania, w przeciwnym razie, gdy użytkownik załaduje aplikację. Testowany przez Apple wyświetlacz czasu aktualizacja alertview lub Apple odrzuca twoją aplikację
Jigar Darji

Hej @Jigar, dzięki za radę. Obecnie nie używam już tej metody w mojej aplikacji, ponieważ teraz wykonujemy wersjonowanie wszystkiego na naszym serwerze. W każdym razie, czy mógłbyś lepiej wyjaśnić, co powiedziałeś? Nie rozumiem i naprawdę dobrze jest wiedzieć. Z góry dziękuję.
Yago Zardo

Dziękuję @uliwitness za wskazówkę, naprawdę pomogło mi to ogólnie ulepszyć mój kod, aby dowiedzieć się o żądaniach asynchronicznych i synchronicznych.
Yago Zardo

Ten link to prawdziwy klejnot!
B3none

13

Po prostu użyj ATAppUpdater . Jest 1 wiersz, bezpieczny dla wątków i szybki. Ma również metody delegowania, jeśli chcesz śledzić akcję użytkownika.

Oto przykład:

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

   return YES;
}

Opcjonalne metody delegata:

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

1
Czy to zadziała dla wersji beta w Testflight? Jeśli nie, czy jest jakieś narzędzie, które to zrobi?
Łukasz Czerwiński

Nie, nie będzie, porównuje tylko aktualną wersję z najnowszą wersją, która jest w AppStore.
emocje

Czy możemy tego użyć w Swift?
Zorayr

11

Uproszczona świetna odpowiedź zamieszczona w tym wątku. Korzystanie Swift 4i 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
    }
  }

}

A następnie, aby go użyć:

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

2
Moja aplikacja działa w sklepie, ale ten sam interfejs API nie zwraca informacji o wersji. Odpowiedź:{ "resultCount":0, "results": [] }
technerd

Po prostu dodając notatkę do porównania wersji, wolałbym, aby serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare (localVersion, options: .numeric) == .orderedDescending zamiast zastępować. z pustym.
Chaitu

@Chaitu dziękuję za sugestię. Skończyło się na przepisywaniu części kodu
porównawczej

9

Zaktualizowano kod Swift 4 z Anup Gupta

Dokonałem pewnych zmian w tym kodzie . Teraz funkcje są wywoływane z kolejki w tle, ponieważ połączenie może być powolne i blokować główny wątek.

Zrobiłem również opcję CFBundleName jako opcjonalną, ponieważ prezentowana wersja miała „CFBundleDisplayName”, która prawdopodobnie nie działała w mojej wersji. Więc teraz, jeśli nie jest obecny, nie ulegnie awarii, ale po prostu nie wyświetli nazwy aplikacji w alercie.

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

Wzywam również do dodania przycisku potwierdzenia:

AppUpdater.shared.showUpdate(withConfirmation: true)

Lub nazwij to tak, aby mieć opcję wymuszania aktualizacji na:

AppUpdater.shared.showUpdate(withConfirmation: false)

Jakieś pomysły, jak to sprawdzić? Jeśli nie działa poprawnie, jedynym sposobem na debugowanie jest debugowanie starszej wersji niż ta w sklepie z aplikacjami.
David Rector

2
Ach, nieważne pytanie. Mogę po prostu zmienić lokalną wersję na „starszą”.
David Rector

Jestem pod wrażeniem twojego kodu @Vasco. Tylko proste pytanie, dlaczego użyłeś „http” zamiast https w tym adresie URL?
Master Agent X

Wielkie dzięki za udostępnienie tego rozwiązania @Vasco! Podoba mi się :) Dlaczego nie używasz: let config = URLSessionConfiguration.background (withIdentifier: "com.example.MyExample.background") dla URLSession, aby uzyskać żądanie w tle?
mc_plectrum

Możesz także pozbyć się wymuszonego rozpakowywania, jak już sprawdzasz, jeśli niech appStoreAppVersion = info? .Version i to samo dla trackURL.
mc_plectrum

7

Oto moja wersja wykorzystująca Swift 4 i popularną bibliotekę Alamofire (i tak używam jej w moich aplikacjach). Żądanie jest asynchroniczne i możesz przekazać wywołanie zwrotne, aby otrzymać powiadomienie po zakończeniu.

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

Użycie jest takie proste:

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

1
Problem z używaniem ourVersion! = appVersion polega na tym, że jest ona uruchamiana, gdy zespół App Store Review sprawdza nową wersję aplikacji. Konwertujemy te ciągi wersji na liczby, a następnie isNew = appVersion> ourVersion.
budidino

@budidino masz rację, właśnie pokazałem wspólne podejście z wykorzystaniem Alamofire. Sposób interpretacji wersji zależy całkowicie od aplikacji i struktury wersji.
Kapitan Północy

Po prostu dodając notatkę do porównania wersji, wolałbym, żeby serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare (localVersion, options: .numeric) == .orderedDescending zamiast porównywać z równymi
Chaitu

6

Czy mogę zasugerować tę małą bibliotekę: https://github.com/nicklockwood/iVersion

Jego celem jest uproszczenie obsługi zdalnych plist w celu wyzwalania powiadomień.


3
Możesz sprawdzić numer wersji bezpośrednio w App Store zamiast hostować gdzieś plik plist. Sprawdź tę odpowiedź: stackoverflow.com/a/6569307/142358
Steve Moser

1
iVersion używa teraz wersji ze sklepu z aplikacjami automatycznie - Plist jest opcjonalny, jeśli chcesz określić inne informacje o wersji niż te w iTunes, ale nie musisz go używać.
Nick Lockwood,

1
Ten kod może wymagać pewnych ulepszeń, ale jest znacznie lepszy niż inne odpowiedzi, które wysyłają synchroniczne żądanie. Jednak sposób, w jaki to robi, to zły styl. Zgłoszę problemy na Github.
uliwitness

Projekt jest teraz przestarzały 😢
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
}

To ulega awarii, gdy nie masz połączenia z Internetem. let data = spróbować? Data (contentOf: url!) Zwróci nil, aw następnej linii wykonasz dane!
Joris Mans,

thx @JorisMans Zaktualizuję go, aby nie
doszło do

Nie rób tego. Użyj URLSession.
JAL,

4

Ta odpowiedź to modyfikacja odpowiedzi datinc https://stackoverflow.com/a/25210143/2735358 .

Funkcja datinc porównuje wersję przez porównanie ciągów. Nie będzie więc porównywać wersji dla wartości większej lub mniejszej niż.

Ale ta zmodyfikowana funkcja porównuje wersję przez NSNumericSearch (porównanie numeryczne) .

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

Posługiwać się:

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

3
Ta odpowiedź sprawia, że ​​żądanie jest synchroniczne. Oznacza to, że w przypadku złego połączenia aplikacja może być bezużyteczna przez kilka minut, aż żądanie powróci.
uliwitness

NSURLSession działa automatycznie na wątkach w tle, chyba że określimy inaczej.
Sebastian Dwornik

4

Widziałem wiele sposobów sprawdzania aktualizacji aplikacji. więc na podstawie wielu odpowiedzi mieszam je i tworzę swoje rozwiązanie, które jest dostępne na GitHubie. Jeśli wymagana jest jakakolwiek aktualizacja, daj mi znać. Ten kod dla Swift 4

Link do GitHub do tego kodu. 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)
    }
}

Odniesienie: https://stackoverflow.com/a/48810541/5855888 i https://github.com/emotality/ATAppUpdater

Miłego kodowania 👍 😊



3

Spróbuj tego z pojedynczym wywołaniem funkcji:

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

Funkcja ostrzegania, aby otworzyć adres URL 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)

}

Jak wywołać powyższą funkcję:

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

Aby uzyskać więcej szczegółów, wypróbuj poniższy link z pełnym kodem:

AppStoreUpdate.swift

ItunesAppInfoResult.swift

ItunesAppInfoItunes.swift

Mam nadzieję, że to pomoże!


2

Oto szybka metoda, która robi to, co sugerują niektóre z odpowiedzi Objective-C. Oczywiście po uzyskaniu informacji ze sklepu z aplikacjami w formacie JSON możesz wyodrębnić informacje o wersji, jeśli chcesz.

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 to adres URL aplikacji w sklepie z aplikacjami?
iamthevoid

@Mario Hendricks to nie działa w wersji szybkiej 3. Wyrzuca kilka błędów. Czy możesz zaktualizować do Swift 3?
George Asda

Ta odpowiedź sprawia, że ​​żądanie jest synchroniczne. Oznacza to, że w przypadku złego połączenia aplikacja może być bezużyteczna przez kilka minut, aż żądanie powróci.
uliwitness

2

Jeśli nie ustawiasz typu zawartości w NSUrlRequest, na pewno nie otrzymasz odpowiedzi, więc wypróbuj poniższy kod, który działa dobrze dla mnie. Mam nadzieję, że to pomoże....

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

Ta odpowiedź sprawia, że ​​żądanie jest synchroniczne. Oznacza to, że w przypadku złego połączenia aplikacja może być bezużyteczna przez kilka minut, aż żądanie powróci.
uliwitness

2

Pochodzący z punktu widzenia aplikacji hybrydowej, to jest przykład javascript. W menu głównym mam stopkę Dostępnej aktualizacji. Jeśli aktualizacja jest dostępna (tj. Mój numer wersji w pliku konfiguracyjnym jest mniejszy niż wersja pobrana, wyświetl stopkę) Spowoduje to skierowanie użytkownika do sklepu z aplikacjami, gdzie użytkownik może kliknąć przycisk aktualizacji.

Otrzymuję również informacje o nowościach (tj. Informacje o wydaniu) i wyświetlam je w trybie modalnym podczas logowania, jeśli jest to pierwszy raz w tej wersji.

Metodę Dostępna aktualizacja można uruchamiać tak często, jak chcesz. Mój jest uruchamiany za każdym razem, gdy użytkownik przejdzie do ekranu głównego.

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: Apple ma API, więc bardzo łatwe do uzyskania

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

Ostrzeżenie: większość udzielonych odpowiedzi pobiera adres URL synchronicznie (za pomocą -dataWithContentsOfURL:lub -sendSynchronousRequest:. Jest to złe, ponieważ oznacza, że ​​aplikacja nie będzie odpowiadać przez kilka minut, jeśli połączenie mobilne zostanie przerwane w trakcie przetwarzania żądania. Nigdy rób synchronicznie dostępu do Internetu na główny wątek.

Prawidłowa odpowiedź to użycie asynchronicznego interfejsu API:

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

Domyślny limit czasu dla połączeń sieciowych wynosi kilka minut, a nawet jeśli żądanie zostanie przesłane, może być wystarczająco wolne w przypadku złego połączenia EDGE, aby zająć tak dużo czasu. W takim przypadku nie chcesz, aby Twoja aplikacja była bezużyteczna. Aby przetestować takie rzeczy, warto uruchomić kod sieciowy za pomocą Network Link Conditioner firmy Apple.


Dzięki za podtrzymanie tego pytania :-)
autorJeevan

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
}

aby porównać ciąg wersji:

https://github.com/eure/AppVersionMonitor


2

DLA SWIFT 4 i 3.2:

Najpierw musimy pobrać identyfikator pakietu ze słownika informacji o pakiecie, ustawić isUpdaet na 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
       }

Następnie musimy wywołać wywołanie urlSession, aby pobrać wersję z 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()

PEŁNY KOD BĘDZIE TAK JAK:

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

Następnie możemy wywołać tę funkcję dowolnym oprogramowaniem, którego potrzebujemy.

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

2

C # odpowiednik @datinc, tak samo jak uzyskanie wersji z Apple App Store. Dołączony kod umożliwiający uzyskanie wersji zarówno dla pakietu, jak i pliku AssemblyInfo.

EDYCJA :: Zwróć uwagę na region „/ us /” zawarty w ciągach adresów URL. Ten kod kraju będzie wymagał odpowiedniej obsługi / zmiany.

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

To pytanie padło w 2011 roku, znalazłem je w 2018 roku, szukając sposobu nie tylko na sprawdzenie nowej wersji aplikacji w App Store, ale również na powiadomienie o tym użytkownika.

Po małych poszukiwaniach doszedłem do wniosku, że odpowiedź juanjo (związana ze Swift 3) https://stackoverflow.com/a/40939740/1218405 jest optymalnym rozwiązaniem, jeśli chcesz to zrobić w kodzie samodzielnie

Mogę również zasugerować dwa świetne projekty na GitHub (ponad 2300 gwiazdek każdy)

Przykład dla syreny (AppDelegate.swift)

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

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

      return true
    }
  • Możesz także wyświetlać różne typy alertów o nowej wersji (pozwalające na pominięcie wersji lub zmuszające użytkownika do aktualizacji)
  • Możesz określić, jak często ma odbywać się sprawdzanie wersji (codziennie / co tydzień / natychmiast)
  • Możesz określić, po ilu dniach pojawi się alert sklepu z aplikacjami o nowej wersji

Linki do istniejącej odpowiedzi nie są odpowiedziami. Ponadto łącza do bibliotek również nie są odpowiedziami, chyba że wyraźnie dodasz, w jaki sposób łącze odpowiada na pytanie (dodaj przykłady kodu itp.).
JAL

1

Szybki 4

Możemy użyć nowego, JSONDecoderaby przeanalizować odpowiedź z itunes.apple.com/lookup i przedstawić ją za pomocą klas lub struktur Decodable:

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

class AppInfo: Decodable {
    var version: String
}

Możemy również dodać inne właściwości na AppInfowypadek, gdybyśmy potrzebowali tej releaseNoteslub innej właściwości.

Teraz możemy wykonać żądanie asynchroniczne za pomocą 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
}

ta funkcja otrzymuje zamknięcie zakończenia, które zostanie wywołane, gdy żądanie zostanie zakończone i zwróci wartość URLSessionDataTaskw przypadku, gdy będziemy musieli anulować żądanie, i można ją wywołać w następujący sposób:

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

Gdzie umieściłeś ten kod? Widzę, że ustawiłeś LookupResult i AppInfo na dekodowalne, ale nie widzę ich nigdzie zapisanych. Czego tu brakuje?
jessi

Deklarujesz klasy LookupResulti AppInfogdzieś w swoim projekcie, najlepiej w osobnym pliku: Są one używane podczas dekodowania odpowiedzi: JSONDecoder().decode(LookupResult.self, from: data)i zawierają ciąg wersji
juanjo

Na podstawie Twojej odpowiedzi tworzę jeden plik przy użyciu Twojego kodu Sprawdź, czy iOS-Swift-ArgAppUpdater
Anup Gupta

@jessi, sprawdź mój kod na GitHubie. Umieściłem tam Twoje rozwiązanie
Anup Gupta

0

Moja propozycja kodu. Na podstawie odpowiedzi @datinc i @ Mario-Hendricks

Powinieneś oczywiście zastąpić dlog_Errorswoim wywołaniem funkcji logowania.

Taka struktura kodu powinna zapobiegać awariom aplikacji w przypadku wystąpienia błędu. Pobieranie pliku appStoreAppVersionnie jest konieczne i nie powinno prowadzić do błędów krytycznych. A jednak przy tego rodzaju strukturze kodu nadal będziesz rejestrować swój niekrytyczny błąd.

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
Ta odpowiedź sprawia, że ​​żądanie jest synchroniczne. Oznacza to, że w przypadku złego połączenia aplikacja może być bezużyteczna przez kilka minut, aż żądanie powróci.
uliwitness

-1

Zaktualizowano dla Swift 3:

jeśli chcesz sprawdzić aktualną wersję swojej aplikacji, użyj poniższego prostego kodu:

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

  let version = object as! String
  print("version: \(version)")
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.