Check if my app has a new version on AppStore Check if my app has a new version on AppStore ios ios

Check if my app has a new version on AppStore


Here is a simple code snippet that lets you know if the current version is different

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

Note: Make sure that when you enter the new version in iTunes, this matches the version in the app you are releasing. If not then the above code will always return YES regardless if the user updates.


Swift 3 version:

func isUpdateAvailable() throws -> Bool {    guard let info = Bundle.main.infoDictionary,        let currentVersion = info["CFBundleShortVersionString"] as? String,        let identifier = info["CFBundleIdentifier"] as? String,        let url = URL(string: "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}

I think is better to throw an error instead of returning false, in this case I created a VersionError but it can be some other you define or NSError

enum VersionError: Error {    case invalidResponse, invalidBundleInfo}

Also consider to call this function from another thread, if the connection is slow it can block the current thread.

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

Update

Using URLSession:

Instead of using Data(contentsOf: url) and block a thread, we can use 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}

example:

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


Updated the swift 4 code from Anup Gupta

I have made some alterations to this code. Now the functions are called from a background queue, since the connection can be slow and therefore block the main thread.

I also made the CFBundleName optional, since the version presented had "CFBundleDisplayName" which didn't work probably in my version. So now if it's not present it won't crash but just won't display the App Name in the alert.

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

I make this call for also adding the confirmation button:

AppUpdater.shared.showUpdate(withConfirmation: true)

Or call it to be called like this to have the force update option on:

AppUpdater.shared.showUpdate(withConfirmation: false)