我想在用户使用我的应用时手动检查是否有新的更新,并提示他下载新版本。我能通过编程方式检查应用商店中我的应用的版本来实现吗?
这是一个简单的代码片段,可以让您了解当前版本是否不同。
-(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;
}
注意:在iTunes中输入新版本时,请确保与您发布的应用程序中的版本匹配。否则,上述代码将始终返回“是”,无论用户是否更新。
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: "https://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
}
我认为在这种情况下,抛出错误而不是返回false更好,因此我创建了一个VersionError,但也可以是您定义的其他错误或NSError。
enum VersionError: Error {
case invalidResponse, invalidBundleInfo
}
如果连接速度较慢,考虑从另一个线程调用此函数,这样可以防止当前线程被阻塞。
DispatchQueue.global().async {
do {
let update = try self.isUpdateAvailable()
DispatchQueue.main.async {
// show alert
}
} catch {
print(error)
}
}
更新
使用URLSession:
不要使用Data(contentsOf: url)
阻塞线程,而是可以使用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: "https://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
}
例子:
_ = try? isUpdateAvailable { (update, error) in
if let error = error {
print(error)
} else if let update = update {
print(update)
}
}
DispatchQueue.global()
会给你一个后台队列,在那个队列里面加载数据,只有在数据加载完成后才返回到主队列。 - juanjo(Result<Bool, Error>) → Void
而不是(Bool?, Error?) -> Void
,因为后者很难处理。这样我们甚至不需要使用“throws”。 - SwiftiSwift这个帖子上有一篇精简的优秀答案。使用 Swift 4
和 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: ".").compactMap { Int($0) }
let arrayLocal = versionLocal.split(separator: ".").compactMap { Int($0) }
if arrayLocal.count != arrayStore.count {
callback(true) // different versioning system
return
}
// check each segment of the version
for (localSegment, storeSegment) in zip(arrayLocal, arrayStore) {
if localSegment < storeSegment {
callback(true)
return
}
}
}
callback(false) // no new version or failed to fetch app store version
}
}
}
然后使用它:
VersionCheck.shared.isUpdateAvailable() { hasUpdates in
print("is update available: \(hasUpdates)")
}
{ "resultCount":0, "results": [] }
- technerdreturn
。 - Libor Zapletalhttps://itunes.apple.com/lookup?bundleId=\(bundleId)
以及本地版本:Bundle.main.infoDictionary?["CFBundleShortVersionString"]
吗?不确定为什么其中任何一个会只返回前两个部分,如果存在3个部分的话 :/ - budiDino从Anup Gupta处更新了swift 4代码。
我对这段代码进行了一些修改。现在,函数是从后台队列中调用的,因为连接可能很慢,从而阻塞主线程。
我还使CFBundleName可选,因为所呈现的版本具有“CFBundleDisplayName”,但在我的版本中无法正常工作。因此,现在如果不存在它,它不会崩溃,只是不会在警报中显示应用程序名称。
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 ""
}
}
}
我建议同时添加一个确认按钮:
AppUpdater.shared.showUpdate(withConfirmation: true)
或者将其称为这样,以便在其中具有强制更新选项:
AppUpdater.shared.showUpdate(withConfirmation: false)
由于我也遇到了同样的问题,我找到了Mario Hendricks提供的答案。不幸的是,当我尝试应用他的代码到我的项目时,XCode会抱怨Casting问题,说"MDLMaterialProperty没有下标成员"。他的代码试图将这个MDLMaterial...设置为常量"lookupResult"的类型,导致每次都失败。我的解决方案是为我的变量提供一个类型注释NSDictionary,以清楚表明我需要的值的类型。有了这个,我可以访问我需要的"value"。
注意:对于此YOURBUNDLEID,您可以从Xcode项目中获取... "Targets > General > Identity > Bundle Identifier"
所以这里是我的代码,并进行了一些简化:
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
}
欢迎提出对这段代码的任何改进建议!
guard
代替 if
。) - adamjansch这是我的代码:
NSString *appInfoUrl = @"http://itunes.apple.com/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"];
/en/
子路径并没有起作用。将其删除后,它就可用了。 - gasparuff只需使用ATAppUpdater。它只有一行代码,线程安全且快速。如果您想要跟踪用户操作,它还具有代理方法。
以下是一个示例:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[ATAppUpdater sharedUpdater] showUpdateWithConfirmation]; // 1 line of code
// or
[[ATAppUpdater sharedUpdater] showUpdateWithForce]; // 1 line of code
return YES;
}
可选的委托方法:
- (void)appUpdaterDidShowUpdateDialog;
- (void)appUpdaterUserDidLaunchAppStore;
- (void)appUpdaterUserDidCancel;
这是我的版本,使用 Swift 4 和流行的 Alamofire 库(我在我的应用程序中也使用它)。请求是异步的,您可以传递回调函数来在完成时得到通知。
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)
}
}
}
使用方法很简单:
VersionCheck.shared.checkAppStore() { isNew, version in
print("IS NEW VERSION AVAILABLE: \(isNew), APP STORE VERSION: \(version)")
}
enum VersionError: Error {
case invalidResponse, invalidBundleInfo
}
@discardableResult
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
}
let request = URLRequest(url: url, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData)
let task = URLSession.shared.dataTask(with: request) { (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 lastVersion = result["version"] as? String else {
throw VersionError.invalidResponse
}
completion(lastVersion > currentVersion, nil)
} catch {
completion(nil, error)
}
}
task.resume()
return task
}
try? isUpdateAvailable {[self] (update, error) in
if let error = error {
print(error)
} else if update ?? false {
// show alert
}
}
@discardableResult
,而不是使用_ =
。 - Laszlo我可以建议使用这个小库: https://github.com/nicklockwood/iVersion
它的目的是简化处理远程 plist 文件以触发通知。
NSNumber
版本号的plist文件,然后将其上传到我的网站上。这个网站是我用来支持我的应用程序和应用程序网页的相同网站。在viewDidLoad
中,我检查网站上的版本号并检查我的应用程序中的当前版本。然后我有一个预先制作的alertView
,它会自动提示更新应用程序。如果您需要,我可以提供代码。 - AndrewOperatingSystemVersion
使用小数比较应用程序版本更新,如2.5.2 - Leo Dabus