You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1431 lines
54 KiB
1431 lines
54 KiB
9 years ago
|
//
|
||
|
// iVersion.m
|
||
|
//
|
||
|
// Version 1.11.4
|
||
|
//
|
||
|
// Created by Nick Lockwood on 26/01/2011.
|
||
|
// Copyright 2011 Charcoal Design
|
||
|
//
|
||
|
// Distributed under the permissive zlib license
|
||
|
// Get the latest version from here:
|
||
|
//
|
||
|
// https://github.com/nicklockwood/iVersion
|
||
|
//
|
||
|
// This software is provided 'as-is', without any express or implied
|
||
|
// warranty. In no event will the authors be held liable for any damages
|
||
|
// arising from the use of this software.
|
||
|
//
|
||
|
// Permission is granted to anyone to use this software for any purpose,
|
||
|
// including commercial applications, and to alter it and redistribute it
|
||
|
// freely, subject to the following restrictions:
|
||
|
//
|
||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||
|
// claim that you wrote the original software. If you use this software
|
||
|
// in a product, an acknowledgment in the product documentation would be
|
||
|
// appreciated but is not required.
|
||
|
//
|
||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||
|
// misrepresented as being the original software.
|
||
|
//
|
||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||
|
//
|
||
|
|
||
|
#import "iVersion.h"
|
||
|
|
||
|
|
||
|
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
|
||
|
#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
|
||
|
#pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis"
|
||
|
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||
|
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
|
||
|
#pragma clang diagnostic ignored "-Wunused-macros"
|
||
|
#pragma clang diagnostic ignored "-Wconversion"
|
||
|
#pragma clang diagnostic ignored "-Wselector"
|
||
|
#pragma clang diagnostic ignored "-Wshadow"
|
||
|
#pragma clang diagnostic ignored "-Wgnu"
|
||
|
|
||
|
|
||
|
#import <Availability.h>
|
||
|
#if !__has_feature(objc_arc)
|
||
|
#error This class requires automatic reference counting
|
||
|
#endif
|
||
|
|
||
|
|
||
|
NSString *const iVersionErrorDomain = @"iVersionErrorDomain";
|
||
|
|
||
|
NSString *const iVersionInThisVersionTitleKey = @"iVersionInThisVersionTitle";
|
||
|
NSString *const iVersionUpdateAvailableTitleKey = @"iVersionUpdateAvailableTitle";
|
||
|
NSString *const iVersionVersionLabelFormatKey = @"iVersionVersionLabelFormat";
|
||
|
NSString *const iVersionOKButtonKey = @"iVersionOKButton";
|
||
|
NSString *const iVersionIgnoreButtonKey = @"iVersionIgnoreButton";
|
||
|
NSString *const iVersionRemindButtonKey = @"iVersionRemindButton";
|
||
|
NSString *const iVersionDownloadButtonKey = @"iVersionDownloadButton";
|
||
|
|
||
|
static NSString *const iVersionAppStoreIDKey = @"iVersionAppStoreID";
|
||
|
static NSString *const iVersionLastVersionKey = @"iVersionLastVersionChecked";
|
||
|
static NSString *const iVersionIgnoreVersionKey = @"iVersionIgnoreVersion";
|
||
|
static NSString *const iVersionLastCheckedKey = @"iVersionLastChecked";
|
||
|
static NSString *const iVersionLastRemindedKey = @"iVersionLastReminded";
|
||
|
|
||
|
static NSString *const iVersionMacAppStoreBundleID = @"com.apple.appstore";
|
||
|
static NSString *const iVersionAppLookupURLFormat = @"http://itunes.apple.com/%@/lookup";
|
||
|
|
||
|
static NSString *const iVersioniOSAppStoreURLFormat = @"itms-apps://itunes.apple.com/app/id%@";
|
||
|
static NSString *const iVersionMacAppStoreURLFormat = @"macappstore://itunes.apple.com/app/id%@";
|
||
|
|
||
|
|
||
|
#define SECONDS_IN_A_DAY 86400.0
|
||
|
#define MAC_APP_STORE_REFRESH_DELAY 5.0
|
||
|
#define REQUEST_TIMEOUT 60.0
|
||
|
|
||
|
|
||
|
@implementation NSString(iVersion)
|
||
|
|
||
|
- (NSComparisonResult)compareVersion:(NSString *)version
|
||
|
{
|
||
|
return [self compare:version options:NSNumericSearch];
|
||
|
}
|
||
|
|
||
|
- (NSComparisonResult)compareVersionDescending:(NSString *)version
|
||
|
{
|
||
|
return (NSComparisonResult)(0 - [self compareVersion:version]);
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
|
||
|
@interface iVersion ()
|
||
|
|
||
|
@property (nonatomic, copy) NSDictionary *remoteVersionsDict;
|
||
|
@property (nonatomic, strong) NSError *downloadError;
|
||
|
@property (nonatomic, copy) NSString *versionDetails;
|
||
|
@property (nonatomic, strong) id visibleLocalAlert;
|
||
|
@property (nonatomic, strong) id visibleRemoteAlert;
|
||
|
@property (nonatomic, assign) BOOL checkingForNewVersion;
|
||
|
|
||
|
@end
|
||
|
|
||
|
|
||
|
@implementation iVersion
|
||
|
|
||
|
+ (void)load
|
||
|
{
|
||
|
[self performSelectorOnMainThread:@selector(sharedInstance) withObject:nil waitUntilDone:NO];
|
||
|
}
|
||
|
|
||
|
+ (iVersion *)sharedInstance
|
||
|
{
|
||
|
static iVersion *sharedInstance = nil;
|
||
|
if (sharedInstance == nil)
|
||
|
{
|
||
|
sharedInstance = [[iVersion alloc] init];
|
||
|
}
|
||
|
return sharedInstance;
|
||
|
}
|
||
|
|
||
|
- (NSString *)localizedStringForKey:(NSString *)key withDefault:(NSString *)defaultString
|
||
|
{
|
||
|
static NSBundle *bundle = nil;
|
||
|
if (bundle == nil)
|
||
|
{
|
||
|
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"iVersion" ofType:@"bundle"];
|
||
|
if (self.useAllAvailableLanguages)
|
||
|
{
|
||
|
bundle = [NSBundle bundleWithPath:bundlePath];
|
||
|
NSString *language = [[NSLocale preferredLanguages] count]? [NSLocale preferredLanguages][0]: @"en";
|
||
|
if (![[bundle localizations] containsObject:language])
|
||
|
{
|
||
|
language = [language componentsSeparatedByString:@"-"][0];
|
||
|
}
|
||
|
if ([[bundle localizations] containsObject:language])
|
||
|
{
|
||
|
bundlePath = [bundle pathForResource:language ofType:@"lproj"];
|
||
|
}
|
||
|
}
|
||
|
bundle = [NSBundle bundleWithPath:bundlePath] ?: [NSBundle mainBundle];
|
||
|
}
|
||
|
defaultString = [bundle localizedStringForKey:key value:defaultString table:nil];
|
||
|
return [[NSBundle mainBundle] localizedStringForKey:key value:defaultString table:nil];
|
||
|
}
|
||
|
|
||
|
- (iVersion *)init
|
||
|
{
|
||
|
if ((self = [super init]))
|
||
|
{
|
||
|
|
||
|
#if TARGET_OS_IPHONE
|
||
|
|
||
|
//register for iphone application events
|
||
|
if (&UIApplicationWillEnterForegroundNotification)
|
||
|
{
|
||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||
|
selector:@selector(applicationWillEnterForeground)
|
||
|
name:UIApplicationWillEnterForegroundNotification
|
||
|
object:nil];
|
||
|
}
|
||
|
|
||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||
|
selector:@selector(didRotate)
|
||
|
name:UIDeviceOrientationDidChangeNotification
|
||
|
object:nil];
|
||
|
|
||
|
#endif
|
||
|
|
||
|
//get country
|
||
|
self.appStoreCountry = [(NSLocale *)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
|
||
|
if ([self.appStoreCountry isEqualToString:@"150"])
|
||
|
{
|
||
|
self.appStoreCountry = @"eu";
|
||
|
}
|
||
|
else if ([[self.appStoreCountry stringByReplacingOccurrencesOfString:@"[A-Za-z]{2}" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, 2)] length])
|
||
|
{
|
||
|
self.appStoreCountry = @"us";
|
||
|
}
|
||
|
|
||
|
//application version (use short version preferentially)
|
||
|
self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
||
|
if ([self.applicationVersion length] == 0)
|
||
|
{
|
||
|
self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
|
||
|
}
|
||
|
|
||
|
//bundle id
|
||
|
self.applicationBundleID = [[NSBundle mainBundle] bundleIdentifier];
|
||
|
|
||
|
//default settings
|
||
|
self.updatePriority = iVersionUpdatePriorityDefault;
|
||
|
self.useAllAvailableLanguages = YES;
|
||
|
self.onlyPromptIfMainWindowIsAvailable = YES;
|
||
|
self.checkAtLaunch = YES;
|
||
|
self.checkPeriod = 0.0f;
|
||
|
self.remindPeriod = 1.0f;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
|
||
|
//enable verbose logging in debug mode
|
||
|
self.verboseLogging = YES;
|
||
|
|
||
|
#endif
|
||
|
|
||
|
//app launched
|
||
|
[self performSelectorOnMainThread:@selector(applicationLaunched) withObject:nil waitUntilDone:NO];
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
- (id<iVersionDelegate>)delegate
|
||
|
{
|
||
|
if (_delegate == nil)
|
||
|
{
|
||
|
|
||
|
#if TARGET_OS_IPHONE
|
||
|
#define APP_CLASS UIApplication
|
||
|
#else
|
||
|
#define APP_CLASS NSApplication
|
||
|
#endif
|
||
|
|
||
|
_delegate = (id<iVersionDelegate>)[[APP_CLASS sharedApplication] delegate];
|
||
|
}
|
||
|
return _delegate;
|
||
|
}
|
||
|
|
||
|
- (NSString *)inThisVersionTitle
|
||
|
{
|
||
|
return _inThisVersionTitle ?: [self localizedStringForKey:iVersionInThisVersionTitleKey withDefault:@"New in this version"];
|
||
|
}
|
||
|
|
||
|
- (NSString *)updateAvailableTitle
|
||
|
{
|
||
|
return _updateAvailableTitle ?: [self localizedStringForKey:iVersionUpdateAvailableTitleKey withDefault:@"New version available"];
|
||
|
}
|
||
|
|
||
|
- (NSString *)versionLabelFormat
|
||
|
{
|
||
|
return _versionLabelFormat ?: [self localizedStringForKey:iVersionVersionLabelFormatKey withDefault:@"Version %@"];
|
||
|
}
|
||
|
|
||
|
- (NSString *)okButtonLabel
|
||
|
{
|
||
|
return _okButtonLabel ?: [self localizedStringForKey:iVersionOKButtonKey withDefault:@"OK"];
|
||
|
}
|
||
|
|
||
|
- (NSString *)ignoreButtonLabel
|
||
|
{
|
||
|
return _ignoreButtonLabel ?: [self localizedStringForKey:iVersionIgnoreButtonKey withDefault:@"Ignore"];
|
||
|
}
|
||
|
|
||
|
- (NSString *)downloadButtonLabel
|
||
|
{
|
||
|
return _downloadButtonLabel ?: [self localizedStringForKey:iVersionDownloadButtonKey withDefault:@"Download"];
|
||
|
}
|
||
|
|
||
|
- (NSString *)remindButtonLabel
|
||
|
{
|
||
|
return _remindButtonLabel ?: [self localizedStringForKey:iVersionRemindButtonKey withDefault:@"Remind Me Later"];
|
||
|
}
|
||
|
|
||
|
- (NSURL *)updateURL
|
||
|
{
|
||
|
if (_updateURL)
|
||
|
{
|
||
|
return _updateURL;
|
||
|
}
|
||
|
|
||
|
if (!self.appStoreID)
|
||
|
{
|
||
|
NSLog(@"iVersion error: No App Store ID was found for this application. If the application is not intended for App Store release then you must specify a custom updateURL.");
|
||
|
}
|
||
|
|
||
|
#if TARGET_OS_IPHONE
|
||
|
|
||
|
return [NSURL URLWithString:[NSString stringWithFormat:iVersioniOSAppStoreURLFormat, @(self.appStoreID)]];
|
||
|
|
||
|
#else
|
||
|
|
||
|
return [NSURL URLWithString:[NSString stringWithFormat:iVersionMacAppStoreURLFormat, @(self.appStoreID)]];
|
||
|
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
- (NSUInteger)appStoreID
|
||
|
{
|
||
|
return [[[NSUserDefaults standardUserDefaults] objectForKey:iVersionAppStoreIDKey] unsignedIntegerValue];
|
||
|
}
|
||
|
|
||
|
- (void)setAppStoreID:(NSUInteger)appStoreID
|
||
|
{
|
||
|
[[NSUserDefaults standardUserDefaults] setInteger:(NSInteger)appStoreID forKey:iVersionAppStoreIDKey];
|
||
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
||
|
}
|
||
|
|
||
|
- (NSDate *)lastChecked
|
||
|
{
|
||
|
return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastCheckedKey];
|
||
|
}
|
||
|
|
||
|
- (void)setLastChecked:(NSDate *)date
|
||
|
{
|
||
|
[[NSUserDefaults standardUserDefaults] setObject:date forKey:iVersionLastCheckedKey];
|
||
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
||
|
}
|
||
|
|
||
|
- (NSDate *)lastReminded
|
||
|
{
|
||
|
return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastRemindedKey];
|
||
|
}
|
||
|
|
||
|
- (void)setLastReminded:(NSDate *)date
|
||
|
{
|
||
|
[[NSUserDefaults standardUserDefaults] setObject:date forKey:iVersionLastRemindedKey];
|
||
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
||
|
}
|
||
|
|
||
|
- (NSString *)ignoredVersion
|
||
|
{
|
||
|
return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionIgnoreVersionKey];
|
||
|
}
|
||
|
|
||
|
- (void)setIgnoredVersion:(NSString *)version
|
||
|
{
|
||
|
[[NSUserDefaults standardUserDefaults] setObject:version forKey:iVersionIgnoreVersionKey];
|
||
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
||
|
}
|
||
|
|
||
|
- (BOOL)viewedVersionDetails
|
||
|
{
|
||
|
return [[[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastVersionKey] isEqualToString:self.applicationVersion];
|
||
|
}
|
||
|
|
||
|
- (void)setViewedVersionDetails:(BOOL)viewed
|
||
|
{
|
||
|
[[NSUserDefaults standardUserDefaults] setObject:(viewed? self.applicationVersion: nil) forKey:iVersionLastVersionKey];
|
||
|
}
|
||
|
|
||
|
- (void)dealloc
|
||
|
{
|
||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||
|
}
|
||
|
|
||
|
- (NSString *)lastVersion
|
||
|
{
|
||
|
return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastVersionKey];
|
||
|
}
|
||
|
|
||
|
- (void)setLastVersion:(NSString *)version
|
||
|
{
|
||
|
[[NSUserDefaults standardUserDefaults] setObject:version forKey:iVersionLastVersionKey];
|
||
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
||
|
}
|
||
|
|
||
|
- (NSDictionary *)localVersionsDict
|
||
|
{
|
||
|
static NSDictionary *versionsDict = nil;
|
||
|
if (versionsDict == nil)
|
||
|
{
|
||
|
if (self.localVersionsPlistPath == nil)
|
||
|
{
|
||
|
versionsDict = [[NSDictionary alloc] init]; //empty dictionary
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NSString *versionsFile = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:self.localVersionsPlistPath];
|
||
|
versionsDict = [[NSDictionary alloc] initWithContentsOfFile:versionsFile];
|
||
|
if (!versionsDict)
|
||
|
{
|
||
|
// Get the path to versions plist in localized directory
|
||
|
NSArray *pathComponents = [self.localVersionsPlistPath componentsSeparatedByString:@"."];
|
||
|
versionsFile = ([pathComponents count] == 2) ? [[NSBundle mainBundle] pathForResource:pathComponents[0] ofType:pathComponents[1]] : nil;
|
||
|
versionsDict = [[NSDictionary alloc] initWithContentsOfFile:versionsFile];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return versionsDict;
|
||
|
}
|
||
|
|
||
|
- (NSString *)mostRecentVersionInDict:(NSDictionary *)dict
|
||
|
{
|
||
|
return [[[dict allKeys] sortedArrayUsingSelector:@selector(compareVersion:)] lastObject];
|
||
|
}
|
||
|
|
||
|
- (NSString *)versionDetails:(NSString *)version inDict:(NSDictionary *)dict
|
||
|
{
|
||
|
id versionData = dict[version];
|
||
|
if ([versionData isKindOfClass:[NSString class]])
|
||
|
{
|
||
|
return versionData;
|
||
|
}
|
||
|
else if ([versionData isKindOfClass:[NSArray class]])
|
||
|
{
|
||
|
return [versionData componentsJoinedByString:@"\n"];
|
||
|
}
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
- (NSString *)versionDetailsSince:(NSString *)lastVersion inDict:(NSDictionary *)dict
|
||
|
{
|
||
|
if (self.previewMode)
|
||
|
{
|
||
|
lastVersion = @"0";
|
||
|
}
|
||
|
BOOL newVersionFound = NO;
|
||
|
NSMutableString *details = [NSMutableString string];
|
||
|
NSArray *versions = [[dict allKeys] sortedArrayUsingSelector:@selector(compareVersionDescending:)];
|
||
|
for (NSString *version in versions)
|
||
|
{
|
||
|
if ([version compareVersion:lastVersion] == NSOrderedDescending)
|
||
|
{
|
||
|
newVersionFound = YES;
|
||
|
if (self.groupNotesByVersion)
|
||
|
{
|
||
|
[details appendString:[self.versionLabelFormat stringByReplacingOccurrencesOfString:@"%@" withString:version]];
|
||
|
[details appendString:@"\n\n"];
|
||
|
}
|
||
|
[details appendString:[self versionDetails:version inDict:dict] ?: @""];
|
||
|
[details appendString:@"\n"];
|
||
|
if (self.groupNotesByVersion)
|
||
|
{
|
||
|
[details appendString:@"\n"];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return newVersionFound? [details stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]: nil;
|
||
|
}
|
||
|
|
||
|
- (NSString *)versionDetails
|
||
|
{
|
||
|
if (!_versionDetails)
|
||
|
{
|
||
|
if (self.viewedVersionDetails)
|
||
|
{
|
||
|
self.versionDetails = [self versionDetails:self.applicationVersion inDict:[self localVersionsDict]];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self.versionDetails = [self versionDetailsSince:self.lastVersion inDict:[self localVersionsDict]];
|
||
|
}
|
||
|
}
|
||
|
return _versionDetails;
|
||
|
}
|
||
|
|
||
|
- (NSString *)URLEncodedString:(NSString *)string
|
||
|
{
|
||
|
CFStringRef stringRef = CFBridgingRetain(string);
|
||
|
CFStringRef encoded = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
|
||
|
stringRef,
|
||
|
NULL,
|
||
|
CFSTR("!*'\"();:@&=+$,/?%#[]% "),
|
||
|
kCFStringEncodingUTF8);
|
||
|
CFRelease(stringRef);
|
||
|
return CFBridgingRelease(encoded);
|
||
|
}
|
||
|
|
||
|
- (void)downloadedVersionsData
|
||
|
{
|
||
|
|
||
|
#if !TARGET_OS_IPHONE
|
||
|
|
||
|
//only show when main window is available
|
||
|
if (self.onlyPromptIfMainWindowIsAvailable && ![[NSApplication sharedApplication] mainWindow])
|
||
|
{
|
||
|
[self performSelector:@selector(downloadedVersionsData) withObject:nil afterDelay:0.5];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
if (self.checkingForNewVersion)
|
||
|
{
|
||
|
//no longer checking
|
||
|
self.checkingForNewVersion = NO;
|
||
|
|
||
|
//check if data downloaded
|
||
|
if (!self.remoteVersionsDict)
|
||
|
{
|
||
|
//log the error
|
||
|
if (self.downloadError)
|
||
|
{
|
||
|
NSLog(@"iVersion update check failed because: %@", [self.downloadError localizedDescription]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NSLog(@"iVersion update check failed because an unknown error occured");
|
||
|
}
|
||
|
|
||
|
if ([self.delegate respondsToSelector:@selector(iVersionVersionCheckDidFailWithError:)])
|
||
|
{
|
||
|
[self.delegate iVersionVersionCheckDidFailWithError:self.downloadError];
|
||
|
}
|
||
|
|
||
|
//deprecated code path
|
||
|
else if ([self.delegate respondsToSelector:@selector(iVersionVersionCheckFailed:)])
|
||
|
{
|
||
|
NSLog(@"iVersionVersionCheckFailed: delegate method is deprecated, use iVersionVersionCheckDidFailWithError: instead");
|
||
|
[self.delegate performSelector:@selector(iVersionVersionCheckFailed:) withObject:self.downloadError];
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//get version details
|
||
|
NSString *details = [self versionDetailsSince:self.applicationVersion inDict:self.remoteVersionsDict];
|
||
|
NSString *mostRecentVersion = [self mostRecentVersionInDict:self.remoteVersionsDict];
|
||
|
if (details)
|
||
|
{
|
||
|
//inform delegate of new version
|
||
|
if ([self.delegate respondsToSelector:@selector(iVersionDidDetectNewVersion:details:)])
|
||
|
{
|
||
|
[self.delegate iVersionDidDetectNewVersion:mostRecentVersion details:details];
|
||
|
}
|
||
|
|
||
|
//deprecated code path
|
||
|
else if ([self.delegate respondsToSelector:@selector(iVersionDetectedNewVersion:details:)])
|
||
|
{
|
||
|
NSLog(@"iVersionDetectedNewVersion:details: delegate method is deprecated, use iVersionDidDetectNewVersion:details: instead");
|
||
|
[self.delegate performSelector:@selector(iVersionDetectedNewVersion:details:) withObject:mostRecentVersion withObject:details];
|
||
|
}
|
||
|
|
||
|
//check if ignored
|
||
|
BOOL showDetails = ![self.ignoredVersion isEqualToString:mostRecentVersion] || self.previewMode;
|
||
|
if (showDetails)
|
||
|
{
|
||
|
if ([self.delegate respondsToSelector:@selector(iVersionShouldDisplayNewVersion:details:)])
|
||
|
{
|
||
|
showDetails = [self.delegate iVersionShouldDisplayNewVersion:mostRecentVersion details:details];
|
||
|
if (!showDetails && self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion did not display the new version because the iVersionShouldDisplayNewVersion:details: delegate method returned NO");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion did not display the new version because it was marked as ignored");
|
||
|
}
|
||
|
|
||
|
//show details
|
||
|
if (showDetails && !self.visibleRemoteAlert)
|
||
|
{
|
||
|
NSString *title = self.updateAvailableTitle;
|
||
|
if (!self.groupNotesByVersion)
|
||
|
{
|
||
|
title = [title stringByAppendingFormat:@" (%@)", mostRecentVersion];
|
||
|
}
|
||
|
|
||
|
self.visibleRemoteAlert = [self showAlertWithTitle:title
|
||
|
details:details
|
||
|
defaultButton:self.downloadButtonLabel
|
||
|
ignoreButton:[self showIgnoreButton]? self.ignoreButtonLabel: nil
|
||
|
remindButton:[self showRemindButton]? self.remindButtonLabel: nil];
|
||
|
}
|
||
|
}
|
||
|
else if ([self.delegate respondsToSelector:@selector(iVersionDidNotDetectNewVersion)])
|
||
|
{
|
||
|
[self.delegate iVersionDidNotDetectNewVersion];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (BOOL)shouldCheckForNewVersion
|
||
|
{
|
||
|
//debug mode?
|
||
|
if (!self.previewMode)
|
||
|
{
|
||
|
//check if within the reminder period
|
||
|
if (self.lastReminded != nil)
|
||
|
{
|
||
|
//reminder takes priority over check period
|
||
|
if ([[NSDate date] timeIntervalSinceDate:self.lastReminded] < self.remindPeriod * SECONDS_IN_A_DAY)
|
||
|
{
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion did not check for a new version because the user last asked to be reminded less than %g days ago", self.remindPeriod);
|
||
|
}
|
||
|
return NO;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//check if within the check period
|
||
|
else if (self.lastChecked != nil && [[NSDate date] timeIntervalSinceDate:self.lastChecked] < self.checkPeriod * SECONDS_IN_A_DAY)
|
||
|
{
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion did not check for a new version because the last check was less than %g days ago", self.checkPeriod);
|
||
|
}
|
||
|
return NO;
|
||
|
}
|
||
|
}
|
||
|
else if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion debug mode is enabled - make sure you disable this for release");
|
||
|
}
|
||
|
|
||
|
//confirm with delegate
|
||
|
if ([self.delegate respondsToSelector:@selector(iVersionShouldCheckForNewVersion)])
|
||
|
{
|
||
|
BOOL shouldCheck = [self.delegate iVersionShouldCheckForNewVersion];
|
||
|
if (!shouldCheck && self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion did not check for a new version because the iVersionShouldCheckForNewVersion delegate method returned NO");
|
||
|
}
|
||
|
return shouldCheck;
|
||
|
}
|
||
|
|
||
|
//perform the check
|
||
|
return YES;
|
||
|
}
|
||
|
|
||
|
- (NSString *)valueForKey:(NSString *)key inJSON:(id)json
|
||
|
{
|
||
|
if ([json isKindOfClass:[NSString class]])
|
||
|
{
|
||
|
//use legacy parser
|
||
|
NSRange keyRange = [json rangeOfString:[NSString stringWithFormat:@"\"%@\"", key]];
|
||
|
if (keyRange.location != NSNotFound)
|
||
|
{
|
||
|
NSInteger start = keyRange.location + keyRange.length;
|
||
|
NSRange valueStart = [json rangeOfString:@":" options:(NSStringCompareOptions)0 range:NSMakeRange(start, [(NSString *)json length] - start)];
|
||
|
if (valueStart.location != NSNotFound)
|
||
|
{
|
||
|
start = valueStart.location + 1;
|
||
|
NSRange valueEnd = [json rangeOfString:@"," options:(NSStringCompareOptions)0 range:NSMakeRange(start, [(NSString *)json length] - start)];
|
||
|
if (valueEnd.location != NSNotFound)
|
||
|
{
|
||
|
NSString *value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
|
||
|
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||
|
while ([value hasPrefix:@"\""] && ![value hasSuffix:@"\""])
|
||
|
{
|
||
|
if (valueEnd.location == NSNotFound)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
NSInteger newStart = valueEnd.location + 1;
|
||
|
valueEnd = [json rangeOfString:@"," options:(NSStringCompareOptions)0 range:NSMakeRange(newStart, [(NSString *)json length] - newStart)];
|
||
|
value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
|
||
|
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||
|
}
|
||
|
|
||
|
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]];
|
||
|
value = [value stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
|
||
|
value = [value stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
|
||
|
value = [value stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""];
|
||
|
value = [value stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
|
||
|
value = [value stringByReplacingOccurrencesOfString:@"\\r" withString:@"\r"];
|
||
|
value = [value stringByReplacingOccurrencesOfString:@"\\t" withString:@"\t"];
|
||
|
value = [value stringByReplacingOccurrencesOfString:@"\\f" withString:@"\f"];
|
||
|
value = [value stringByReplacingOccurrencesOfString:@"\\b" withString:@"\f"];
|
||
|
|
||
|
while (YES)
|
||
|
{
|
||
|
NSRange unicode = [value rangeOfString:@"\\u"];
|
||
|
if (unicode.location == NSNotFound || unicode.location + unicode.length == 0)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
uint32_t c = 0;
|
||
|
NSString *hex = [value substringWithRange:NSMakeRange(unicode.location + 2, 4)];
|
||
|
NSScanner *scanner = [NSScanner scannerWithString:hex];
|
||
|
[scanner scanHexInt:&c];
|
||
|
|
||
|
if (c <= 0xffff)
|
||
|
{
|
||
|
value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C", (unichar)c]];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//convert character to surrogate pair
|
||
|
uint16_t x = (uint16_t)c;
|
||
|
uint16_t u = (c >> 16) & ((1 << 5) - 1);
|
||
|
uint16_t w = (uint16_t)u - 1;
|
||
|
unichar high = 0xd800 | (w << 6) | x >> 10;
|
||
|
unichar low = (uint16_t)(0xdc00 | (x & ((1 << 10) - 1)));
|
||
|
|
||
|
value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C%C", high, low]];
|
||
|
}
|
||
|
}
|
||
|
return value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return json[key];
|
||
|
}
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
- (void)setAppStoreIDOnMainThread:(NSString *)appStoreIDString
|
||
|
{
|
||
|
self.appStoreID = [appStoreIDString longLongValue];
|
||
|
}
|
||
|
|
||
|
- (void)checkForNewVersionInBackground
|
||
|
{
|
||
|
@synchronized (self)
|
||
|
{
|
||
|
@autoreleasepool
|
||
|
{
|
||
|
BOOL newerVersionAvailable = NO;
|
||
|
BOOL osVersionSupported = NO;
|
||
|
NSString *latestVersion = nil;
|
||
|
NSDictionary *versions = nil;
|
||
|
|
||
|
//first check iTunes
|
||
|
NSString *iTunesServiceURL = [NSString stringWithFormat:iVersionAppLookupURLFormat, self.appStoreCountry];
|
||
|
if (self.appStoreID)
|
||
|
{
|
||
|
iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?id=%@", @(self.appStoreID)];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?bundleId=%@", self.applicationBundleID];
|
||
|
}
|
||
|
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion is checking %@ for a new app version...", iTunesServiceURL);
|
||
|
}
|
||
|
|
||
|
NSError *error = nil;
|
||
|
NSURLResponse *response = nil;
|
||
|
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:iTunesServiceURL]
|
||
|
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
||
|
timeoutInterval:REQUEST_TIMEOUT];
|
||
|
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
|
||
|
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
|
||
|
if (data && statusCode == 200)
|
||
|
{
|
||
|
//in case error is garbage...
|
||
|
error = nil;
|
||
|
|
||
|
id json = nil;
|
||
|
if ([NSJSONSerialization class])
|
||
|
{
|
||
|
json = [[NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)0 error:&error][@"results"] lastObject];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//convert to string
|
||
|
json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||
|
}
|
||
|
|
||
|
if (!error)
|
||
|
{
|
||
|
//check bundle ID matches
|
||
|
NSString *bundleID = [self valueForKey:@"bundleId" inJSON:json];
|
||
|
if (bundleID)
|
||
|
{
|
||
|
if ([bundleID isEqualToString:self.applicationBundleID])
|
||
|
{
|
||
|
//get supported OS version
|
||
|
NSString *minimumSupportedOSVersion = [self valueForKey:@"minimumOsVersion" inJSON:json];
|
||
|
|
||
|
#if TARGET_OS_IPHONE
|
||
|
|
||
|
NSString *systemVersion = [UIDevice currentDevice].systemVersion;
|
||
|
|
||
|
#else
|
||
|
NSString *systemVersion = nil;
|
||
|
|
||
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1100
|
||
|
|
||
|
if ([[NSProcessInfo class] respondsToSelector:@selector(processInfo)])
|
||
|
{
|
||
|
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
|
||
|
systemVersion = [NSString stringWithFormat:@"%zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion];
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
|
||
|
#pragma clang diagnostic push
|
||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||
|
|
||
|
SInt32 majorVersion = 0, minorVersion = 0, patchVersion = 0;
|
||
|
Gestalt(gestaltSystemVersionMajor, &majorVersion);
|
||
|
Gestalt(gestaltSystemVersionMinor, &minorVersion);
|
||
|
Gestalt(gestaltSystemVersionBugFix, &patchVersion);
|
||
|
systemVersion = [NSString stringWithFormat:@"%d.%d.%d", majorVersion, minorVersion, patchVersion];
|
||
|
|
||
|
#pragma clang diagnostic pop
|
||
|
|
||
|
}
|
||
|
#endif
|
||
|
osVersionSupported = ([systemVersion compare:minimumSupportedOSVersion options:NSNumericSearch] != NSOrderedAscending);
|
||
|
if (!osVersionSupported)
|
||
|
{
|
||
|
error = [NSError errorWithDomain:iVersionErrorDomain
|
||
|
code:iVersionErrorOSVersionNotSupported
|
||
|
userInfo:@{NSLocalizedDescriptionKey: @"Current OS version is not supported."}];
|
||
|
}
|
||
|
|
||
|
//get version details
|
||
|
NSString *releaseNotes = [self valueForKey:@"releaseNotes" inJSON:json];
|
||
|
latestVersion = [self valueForKey:@"version" inJSON:json];
|
||
|
if (latestVersion && osVersionSupported)
|
||
|
{
|
||
|
versions = @{latestVersion: releaseNotes ?: @""};
|
||
|
}
|
||
|
|
||
|
//get app id
|
||
|
if (!self.appStoreID)
|
||
|
{
|
||
|
NSString *appStoreIDString = [self valueForKey:@"trackId" inJSON:json];
|
||
|
[self performSelectorOnMainThread:@selector(setAppStoreIDOnMainThread:) withObject:appStoreIDString waitUntilDone:YES];
|
||
|
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion found the app on iTunes. The App Store ID is %@", appStoreIDString);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//check for new version
|
||
|
newerVersionAvailable = ([latestVersion compareVersion:self.applicationVersion] == NSOrderedDescending);
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
if (newerVersionAvailable)
|
||
|
{
|
||
|
NSLog(@"iVersion found a new version (%@) of the app on iTunes. Current version is %@", latestVersion, self.applicationVersion);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NSLog(@"iVersion did not find a new version of the app on iTunes. Current version is %@, latest version is %@", self.applicationVersion, latestVersion);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion found that the application bundle ID (%@) does not match the bundle ID of the app found on iTunes (%@) with the specified App Store ID (%@)", self.applicationBundleID, bundleID, @(self.appStoreID));
|
||
|
}
|
||
|
|
||
|
error = [NSError errorWithDomain:iVersionErrorDomain
|
||
|
code:iVersionErrorBundleIdDoesNotMatchAppStore
|
||
|
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Application bundle ID does not match expected value of %@", bundleID]}];
|
||
|
}
|
||
|
}
|
||
|
else if (self.appStoreID || !self.remoteVersionsPlistURL)
|
||
|
{
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion could not find this application on iTunes. If your app is not intended for App Store release then you must specify a remoteVersionsPlistURL. If this is the first release of your application then it's not a problem that it cannot be found on the store yet");
|
||
|
}
|
||
|
|
||
|
error = [NSError errorWithDomain:iVersionErrorDomain
|
||
|
code:iVersionErrorApplicationNotFoundOnAppStore
|
||
|
userInfo:@{NSLocalizedDescriptionKey: @"The application could not be found on the App Store."}];
|
||
|
}
|
||
|
else if (!self.appStoreID && self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion could not find your app on iTunes. If your app is not yet on the store or is not intended for App Store release then don't worry about this");
|
||
|
}
|
||
|
|
||
|
//now check plist for alternative release notes
|
||
|
if (((self.appStoreID && newerVersionAvailable && osVersionSupported) || !self.appStoreID || self.previewMode) && self.remoteVersionsPlistURL)
|
||
|
{
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion will check %@ for %@", self.remoteVersionsPlistURL, self.appStoreID? @"release notes": @"a new app version");
|
||
|
}
|
||
|
|
||
|
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.remoteVersionsPlistURL] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:REQUEST_TIMEOUT];
|
||
|
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
|
||
|
if (data)
|
||
|
{
|
||
|
NSPropertyListFormat format;
|
||
|
NSDictionary *plistVersions = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:&format error:&error];
|
||
|
|
||
|
if (latestVersion)
|
||
|
{
|
||
|
//remove versions that are greater than latest in app store
|
||
|
NSMutableDictionary *versions = [NSMutableDictionary dictionary];
|
||
|
for (NSString *version in plistVersions)
|
||
|
{
|
||
|
if ([version compareVersion:latestVersion] != NSOrderedDescending)
|
||
|
{
|
||
|
versions[version] = plistVersions[version];
|
||
|
}
|
||
|
}
|
||
|
plistVersions = versions;
|
||
|
}
|
||
|
if (!latestVersion || plistVersions[latestVersion] || !_useAppStoreDetailsIfNoPlistEntryFound)
|
||
|
{
|
||
|
versions = [plistVersions copy];
|
||
|
}
|
||
|
}
|
||
|
else if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion was unable to download the user-specified release notes");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (statusCode >= 400)
|
||
|
{
|
||
|
//http error
|
||
|
NSString *message = [NSString stringWithFormat:@"The server returned a %@ error", @(statusCode)];
|
||
|
error = [NSError errorWithDomain:@"HTTPResponseErrorDomain" code:statusCode userInfo:@{NSLocalizedDescriptionKey: message}];
|
||
|
}
|
||
|
}
|
||
|
[self performSelectorOnMainThread:@selector(setDownloadError:) withObject:error waitUntilDone:YES];
|
||
|
[self performSelectorOnMainThread:@selector(setRemoteVersionsDict:) withObject:versions waitUntilDone:YES];
|
||
|
[self performSelectorOnMainThread:@selector(setLastChecked:) withObject:[NSDate date] waitUntilDone:YES];
|
||
|
[self performSelectorOnMainThread:@selector(downloadedVersionsData) withObject:nil waitUntilDone:YES];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)checkForNewVersion
|
||
|
{
|
||
|
if (!self.checkingForNewVersion)
|
||
|
{
|
||
|
self.checkingForNewVersion = YES;
|
||
|
[self performSelectorInBackground:@selector(checkForNewVersionInBackground) withObject:nil];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)checkIfNewVersion
|
||
|
{
|
||
|
|
||
|
#if !TARGET_OS_IPHONE
|
||
|
|
||
|
//only show when main window is available
|
||
|
if (self.onlyPromptIfMainWindowIsAvailable && ![[NSApplication sharedApplication] mainWindow])
|
||
|
{
|
||
|
[self performSelector:@selector(checkIfNewVersion) withObject:nil afterDelay:0.5];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
if (self.lastVersion != nil || self.showOnFirstLaunch || self.previewMode)
|
||
|
{
|
||
|
if ([self.applicationVersion compareVersion:self.lastVersion] == NSOrderedDescending || self.previewMode)
|
||
|
{
|
||
|
//clear reminder
|
||
|
self.lastReminded = nil;
|
||
|
|
||
|
//get version details
|
||
|
BOOL showDetails = !!self.versionDetails;
|
||
|
if (showDetails && [self.delegate respondsToSelector:@selector(iVersionShouldDisplayCurrentVersionDetails:)])
|
||
|
{
|
||
|
showDetails = [self.delegate iVersionShouldDisplayCurrentVersionDetails:self.versionDetails];
|
||
|
}
|
||
|
|
||
|
//show details
|
||
|
if (showDetails && !self.visibleLocalAlert && !self.visibleRemoteAlert)
|
||
|
{
|
||
|
self.visibleLocalAlert = [self showAlertWithTitle:self.inThisVersionTitle
|
||
|
details:self.versionDetails
|
||
|
defaultButton:self.okButtonLabel
|
||
|
ignoreButton:nil
|
||
|
remindButton:nil];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//record this as last viewed release
|
||
|
self.viewedVersionDetails = YES;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (BOOL)showIgnoreButton
|
||
|
{
|
||
|
return [self.ignoreButtonLabel length] && self.updatePriority < iVersionUpdatePriorityMedium;
|
||
|
}
|
||
|
|
||
|
- (BOOL)showRemindButton
|
||
|
{
|
||
|
return [self.remindButtonLabel length] && self.updatePriority < iVersionUpdatePriorityHigh;
|
||
|
}
|
||
|
|
||
|
- (id)showAlertWithTitle:(NSString *)title
|
||
|
details:(NSString *)details
|
||
|
defaultButton:(NSString *)defaultButton
|
||
|
ignoreButton:(NSString *)ignoreButton
|
||
|
remindButton:(NSString *)remindButton
|
||
|
{
|
||
|
|
||
|
#if TARGET_OS_IPHONE
|
||
|
|
||
|
UIViewController *topController = [UIApplication sharedApplication].delegate.window.rootViewController;
|
||
|
while (topController.presentedViewController)
|
||
|
{
|
||
|
topController = topController.presentedViewController;
|
||
|
}
|
||
|
|
||
|
if ([UIAlertController class] && topController && self.useUIAlertControllerIfAvailable)
|
||
|
{
|
||
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:details preferredStyle:UIAlertControllerStyleAlert];
|
||
|
|
||
|
//download/ok action
|
||
|
[alert addAction:[UIAlertAction actionWithTitle:self.downloadButtonLabel style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
|
||
|
[self didDismissAlert:alert withButtonAtIndex:0];
|
||
|
}]];
|
||
|
|
||
|
//ignore action
|
||
|
if ([self showIgnoreButton])
|
||
|
{
|
||
|
[alert addAction:[UIAlertAction actionWithTitle:self.ignoreButtonLabel style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction *action) {
|
||
|
[self didDismissAlert:alert withButtonAtIndex:1];
|
||
|
}]];
|
||
|
}
|
||
|
|
||
|
//remind action
|
||
|
if ([self showRemindButton])
|
||
|
{
|
||
|
[alert addAction:[UIAlertAction actionWithTitle:self.remindButtonLabel style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
|
||
|
[self didDismissAlert:alert withButtonAtIndex:[self showIgnoreButton]? 2: 1];
|
||
|
}]];
|
||
|
}
|
||
|
|
||
|
//get current view controller and present alert
|
||
|
[topController presentViewController:alert animated:YES completion:NULL];
|
||
|
|
||
|
return alert;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
|
||
|
message:details
|
||
|
delegate:(id<UIAlertViewDelegate>)self
|
||
|
cancelButtonTitle:nil
|
||
|
otherButtonTitles:defaultButton, nil];
|
||
|
if (ignoreButton)
|
||
|
{
|
||
|
[alert addButtonWithTitle:ignoreButton];
|
||
|
alert.cancelButtonIndex = 1;
|
||
|
}
|
||
|
|
||
|
if (remindButton)
|
||
|
{
|
||
|
[alert addButtonWithTitle:remindButton];
|
||
|
}
|
||
|
|
||
|
[alert show];
|
||
|
|
||
|
return alert;
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
NSAlert *alert = [[NSAlert alloc] init];
|
||
|
alert.messageText = title;
|
||
|
alert.informativeText = self.inThisVersionTitle;
|
||
|
[alert addButtonWithTitle:defaultButton];
|
||
|
|
||
|
NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 380.0, 15.0)];
|
||
|
NSSize contentSize = [scrollview contentSize];
|
||
|
scrollview.borderType = NSBezelBorder;
|
||
|
scrollview.hasVerticalScroller = YES;
|
||
|
scrollview.hasHorizontalScroller = NO;
|
||
|
scrollview.autoresizingMask = (NSAutoresizingMaskOptions)(NSViewWidthSizable|NSViewHeightSizable);
|
||
|
|
||
|
NSTextView *textView = [[NSTextView alloc] initWithFrame:NSMakeRect(0.0, 0.0, contentSize.width, contentSize.height)];
|
||
|
textView.minSize = NSMakeSize(0.0, contentSize.height);
|
||
|
textView.maxSize = NSMakeSize(FLT_MAX, FLT_MAX);
|
||
|
textView.verticallyResizable = YES;
|
||
|
textView.horizontallyResizable = NO;
|
||
|
textView.autoresizingMask = NSViewWidthSizable;
|
||
|
textView.textContainer.containerSize = NSMakeSize(contentSize.width, FLT_MAX);
|
||
|
textView.textContainer.widthTracksTextView = YES;
|
||
|
textView.string = details;
|
||
|
scrollview.documentView = textView;
|
||
|
[textView sizeToFit];
|
||
|
|
||
|
CGFloat height = MIN(200.0, [[scrollview documentView] frame].size.height) + 3.0;
|
||
|
scrollview.frame = NSMakeRect(0.0, 0.0, scrollview.frame.size.width, height);
|
||
|
alert.accessoryView = scrollview;
|
||
|
|
||
|
if (ignoreButton)
|
||
|
{
|
||
|
[alert addButtonWithTitle:ignoreButton];
|
||
|
}
|
||
|
|
||
|
if (remindButton)
|
||
|
{
|
||
|
[alert addButtonWithTitle:remindButton];
|
||
|
}
|
||
|
|
||
|
#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9
|
||
|
|
||
|
if (![alert respondsToSelector:@selector(runModal:)])
|
||
|
{
|
||
|
|
||
|
NSModalResponse modalResponse = [alert runModal];
|
||
|
if (modalResponse == NSAlertFirstButtonReturn)
|
||
|
{
|
||
|
//right most button
|
||
|
[self didDismissAlert:alert withButtonAtIndex:0];
|
||
|
}
|
||
|
else if (modalResponse == NSAlertSecondButtonReturn)
|
||
|
{
|
||
|
[self didDismissAlert:alert withButtonAtIndex:1];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
[self didDismissAlert:alert withButtonAtIndex:2];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
|
||
|
#endif
|
||
|
|
||
|
{
|
||
|
NSModalResponse modalResponse = [alert runModal];
|
||
|
if (modalResponse == NSAlertFirstButtonReturn)
|
||
|
{
|
||
|
//right most button
|
||
|
[self didDismissAlert:alert withButtonAtIndex:0];
|
||
|
}
|
||
|
else if (modalResponse == NSAlertSecondButtonReturn)
|
||
|
{
|
||
|
[self didDismissAlert:alert withButtonAtIndex:1];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
[self didDismissAlert:alert withButtonAtIndex:2];
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return alert;
|
||
|
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
- (void)didDismissAlert:(id)alertView withButtonAtIndex:(NSInteger)buttonIndex
|
||
|
{
|
||
|
//get button indices
|
||
|
NSInteger downloadButtonIndex = 0;
|
||
|
NSInteger ignoreButtonIndex = [self showIgnoreButton]? 1: 0;
|
||
|
NSInteger remindButtonIndex = [self showRemindButton]? ignoreButtonIndex + 1: 0;
|
||
|
|
||
|
//latest version
|
||
|
NSString *latestVersion = [self mostRecentVersionInDict:self.remoteVersionsDict];
|
||
|
|
||
|
if (alertView == self.visibleLocalAlert)
|
||
|
{
|
||
|
//record that details have been viewed
|
||
|
self.viewedVersionDetails = YES;
|
||
|
|
||
|
//release alert
|
||
|
self.visibleLocalAlert = nil;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (buttonIndex == downloadButtonIndex)
|
||
|
{
|
||
|
//clear reminder
|
||
|
self.lastReminded = nil;
|
||
|
|
||
|
//log event
|
||
|
if ([self.delegate respondsToSelector:@selector(iVersionUserDidAttemptToDownloadUpdate:)])
|
||
|
{
|
||
|
[self.delegate iVersionUserDidAttemptToDownloadUpdate:latestVersion];
|
||
|
}
|
||
|
|
||
|
if (![self.delegate respondsToSelector:@selector(iVersionShouldOpenAppStore)] ||
|
||
|
[self.delegate iVersionShouldOpenAppStore])
|
||
|
{
|
||
|
//go to download page
|
||
|
[self openAppPageInAppStore];
|
||
|
}
|
||
|
}
|
||
|
else if (buttonIndex == ignoreButtonIndex)
|
||
|
{
|
||
|
//ignore this version
|
||
|
self.ignoredVersion = latestVersion;
|
||
|
self.lastReminded = nil;
|
||
|
|
||
|
//log event
|
||
|
if ([self.delegate respondsToSelector:@selector(iVersionUserDidIgnoreUpdate:)])
|
||
|
{
|
||
|
[self.delegate iVersionUserDidIgnoreUpdate:latestVersion];
|
||
|
}
|
||
|
}
|
||
|
else if (buttonIndex == remindButtonIndex)
|
||
|
{
|
||
|
//remind later
|
||
|
self.lastReminded = [NSDate date];
|
||
|
|
||
|
//log event
|
||
|
if ([self.delegate respondsToSelector:@selector(iVersionUserDidRequestReminderForUpdate:)])
|
||
|
{
|
||
|
[self.delegate iVersionUserDidRequestReminderForUpdate:latestVersion];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//release alert
|
||
|
self.visibleRemoteAlert = nil;
|
||
|
}
|
||
|
|
||
|
#if TARGET_OS_IPHONE
|
||
|
|
||
|
- (BOOL)openAppPageInAppStore
|
||
|
{
|
||
|
if (!_updateURL && !self.appStoreID)
|
||
|
{
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion was unable to open the App Store because the app store ID is not set.");
|
||
|
}
|
||
|
return NO;
|
||
|
}
|
||
|
|
||
|
#if defined(IVERSION_USE_STOREKIT) && IVERSION_USE_STOREKIT
|
||
|
|
||
|
if (!_updateURL && [SKStoreProductViewController class])
|
||
|
{
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion will attempt to open the StoreKit in-app product page using the following app store ID: %@", @(self.appStoreID));
|
||
|
}
|
||
|
|
||
|
//create store view controller
|
||
|
SKStoreProductViewController *productController = [[SKStoreProductViewController alloc] init];
|
||
|
productController.delegate = (id<SKStoreProductViewControllerDelegate>)self;
|
||
|
|
||
|
//load product details
|
||
|
NSDictionary *productParameters = @{SKStoreProductParameterITunesItemIdentifier: [@(_appStoreID) description]};
|
||
|
[productController loadProductWithParameters:productParameters completionBlock:NULL];
|
||
|
|
||
|
//get root view controller
|
||
|
UIWindow *window = [[UIApplication sharedApplication] delegate].window;
|
||
|
UIViewController *rootViewController = window.rootViewController;
|
||
|
if (!rootViewController)
|
||
|
{
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion couldn't find a root view controller from which to display StoreKit product page");
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
while (rootViewController.presentedViewController)
|
||
|
{
|
||
|
rootViewController = rootViewController.presentedViewController;
|
||
|
}
|
||
|
|
||
|
//present product view controller
|
||
|
[rootViewController presentViewController:productController animated:YES completion:nil];
|
||
|
if ([self.delegate respondsToSelector:@selector(iVersionDidPresentStoreKitModal)])
|
||
|
{
|
||
|
[self.delegate iVersionDidPresentStoreKitModal];
|
||
|
}
|
||
|
return YES;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion will open the App Store using the following URL: %@", self.updateURL);
|
||
|
}
|
||
|
|
||
|
[[UIApplication sharedApplication] openURL:self.updateURL];
|
||
|
return YES;
|
||
|
}
|
||
|
|
||
|
- (void)productViewControllerDidFinish:(UIViewController *)controller
|
||
|
{
|
||
|
[controller.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
|
||
|
if ([self.delegate respondsToSelector:@selector(iVersionDidDismissStoreKitModal)])
|
||
|
{
|
||
|
[self.delegate iVersionDidDismissStoreKitModal];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)resizeAlertView:(UIAlertView *)alertView
|
||
|
{
|
||
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone &&
|
||
|
UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation) &&
|
||
|
[[UIDevice currentDevice].systemVersion floatValue] < 7.0f)
|
||
|
{
|
||
|
CGFloat max = alertView.window.bounds.size.height - alertView.frame.size.height - 10.0f;
|
||
|
CGFloat offset = 0.0f;
|
||
|
for (UIView *view in alertView.subviews)
|
||
|
{
|
||
|
CGRect frame = view.frame;
|
||
|
if ([view isKindOfClass:[UILabel class]])
|
||
|
{
|
||
|
UILabel *label = (UILabel *)view;
|
||
|
if ([label.text isEqualToString:alertView.message])
|
||
|
{
|
||
|
label.lineBreakMode = NSLineBreakByWordWrapping;
|
||
|
label.numberOfLines = 0;
|
||
|
label.alpha = 1.0f;
|
||
|
[label sizeToFit];
|
||
|
offset = label.frame.size.height - frame.size.height;
|
||
|
frame.size.height = label.frame.size.height;
|
||
|
if (offset > max)
|
||
|
{
|
||
|
frame.size.height -= (offset - max);
|
||
|
offset = max;
|
||
|
}
|
||
|
if (offset > max - 10.0f)
|
||
|
{
|
||
|
frame.size.height -= (offset - max - 10);
|
||
|
frame.origin.y += (offset - max - 10) / 2.0f;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ([view isKindOfClass:[UITextView class]])
|
||
|
{
|
||
|
view.alpha = 0.0f;
|
||
|
}
|
||
|
else if ([view isKindOfClass:[UIControl class]])
|
||
|
{
|
||
|
frame.origin.y += offset;
|
||
|
}
|
||
|
view.frame = frame;
|
||
|
}
|
||
|
CGRect frame = alertView.frame;
|
||
|
frame.origin.y -= roundf(offset/2.0f);
|
||
|
frame.size.height += offset;
|
||
|
alertView.frame = frame;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)didRotate
|
||
|
{
|
||
|
[self performSelectorOnMainThread:@selector(resizeAlertView:) withObject:self.visibleLocalAlert waitUntilDone:NO];
|
||
|
[self performSelectorOnMainThread:@selector(resizeAlertView:) withObject:self.visibleRemoteAlert waitUntilDone:NO];
|
||
|
}
|
||
|
|
||
|
- (void)willPresentAlertView:(UIAlertView *)alertView
|
||
|
{
|
||
|
[self resizeAlertView:alertView];
|
||
|
}
|
||
|
|
||
|
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
|
||
|
{
|
||
|
[self didDismissAlert:alertView withButtonAtIndex:buttonIndex];
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(__unused void *)contextInfo
|
||
|
{
|
||
|
[self didDismissAlert:alert withButtonAtIndex:returnCode - NSAlertFirstButtonReturn];
|
||
|
}
|
||
|
|
||
|
- (void)openAppPageWhenAppStoreLaunched
|
||
|
{
|
||
|
//check if app store is running
|
||
|
for (NSRunningApplication *app in [[NSWorkspace sharedWorkspace] runningApplications])
|
||
|
{
|
||
|
if ([app.bundleIdentifier isEqualToString:iVersionMacAppStoreBundleID])
|
||
|
{
|
||
|
//open app page
|
||
|
[[NSWorkspace sharedWorkspace] performSelector:@selector(openURL:) withObject:self.updateURL afterDelay:MAC_APP_STORE_REFRESH_DELAY];
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//try again
|
||
|
[self performSelector:@selector(openAppPageWhenAppStoreLaunched) withObject:nil afterDelay:0.0];
|
||
|
}
|
||
|
|
||
|
- (BOOL)openAppPageInAppStore
|
||
|
{
|
||
|
if (!_updateURL && !self.appStoreID)
|
||
|
{
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion was unable to open the App Store because the app store ID is not set.");
|
||
|
}
|
||
|
return NO;
|
||
|
}
|
||
|
|
||
|
if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion will open the App Store using the following URL: %@", self.updateURL);
|
||
|
}
|
||
|
|
||
|
[[NSWorkspace sharedWorkspace] openURL:self.updateURL];
|
||
|
if (!_updateURL) [self openAppPageWhenAppStoreLaunched];
|
||
|
return YES;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
- (void)applicationLaunched
|
||
|
{
|
||
|
|
||
|
if (self.checkAtLaunch)
|
||
|
{
|
||
|
[self checkIfNewVersion];
|
||
|
if ([self shouldCheckForNewVersion]) [self checkForNewVersion];
|
||
|
}
|
||
|
else if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion will not check for updates because the checkAtLaunch option is disabled");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if TARGET_OS_IPHONE
|
||
|
|
||
|
- (void)applicationWillEnterForeground
|
||
|
{
|
||
|
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
|
||
|
{
|
||
|
if (self.checkAtLaunch)
|
||
|
{
|
||
|
if ([self shouldCheckForNewVersion]) [self checkForNewVersion];
|
||
|
}
|
||
|
else if (self.verboseLogging)
|
||
|
{
|
||
|
NSLog(@"iVersion will not check for updates because the checkAtLaunch option is disabled");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
@end
|