Я хотел бы вручную проверить, есть ли новые обновления для моего приложения, когда пользователь в нем, и попросить его загрузить новую версию. Могу ли я это сделать, проверив версию моего приложения в магазине приложений - программно?
Проверьте, есть ли у моего приложения новая версия в AppStore
Ответ 1
Вот простой фрагмент кода, который позволяет узнать, отличается ли текущая версия
-(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 это соответствует версии в приложении, которое вы выпускаете. Если нет, то указанный выше код всегда будет возвращать YES независимо от того, обновляется ли пользователь.
Ответ 2
Версия 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
}
Я думаю, что лучше выбросить ошибку вместо того, чтобы возвращать 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: "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
}
Пример:
_ = try? isUpdateAvailable { (update, error) in
if let error = error {
print(error)
} else if let update = update {
print(update)
}
}
Ответ 3
Поскольку у меня была та же проблема, я нашел ответ, предоставленный Марио Хендрикс. Unfornatelly, когда я попытался оценить его код в своем проекте, XCode действительно жаловался на проблемы с кастингом, говоря: "MDLMaterialProperty не имеет членов подстроки". Его код пытался установить этот MDLMaterial... как тип константы "lookupResult", заставляя кастинг "Int" терпеть неудачу каждый раз. Мое решение состояло в том, чтобы предоставить аннотацию типа для моей переменной NSDictionary, чтобы яснить о том, какое значение мне нужно. При этом я мог бы получить доступ к значению "версия", которое мне было необходимо.
Обс: Для этого YOURBUNDLEID вы можете получить от своего проекта Xcode.... "Цели > Общие > Идентификация > Идентификатор пакетa >
Итак, вот мой код с некоторыми упрощениями:
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 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
}
Все предложения по улучшению этого кода приветствуются!
Ответ 4
Спасибо Стиву Мозеру за его ссылку, вот мой код:
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"];
Ответ 5
Просто используйте ATAppUpdater. Это 1 линия, потокобезопасная и быстрая. Он также имеет методы делегирования, если вы хотите отслеживать действия пользователя.
Вот пример:
- (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;
Ответ 6
Могу ли я предложить эту небольшую библиотеку: https://github.com/nicklockwood/iVersion
Его цель - упростить обработку удаленных plists для запуска уведомлений.
Ответ 7
Вот моя версия, использующая 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)")
}
Ответ 8
Обновлен код swift 4 от Anup Gupta
Я сделал некоторые изменения в этом коде. Теперь функции вызываются из фоновой очереди, поскольку соединение может быть медленным и, следовательно, блокировать основной поток.
Я также сделал 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)
Ответ 9
Этот ответ является модификацией ответа datinc fooobar.com/questions/114145/....
datinc funtion сравнивает версию с помощью сравнения строк. Таким образом, он не будет сравнивать версию больше или меньше.
Но эта модифицированная функция сравнивает версию с помощью NSNumericSearch (числовое сравнение).
- (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];
}
Применение:
[self checkForUpdateWithHandler:^(BOOL isUpdateAvailable) {
if (isUpdateAvailable) {
// show alert
}
}];
Ответ 10
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
}
Ответ 11
Вот быстрый метод, который делает то, что предлагают некоторые из Objective-C. Очевидно, что как только вы получите информацию из магазина приложений JSON, вы можете извлечь заметки о выпуске, если хотите.
func appUpdateAvailable(storeInfoURL: String) -> Bool
{
var upgradeAvailable = false
// Get the main bundle of the app so that we can determine the app 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
}
Ответ 12
Если вы не задаете тип содержимого в NSUrlRequest, то, конечно, вы не получите ответ, поэтому попробуйте приведенный ниже код, он отлично работает для меня. Надеюсь, это поможет...
-(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;
}
Ответ 13
Прибывая из гибридного приложения POV, это пример javascript, у меня есть нижний колонтитул Update Available в моем основном меню. Если обновление доступно (т.е. Мой номер версии в файле конфигурации меньше, чем найденная версия, отобразите нижний колонтитул). Затем он направит пользователя в хранилище приложений, где пользователь может нажать кнопку обновления.
Я также получаю новые данные (например, заметки о выпуске) и отображаю их в модальном имени, если он первый раз в этой версии.
Доступный метод Update можно запускать так часто, как вам нравится. Mine запускается каждый раз, когда пользователь переходит на главный экран.
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);
});
}
Обратный вызов: у Apple есть API, поэтому очень легко получить
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(' ');
// Release Notes
settings.updateReleaseNotes('v' + storeVersion, releaseNotes);
}
}
Ответ 14
Предупреждение:. Большинство ответов, которые вы получили, получают синхронный URL (используя -dataWithContentsOfURL:
или -sendSynchronousRequest:
. Это плохо, так как это означает, что ваше приложение будет не отвечать на несколько минут, если мобильный соединение падает во время выполнения запроса. Никогда не подключайте интернет-доступ к основному потоку.
Правильный ответ заключается в использовании асинхронного 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];
Тайм-аут по умолчанию для сетевых подключений составляет несколько минут. И даже если запрос проходит, он может быть достаточно медленным по сравнению с плохой связью EDGE, чтобы пройти так долго. В этом случае вы не хотите, чтобы ваше приложение было непригодным для использования. Чтобы протестировать такие вещи, полезно запустить сетевой код с помощью Apple Network Link Conditioner.
Ответ 15
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
}
для сравнения строки версии:
Ответ 16
Этот вопрос был задан в 2011 году, я нашел его в 2018 году, ища какой-то способ не только проверить новую версию приложения в App Store, но и уведомить об этом пользователя.
После небольшого исследования я пришел к выводу, что ответ juanjo (связанный с Swift 3) fooobar.com/questions/114145/... - это оптимальное решение, если вы хотите сделать это в коде самостоятельно
Также я могу предложить два больших проекта на GitHub (2300+)
- https://github.com/ArtSabintsev/Harpy для Objective-C
- https://github.com/ArtSabintsev/Siren для Swift
Пример для Siren (AppDelegate.swift)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let siren = Siren.shared
siren.checkVersion(checkType: .immediately)
return true
}
- Вы также можете показывать различные типы предупреждений о новой версии (позволяя пропускать версию или принуждать пользователя к обновлению)
- Вы можете указать, как часто выполняется проверка версии (ежедневно/еженедельно/сразу)
- Вы можете указать, сколько дней после появления новой версии для уведомления о магазине приложений должно появиться
Ответ 17
Swift 4
Мы можем использовать новый JSONDecoder
для анализа ответа от itunes.apple.com/lookup и представления его с помощью классов или структур Decodable:
class LookupResult: Decodable {
var results: [AppInfo]
}
class AppInfo: Decodable {
var version: String
}
Мы можем также добавить другие свойства AppInfo
в случае, если нам нужны releaseNotes
или какой - либо другой собственности.
Теперь мы можем сделать запрос async, используя 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
}
эта функция получает закрытие завершения, которое будет вызываться, когда запрос будет завершен, и возвращает URLSessionDataTask
в случае, если нам нужно отменить запрос, и его можно вызвать следующим образом:
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")
}
}
}
Ответ 18
Я видел много способов проверить обновление приложения. поэтому, основываясь на многих ответах, я их смешиваю и создаю свое решение, которое доступно на GitHub. Если требуется какое-либо обновление. Пожалуйста, дайте мне знать. Этот код для Swift 4
Ссылка GitHub К этому коду. https://github.com/anupgupta-arg/iOS-Swift-ArgAppUpdater
import UIKit
enum VersionError: Error {
case invalidBundleInfo, invalidResponse
}
class LookupResult: Decodable {
var results: [AppInfo]
}
class AppInfo: Decodable {
var version: String
var trackViewUrl: String
//let identifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
// You can add many thing based on "http://itunes.apple.com/lookup?bundleId=\(identifier)" response
// here version and trackViewUrl are key of URL response
// so you can add all key beased on your requirement.
}
class ArgAppUpdater: NSObject {
private static var _instance: ArgAppUpdater?;
private override init() {
}
public static func getSingleton() -> ArgAppUpdater {
if (ArgAppUpdater._instance == nil) {
ArgAppUpdater._instance = ArgAppUpdater.init();
}
return ArgAppUpdater._instance!;
}
private func getAppInfo(completion: @escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
guard let identifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
DispatchQueue.main.async {
completion(nil, VersionError.invalidBundleInfo)
}
return nil
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let error = error { throw error }
guard let data = data else { throw VersionError.invalidResponse }
print("Data:::",data)
print("response###",response!)
let result = try JSONDecoder().decode(LookupResult.self, from: data)
let dictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
print("dictionary",dictionary!)
guard let info = result.results.first else { throw VersionError.invalidResponse }
print("result:::",result)
completion(info, nil)
} catch {
completion(nil, error)
}
}
task.resume()
print("task ******", task)
return task
}
private func checkVersion(force: Bool) {
let info = Bundle.main.infoDictionary
let currentVersion = info?["CFBundleShortVersionString"] as? String
_ = getAppInfo { (info, error) in
let appStoreAppVersion = info?.version
if let error = error {
print(error)
}else if appStoreAppVersion!.compare(currentVersion!, options: .numeric) == .orderedDescending {
// print("needs update")
// print("hiiii")
DispatchQueue.main.async {
let topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
}
}
}
}
func showUpdateWithConfirmation() {
checkVersion(force : false)
}
func showUpdateWithForce() {
checkVersion(force : true)
}
}
extension UIViewController {
fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
print("AppURL:::::",AppURL)
let bundleName = Bundle.main.infoDictionary!["CFBundleDisplayName"] as! String;
let alertMessage = "\(bundleName) Version \(Version) is available on AppStore."
let alertTitle = "New Version"
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
if !Force {
let notNowButton = UIAlertAction(title: "Not Now", style: .default) { (action:UIAlertAction) in
print("Don't Call API");
}
alertController.addAction(notNowButton)
}
let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
print("Call API");
print("No update")
guard let url = URL(string: AppURL) else {
return
}
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
alertController.addAction(updateButton)
self.present(alertController, animated: true, completion: nil)
}
}
Refrence: fooobar.com/questions/114145/... И https://github.com/emotality/ATAppUpdater
Счастливое кодирование 👍 😊
Ответ 19
Для SWIFT 4 и 3.2:
Во-первых, нам нужно получить идентификатор пакета из словаря информации о пакете, установить isUpdaet как 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
}
Затем нам нужно вызвать вызов urlSession для получения версии из 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()
ПОЛНЫЙ КОДЕКС БУДЕТ НРАВИТСЯ ЭТО:
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()
}
Затем мы можем вызвать нужную нам функцию.
checkForUpdate { (isUpdate) in
print("Update needed:\(isUpdate)")
if isUpdate{
DispatchQueue.main.async {
print("new update Available")
}
}
}
Ответ 20
С# эквивалентность @datinc, так же, как и получение версии Apple App Store. Включен код для получения версии как для пакета, так и для файла AssemblyInfo.
РЕДАКТИРОВАТЬ :: Обратите внимание на регион, "/us/", включенный в строку urlString. Этот код страны необходимо будет обработать/изменить соответствующим образом.
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();
}
Ответ 21
Мое предложение кода. Основываясь на ответах @datinc и @Mario-Hendricks
Разумеется, замените dlog_Error
своим функциональным вызовом ведения журнала.
Такая структура кода должна помешать вашему приложению сбой в случае ошибки.
Для извлечения appStoreAppVersion
не является обязательным и не должно приводить к фатальным ошибкам.
И все же, с такой структурой кода, вы все равно получите свою нефатальную ошибку.
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())
}
}
Ответ 22
Обновлено для быстрого 3:
если вы хотите проверить текущую версию своего приложения, используемую ниже простого кода:
let object = Bundle.main.infoDictionary?["CFBundleShortVersionString"]
let version = object as! String
print("version: \(version)")
Ответ 23
Упрощенный отличный ответ размещен в этой теме. Использование 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 appStoreVersion = entry["version"] as? String, let installedVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
if let installed = Int(installedVersion.replacingOccurrences(of: ".", with: "")), let appStore = Int(appStoreVersion.replacingOccurrences(of: ".", with: "")), appStore > installed {
callback(true)
}
}
}
}
}
А потом использовать это:
VersionCheck.shared.isUpdateAvailable() { (hasUpdates) in
print("is update available: \(hasUpdates)")
}
Ответ 24
новая версия на AppStore
swift 3.0
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 version number
let bundle = Bundle.main
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(contentsOf: urlOnAppStore! as URL) {
// Try to deserialize the JSON that we got
if let dict: NSDictionary = try! JSONSerialization.jsonObject(with: dataInJSON as Data, options: JSONSerialization.ReadingOptions.allowFragments) as! [String: AnyObject] as NSDictionary? {
if let results:NSArray = dict["results"] as? NSArray {
if let version = ((results[0] as! NSDictionary).value(forKey: "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
}