iVersion.m 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438
  1. //
  2. // iVersion.m
  3. //
  4. // Version 1.11.5
  5. //
  6. // Created by Nick Lockwood on 26/01/2011.
  7. // Copyright 2011 Charcoal Design
  8. //
  9. // Distributed under the permissive zlib license
  10. // Get the latest version from here:
  11. //
  12. // https://github.com/nicklockwood/iVersion
  13. //
  14. // This software is provided 'as-is', without any express or implied
  15. // warranty. In no event will the authors be held liable for any damages
  16. // arising from the use of this software.
  17. //
  18. // Permission is granted to anyone to use this software for any purpose,
  19. // including commercial applications, and to alter it and redistribute it
  20. // freely, subject to the following restrictions:
  21. //
  22. // 1. The origin of this software must not be misrepresented; you must not
  23. // claim that you wrote the original software. If you use this software
  24. // in a product, an acknowledgment in the product documentation would be
  25. // appreciated but is not required.
  26. //
  27. // 2. Altered source versions must be plainly marked as such, and must not be
  28. // misrepresented as being the original software.
  29. //
  30. // 3. This notice may not be removed or altered from any source distribution.
  31. //
  32. #import "iVersion.h"
  33. #pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
  34. #pragma clang diagnostic ignored "-Wobjc-missing-property-synthesis"
  35. #pragma clang diagnostic ignored "-Wpartial-availability"
  36. #pragma clang diagnostic ignored "-Wundeclared-selector"
  37. #pragma clang diagnostic ignored "-Wdirect-ivar-access"
  38. #pragma clang diagnostic ignored "-Wdouble-promotion"
  39. #pragma clang diagnostic ignored "-Wunused-macros"
  40. #pragma clang diagnostic ignored "-Wconversion"
  41. #pragma clang diagnostic ignored "-Wselector"
  42. #pragma clang diagnostic ignored "-Wshadow"
  43. #pragma clang diagnostic ignored "-Wgnu"
  44. #import <Availability.h>
  45. #if !__has_feature(objc_arc)
  46. #error This class requires automatic reference counting
  47. #endif
  48. NSString *const iVersionErrorDomain = @"iVersionErrorDomain";
  49. NSString *const iVersionInThisVersionTitleKey = @"iVersionInThisVersionTitle";
  50. NSString *const iVersionUpdateAvailableTitleKey = @"iVersionUpdateAvailableTitle";
  51. NSString *const iVersionVersionLabelFormatKey = @"iVersionVersionLabelFormat";
  52. NSString *const iVersionOKButtonKey = @"iVersionOKButton";
  53. NSString *const iVersionIgnoreButtonKey = @"iVersionIgnoreButton";
  54. NSString *const iVersionRemindButtonKey = @"iVersionRemindButton";
  55. NSString *const iVersionDownloadButtonKey = @"iVersionDownloadButton";
  56. static NSString *const iVersionAppStoreIDKey = @"iVersionAppStoreID";
  57. static NSString *const iVersionLastVersionKey = @"iVersionLastVersionChecked";
  58. static NSString *const iVersionIgnoreVersionKey = @"iVersionIgnoreVersion";
  59. static NSString *const iVersionLastCheckedKey = @"iVersionLastChecked";
  60. static NSString *const iVersionLastRemindedKey = @"iVersionLastReminded";
  61. static NSString *const iVersionMacAppStoreBundleID = @"com.apple.appstore";
  62. static NSString *const iVersionAppLookupURLFormat = @"https://itunes.apple.com/%@/lookup";
  63. static NSString *const iVersioniOSAppStoreURLFormat = @"itms-apps://itunes.apple.com/app/id%@";
  64. static NSString *const iVersionMacAppStoreURLFormat = @"macappstore://itunes.apple.com/app/id%@";
  65. #define SECONDS_IN_A_DAY 86400.0
  66. #define MAC_APP_STORE_REFRESH_DELAY 5.0
  67. #define REQUEST_TIMEOUT 60.0
  68. @implementation NSString(iVersion)
  69. - (NSComparisonResult)compareVersion:(NSString *)version
  70. {
  71. return [self compare:version options:NSNumericSearch];
  72. }
  73. - (NSComparisonResult)compareVersionDescending:(NSString *)version
  74. {
  75. return (NSComparisonResult)(0 - [self compareVersion:version]);
  76. }
  77. @end
  78. @interface iVersion ()
  79. @property (nonatomic, copy) NSDictionary *remoteVersionsDict;
  80. @property (nonatomic, strong) NSError *downloadError;
  81. @property (nonatomic, copy) NSString *versionDetails;
  82. @property (nonatomic, strong) id visibleLocalAlert;
  83. @property (nonatomic, strong) id visibleRemoteAlert;
  84. @property (nonatomic, assign) BOOL checkingForNewVersion;
  85. @end
  86. @implementation iVersion
  87. + (void)load
  88. {
  89. // [self performSelectorOnMainThread:@selector(sharedInstance) withObject:nil waitUntilDone:NO];
  90. }
  91. + (iVersion *)sharedInstance
  92. {
  93. static iVersion *sharedInstance = nil;
  94. if (sharedInstance == nil)
  95. {
  96. sharedInstance = [[iVersion alloc] init];
  97. }
  98. return sharedInstance;
  99. }
  100. - (NSString *)localizedStringForKey:(NSString *)key withDefault:(NSString *)defaultString
  101. {
  102. static NSBundle *bundle = nil;
  103. if (bundle == nil)
  104. {
  105. NSString *bundlePath = [[NSBundle bundleForClass:[iVersion class]] pathForResource:@"iVersion" ofType:@"bundle"];
  106. if (self.useAllAvailableLanguages)
  107. {
  108. bundle = [NSBundle bundleWithPath:bundlePath];
  109. NSString *language = [[NSLocale preferredLanguages] count]? [NSLocale preferredLanguages][0]: @"en";
  110. if (![[bundle localizations] containsObject:language])
  111. {
  112. language = [language componentsSeparatedByString:@"-"][0];
  113. }
  114. if ([[bundle localizations] containsObject:language])
  115. {
  116. bundlePath = [bundle pathForResource:language ofType:@"lproj"];
  117. }
  118. }
  119. bundle = [NSBundle bundleWithPath:bundlePath] ?: [NSBundle mainBundle];
  120. }
  121. defaultString = [bundle localizedStringForKey:key value:defaultString table:nil];
  122. return [[NSBundle mainBundle] localizedStringForKey:key value:defaultString table:nil];
  123. }
  124. - (iVersion *)init
  125. {
  126. if ((self = [super init]))
  127. {
  128. #if TARGET_OS_IPHONE
  129. //register for iphone application events
  130. if (&UIApplicationWillEnterForegroundNotification)
  131. {
  132. [[NSNotificationCenter defaultCenter] addObserver:self
  133. selector:@selector(applicationWillEnterForeground)
  134. name:UIApplicationWillEnterForegroundNotification
  135. object:nil];
  136. }
  137. [[NSNotificationCenter defaultCenter] addObserver:self
  138. selector:@selector(didRotate)
  139. name:UIDeviceOrientationDidChangeNotification
  140. object:nil];
  141. #endif
  142. //get country
  143. self.appStoreCountry = [(NSLocale *)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
  144. if ([self.appStoreCountry isEqualToString:@"150"])
  145. {
  146. self.appStoreCountry = @"eu";
  147. }
  148. else if ([[self.appStoreCountry stringByReplacingOccurrencesOfString:@"[A-Za-z]{2}" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, 2)] length])
  149. {
  150. self.appStoreCountry = @"us";
  151. }
  152. else if ([self.appStoreCountry isEqualToString:@"GI"])
  153. {
  154. self.appStoreCountry = @"GB";
  155. }
  156. //application version (use short version preferentially)
  157. self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
  158. if ([self.applicationVersion length] == 0)
  159. {
  160. self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey];
  161. }
  162. //bundle id
  163. self.applicationBundleID = [[NSBundle mainBundle] bundleIdentifier];
  164. //default settings
  165. self.updatePriority = iVersionUpdatePriorityDefault;
  166. self.useAllAvailableLanguages = YES;
  167. self.onlyPromptIfMainWindowIsAvailable = YES;
  168. self.checkAtLaunch = YES;
  169. self.checkPeriod = 0.0f;
  170. self.remindPeriod = 1.0f;
  171. #ifdef DEBUG
  172. //enable verbose logging in debug mode
  173. self.verboseLogging = YES;
  174. #endif
  175. //app launched
  176. [self performSelectorOnMainThread:@selector(applicationLaunched) withObject:nil waitUntilDone:NO];
  177. }
  178. return self;
  179. }
  180. - (id<iVersionDelegate>)delegate
  181. {
  182. if (_delegate == nil)
  183. {
  184. #if TARGET_OS_IPHONE
  185. #define APP_CLASS UIApplication
  186. #else
  187. #define APP_CLASS NSApplication
  188. #endif
  189. _delegate = (id<iVersionDelegate>)[[APP_CLASS sharedApplication] delegate];
  190. }
  191. return _delegate;
  192. }
  193. - (NSString *)inThisVersionTitle
  194. {
  195. return _inThisVersionTitle ?: [self localizedStringForKey:iVersionInThisVersionTitleKey withDefault:@"New in this version"];
  196. }
  197. - (NSString *)updateAvailableTitle
  198. {
  199. return _updateAvailableTitle ?: [self localizedStringForKey:iVersionUpdateAvailableTitleKey withDefault:@"New version available"];
  200. }
  201. - (NSString *)versionLabelFormat
  202. {
  203. return _versionLabelFormat ?: [self localizedStringForKey:iVersionVersionLabelFormatKey withDefault:@"Version %@"];
  204. }
  205. - (NSString *)okButtonLabel
  206. {
  207. return _okButtonLabel ?: [self localizedStringForKey:iVersionOKButtonKey withDefault:@"OK"];
  208. }
  209. - (NSString *)ignoreButtonLabel
  210. {
  211. return _ignoreButtonLabel ?: [self localizedStringForKey:iVersionIgnoreButtonKey withDefault:@"Ignore"];
  212. }
  213. - (NSString *)downloadButtonLabel
  214. {
  215. return _downloadButtonLabel ?: [self localizedStringForKey:iVersionDownloadButtonKey withDefault:@"Download"];
  216. }
  217. - (NSString *)remindButtonLabel
  218. {
  219. return _remindButtonLabel ?: [self localizedStringForKey:iVersionRemindButtonKey withDefault:@"Remind Me Later"];
  220. }
  221. - (NSURL *)updateURL
  222. {
  223. if (_updateURL)
  224. {
  225. return _updateURL;
  226. }
  227. if (!self.appStoreID)
  228. {
  229. 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.");
  230. }
  231. #if TARGET_OS_IPHONE
  232. return [NSURL URLWithString:[NSString stringWithFormat:iVersioniOSAppStoreURLFormat, @(self.appStoreID)]];
  233. #else
  234. return [NSURL URLWithString:[NSString stringWithFormat:iVersionMacAppStoreURLFormat, @(self.appStoreID)]];
  235. #endif
  236. }
  237. - (NSUInteger)appStoreID
  238. {
  239. return [[[NSUserDefaults standardUserDefaults] objectForKey:iVersionAppStoreIDKey] unsignedIntegerValue];
  240. }
  241. - (void)setAppStoreID:(NSUInteger)appStoreID
  242. {
  243. [[NSUserDefaults standardUserDefaults] setInteger:(NSInteger)appStoreID forKey:iVersionAppStoreIDKey];
  244. [[NSUserDefaults standardUserDefaults] synchronize];
  245. }
  246. - (NSDate *)lastChecked
  247. {
  248. return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastCheckedKey];
  249. }
  250. - (void)setLastChecked:(NSDate *)date
  251. {
  252. [[NSUserDefaults standardUserDefaults] setObject:date forKey:iVersionLastCheckedKey];
  253. [[NSUserDefaults standardUserDefaults] synchronize];
  254. }
  255. - (NSDate *)lastReminded
  256. {
  257. return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastRemindedKey];
  258. }
  259. - (void)setLastReminded:(NSDate *)date
  260. {
  261. [[NSUserDefaults standardUserDefaults] setObject:date forKey:iVersionLastRemindedKey];
  262. [[NSUserDefaults standardUserDefaults] synchronize];
  263. }
  264. - (NSString *)ignoredVersion
  265. {
  266. return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionIgnoreVersionKey];
  267. }
  268. - (void)setIgnoredVersion:(NSString *)version
  269. {
  270. [[NSUserDefaults standardUserDefaults] setObject:version forKey:iVersionIgnoreVersionKey];
  271. [[NSUserDefaults standardUserDefaults] synchronize];
  272. }
  273. - (BOOL)viewedVersionDetails
  274. {
  275. return [[[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastVersionKey] isEqualToString:self.applicationVersion];
  276. }
  277. - (void)setViewedVersionDetails:(BOOL)viewed
  278. {
  279. [[NSUserDefaults standardUserDefaults] setObject:(viewed? self.applicationVersion: nil) forKey:iVersionLastVersionKey];
  280. }
  281. - (void)dealloc
  282. {
  283. [[NSNotificationCenter defaultCenter] removeObserver:self];
  284. }
  285. - (NSString *)lastVersion
  286. {
  287. return [[NSUserDefaults standardUserDefaults] objectForKey:iVersionLastVersionKey];
  288. }
  289. - (void)setLastVersion:(NSString *)version
  290. {
  291. [[NSUserDefaults standardUserDefaults] setObject:version forKey:iVersionLastVersionKey];
  292. [[NSUserDefaults standardUserDefaults] synchronize];
  293. }
  294. - (NSDictionary *)localVersionsDict
  295. {
  296. static NSDictionary *versionsDict = nil;
  297. if (versionsDict == nil)
  298. {
  299. if (self.localVersionsPlistPath == nil)
  300. {
  301. versionsDict = [[NSDictionary alloc] init]; //empty dictionary
  302. }
  303. else
  304. {
  305. NSString *versionsFile = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:self.localVersionsPlistPath];
  306. versionsDict = [[NSDictionary alloc] initWithContentsOfFile:versionsFile];
  307. if (!versionsDict)
  308. {
  309. // Get the path to versions plist in localized directory
  310. NSArray *pathComponents = [self.localVersionsPlistPath componentsSeparatedByString:@"."];
  311. versionsFile = ([pathComponents count] == 2) ? [[NSBundle mainBundle] pathForResource:pathComponents[0] ofType:pathComponents[1]] : nil;
  312. versionsDict = [[NSDictionary alloc] initWithContentsOfFile:versionsFile];
  313. }
  314. }
  315. }
  316. return versionsDict;
  317. }
  318. - (NSString *)mostRecentVersionInDict:(NSDictionary *)dict
  319. {
  320. return [[[dict allKeys] sortedArrayUsingSelector:@selector(compareVersion:)] lastObject];
  321. }
  322. - (NSString *)versionDetails:(NSString *)version inDict:(NSDictionary *)dict
  323. {
  324. if (self.versionDetailsDefault && ([self.versionDetailsDefault length] > 0)) {
  325. return self.versionDetailsDefault;
  326. }
  327. id versionData = dict[version];
  328. if ([versionData isKindOfClass:[NSString class]])
  329. {
  330. return versionData;
  331. }
  332. else if ([versionData isKindOfClass:[NSArray class]])
  333. {
  334. return [versionData componentsJoinedByString:@"\n"];
  335. }
  336. return nil;
  337. }
  338. - (NSString *)versionDetailsSince:(NSString *)lastVersion inDict:(NSDictionary *)dict
  339. {
  340. if (self.versionDetailsDefault && ([self.versionDetailsDefault length] > 0)) {
  341. return self.versionDetailsDefault;
  342. }
  343. if (self.previewMode)
  344. {
  345. lastVersion = @"0";
  346. }
  347. BOOL newVersionFound = NO;
  348. NSMutableString *details = [NSMutableString string];
  349. NSArray *versions = [[dict allKeys] sortedArrayUsingSelector:@selector(compareVersionDescending:)];
  350. for (NSString *version in versions)
  351. {
  352. if ([version compareVersion:lastVersion] == NSOrderedDescending)
  353. {
  354. newVersionFound = YES;
  355. if (self.groupNotesByVersion)
  356. {
  357. [details appendString:[self.versionLabelFormat stringByReplacingOccurrencesOfString:@"%@" withString:version]];
  358. [details appendString:@"\n\n"];
  359. }
  360. [details appendString:[self versionDetails:version inDict:dict] ?: @""];
  361. [details appendString:@"\n"];
  362. if (self.groupNotesByVersion)
  363. {
  364. [details appendString:@"\n"];
  365. }
  366. }
  367. }
  368. return newVersionFound? [details stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]: nil;
  369. }
  370. - (NSString *)versionDetails
  371. {
  372. if (!_versionDetails)
  373. {
  374. if (self.viewedVersionDetails)
  375. {
  376. self.versionDetails = [self versionDetails:self.applicationVersion inDict:[self localVersionsDict]];
  377. }
  378. else
  379. {
  380. self.versionDetails = [self versionDetailsSince:self.lastVersion inDict:[self localVersionsDict]];
  381. }
  382. }
  383. return _versionDetails;
  384. }
  385. - (NSString *)URLEncodedString:(NSString *)string
  386. {
  387. CFStringRef stringRef = CFBridgingRetain(string);
  388. #pragma clang diagnostic push
  389. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  390. CFStringRef encoded = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
  391. stringRef,
  392. NULL,
  393. CFSTR("!*'\"();:@&=+$,/?%#[]% "),
  394. kCFStringEncodingUTF8);
  395. #pragma clang diagnostic pop
  396. CFRelease(stringRef);
  397. return CFBridgingRelease(encoded);
  398. }
  399. - (void)downloadedVersionsData
  400. {
  401. #if !TARGET_OS_IPHONE
  402. //only show when main window is available
  403. if (self.onlyPromptIfMainWindowIsAvailable && ![[NSApplication sharedApplication] mainWindow])
  404. {
  405. [self performSelector:@selector(downloadedVersionsData) withObject:nil afterDelay:0.5];
  406. return;
  407. }
  408. #endif
  409. if (self.checkingForNewVersion)
  410. {
  411. //no longer checking
  412. self.checkingForNewVersion = NO;
  413. //check if data downloaded
  414. if (!self.remoteVersionsDict)
  415. {
  416. //log the error
  417. if (self.downloadError)
  418. {
  419. NSLog(@"iVersion update check failed because: %@", [self.downloadError localizedDescription]);
  420. }
  421. else
  422. {
  423. NSLog(@"iVersion update check failed because an unknown error occured");
  424. }
  425. if ([self.delegate respondsToSelector:@selector(iVersionVersionCheckDidFailWithError:)])
  426. {
  427. [self.delegate iVersionVersionCheckDidFailWithError:self.downloadError];
  428. }
  429. //deprecated code path
  430. else if ([self.delegate respondsToSelector:@selector(iVersionVersionCheckFailed:)])
  431. {
  432. NSLog(@"iVersionVersionCheckFailed: delegate method is deprecated, use iVersionVersionCheckDidFailWithError: instead");
  433. [self.delegate performSelector:@selector(iVersionVersionCheckFailed:) withObject:self.downloadError];
  434. }
  435. return;
  436. }
  437. //get version details
  438. NSString *details = [self versionDetailsSince:self.applicationVersion inDict:self.remoteVersionsDict];
  439. NSString *mostRecentVersion = [self mostRecentVersionInDict:self.remoteVersionsDict];
  440. if (details)
  441. {
  442. //inform delegate of new version
  443. if ([self.delegate respondsToSelector:@selector(iVersionDidDetectNewVersion:details:)])
  444. {
  445. [self.delegate iVersionDidDetectNewVersion:mostRecentVersion details:details];
  446. }
  447. //deprecated code path
  448. else if ([self.delegate respondsToSelector:@selector(iVersionDetectedNewVersion:details:)])
  449. {
  450. NSLog(@"iVersionDetectedNewVersion:details: delegate method is deprecated, use iVersionDidDetectNewVersion:details: instead");
  451. [self.delegate performSelector:@selector(iVersionDetectedNewVersion:details:) withObject:mostRecentVersion withObject:details];
  452. }
  453. //check if ignored
  454. BOOL showDetails = ![self.ignoredVersion isEqualToString:mostRecentVersion] || self.previewMode;
  455. if (showDetails)
  456. {
  457. if ([self.delegate respondsToSelector:@selector(iVersionShouldDisplayNewVersion:details:)])
  458. {
  459. showDetails = [self.delegate iVersionShouldDisplayNewVersion:mostRecentVersion details:details];
  460. if (!showDetails && self.verboseLogging)
  461. {
  462. NSLog(@"iVersion did not display the new version because the iVersionShouldDisplayNewVersion:details: delegate method returned NO");
  463. }
  464. }
  465. }
  466. else if (self.verboseLogging)
  467. {
  468. NSLog(@"iVersion did not display the new version because it was marked as ignored");
  469. }
  470. //show details
  471. if (showDetails && !self.visibleRemoteAlert)
  472. {
  473. NSString *title = self.updateAvailableTitle;
  474. if (!self.groupNotesByVersion)
  475. {
  476. title = [title stringByAppendingFormat:@" (%@)", mostRecentVersion];
  477. }
  478. self.visibleRemoteAlert = [self showAlertWithTitle:title
  479. details:details
  480. defaultButton:self.downloadButtonLabel
  481. ignoreButton:[self showIgnoreButton]? self.ignoreButtonLabel: nil
  482. remindButton:[self showRemindButton]? self.remindButtonLabel: nil];
  483. }
  484. }
  485. else if ([self.delegate respondsToSelector:@selector(iVersionDidNotDetectNewVersion)])
  486. {
  487. [self.delegate iVersionDidNotDetectNewVersion];
  488. }
  489. }
  490. }
  491. - (BOOL)shouldCheckForNewVersion
  492. {
  493. //debug mode?
  494. if (!self.previewMode)
  495. {
  496. //check if within the reminder period
  497. if (self.lastReminded != nil)
  498. {
  499. //reminder takes priority over check period
  500. if ([[NSDate date] timeIntervalSinceDate:self.lastReminded] < self.remindPeriod * SECONDS_IN_A_DAY)
  501. {
  502. if (self.verboseLogging)
  503. {
  504. NSLog(@"iVersion did not check for a new version because the user last asked to be reminded less than %g days ago", self.remindPeriod);
  505. }
  506. return NO;
  507. }
  508. }
  509. //check if within the check period
  510. else if (self.lastChecked != nil && [[NSDate date] timeIntervalSinceDate:self.lastChecked] < self.checkPeriod * SECONDS_IN_A_DAY)
  511. {
  512. if (self.verboseLogging)
  513. {
  514. NSLog(@"iVersion did not check for a new version because the last check was less than %g days ago", self.checkPeriod);
  515. }
  516. return NO;
  517. }
  518. }
  519. else if (self.verboseLogging)
  520. {
  521. NSLog(@"iVersion debug mode is enabled - make sure you disable this for release");
  522. }
  523. //confirm with delegate
  524. if ([self.delegate respondsToSelector:@selector(iVersionShouldCheckForNewVersion)])
  525. {
  526. BOOL shouldCheck = [self.delegate iVersionShouldCheckForNewVersion];
  527. if (!shouldCheck && self.verboseLogging)
  528. {
  529. NSLog(@"iVersion did not check for a new version because the iVersionShouldCheckForNewVersion delegate method returned NO");
  530. }
  531. return shouldCheck;
  532. }
  533. //perform the check
  534. return YES;
  535. }
  536. - (NSString *)valueForKey:(NSString *)key inJSON:(id)json
  537. {
  538. if ([json isKindOfClass:[NSString class]])
  539. {
  540. //use legacy parser
  541. NSRange keyRange = [json rangeOfString:[NSString stringWithFormat:@"\"%@\"", key]];
  542. if (keyRange.location != NSNotFound)
  543. {
  544. NSInteger start = keyRange.location + keyRange.length;
  545. NSRange valueStart = [json rangeOfString:@":" options:(NSStringCompareOptions)0 range:NSMakeRange(start, [(NSString *)json length] - start)];
  546. if (valueStart.location != NSNotFound)
  547. {
  548. start = valueStart.location + 1;
  549. NSRange valueEnd = [json rangeOfString:@"," options:(NSStringCompareOptions)0 range:NSMakeRange(start, [(NSString *)json length] - start)];
  550. if (valueEnd.location != NSNotFound)
  551. {
  552. NSString *value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
  553. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  554. while ([value hasPrefix:@"\""] && ![value hasSuffix:@"\""])
  555. {
  556. if (valueEnd.location == NSNotFound)
  557. {
  558. break;
  559. }
  560. NSInteger newStart = valueEnd.location + 1;
  561. valueEnd = [json rangeOfString:@"," options:(NSStringCompareOptions)0 range:NSMakeRange(newStart, [(NSString *)json length] - newStart)];
  562. value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
  563. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  564. }
  565. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]];
  566. value = [value stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
  567. value = [value stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
  568. value = [value stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""];
  569. value = [value stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
  570. value = [value stringByReplacingOccurrencesOfString:@"\\r" withString:@"\r"];
  571. value = [value stringByReplacingOccurrencesOfString:@"\\t" withString:@"\t"];
  572. value = [value stringByReplacingOccurrencesOfString:@"\\f" withString:@"\f"];
  573. value = [value stringByReplacingOccurrencesOfString:@"\\b" withString:@"\f"];
  574. while (YES)
  575. {
  576. NSRange unicode = [value rangeOfString:@"\\u"];
  577. if (unicode.location == NSNotFound || unicode.location + unicode.length == 0)
  578. {
  579. break;
  580. }
  581. uint32_t c = 0;
  582. NSString *hex = [value substringWithRange:NSMakeRange(unicode.location + 2, 4)];
  583. NSScanner *scanner = [NSScanner scannerWithString:hex];
  584. [scanner scanHexInt:&c];
  585. if (c <= 0xffff)
  586. {
  587. value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C", (unichar)c]];
  588. }
  589. else
  590. {
  591. //convert character to surrogate pair
  592. uint16_t x = (uint16_t)c;
  593. uint16_t u = (c >> 16) & ((1 << 5) - 1);
  594. uint16_t w = (uint16_t)u - 1;
  595. unichar high = 0xd800 | (w << 6) | x >> 10;
  596. unichar low = (uint16_t)(0xdc00 | (x & ((1 << 10) - 1)));
  597. value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C%C", high, low]];
  598. }
  599. }
  600. return value;
  601. }
  602. }
  603. }
  604. }
  605. else
  606. {
  607. return json[key];
  608. }
  609. return nil;
  610. }
  611. - (void)setAppStoreIDOnMainThread:(NSString *)appStoreIDString
  612. {
  613. self.appStoreID = [appStoreIDString longLongValue];
  614. }
  615. - (void)checkForNewVersionInBackground
  616. {
  617. @synchronized (self)
  618. {
  619. @autoreleasepool
  620. {
  621. BOOL newerVersionAvailable = NO;
  622. BOOL osVersionSupported = NO;
  623. NSString *latestVersion = nil;
  624. NSDictionary *versions = nil;
  625. //first check iTunes
  626. NSString *iTunesServiceURL = [NSString stringWithFormat:iVersionAppLookupURLFormat, self.appStoreCountry];
  627. if (self.appStoreID)
  628. {
  629. iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?id=%@", @(self.appStoreID)];
  630. }
  631. else
  632. {
  633. iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?bundleId=%@", self.applicationBundleID];
  634. }
  635. if (self.verboseLogging)
  636. {
  637. NSLog(@"iVersion is checking %@ for a new app version...", iTunesServiceURL);
  638. }
  639. NSError *error = nil;
  640. NSURLResponse *response = nil;
  641. NSURL *url = [NSURL URLWithString:iTunesServiceURL];
  642. NSURLRequest *request = [NSURLRequest requestWithURL:url
  643. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  644. timeoutInterval:REQUEST_TIMEOUT];
  645. #pragma clang diagnostic push
  646. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  647. NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
  648. #pragma clang diagnostic pop
  649. NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
  650. if (data && statusCode == 200)
  651. {
  652. //in case error is garbage...
  653. error = nil;
  654. id json = nil;
  655. if ([NSJSONSerialization class])
  656. {
  657. json = [[NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)0 error:&error][@"results"] lastObject];
  658. }
  659. else
  660. {
  661. //convert to string
  662. json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  663. }
  664. if (!error)
  665. {
  666. //check bundle ID matches
  667. NSString *bundleID = [self valueForKey:@"bundleId" inJSON:json];
  668. if (bundleID)
  669. {
  670. if ([bundleID isEqualToString:self.applicationBundleID])
  671. {
  672. //get supported OS version
  673. NSString *minimumSupportedOSVersion = [self valueForKey:@"minimumOsVersion" inJSON:json];
  674. #if TARGET_OS_IPHONE
  675. NSString *systemVersion = [UIDevice currentDevice].systemVersion;
  676. #else
  677. NSString *systemVersion = nil;
  678. #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1100
  679. if ([[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)])
  680. {
  681. NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
  682. systemVersion = [NSString stringWithFormat:@"%zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion];
  683. }
  684. else
  685. #endif
  686. {
  687. #pragma clang diagnostic push
  688. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  689. SInt32 majorVersion = 0, minorVersion = 0, patchVersion = 0;
  690. Gestalt(gestaltSystemVersionMajor, &majorVersion);
  691. Gestalt(gestaltSystemVersionMinor, &minorVersion);
  692. Gestalt(gestaltSystemVersionBugFix, &patchVersion);
  693. systemVersion = [NSString stringWithFormat:@"%d.%d.%d", majorVersion, minorVersion, patchVersion];
  694. #pragma clang diagnostic pop
  695. }
  696. #endif
  697. osVersionSupported = ([systemVersion compare:minimumSupportedOSVersion options:NSNumericSearch] != NSOrderedAscending);
  698. if (!osVersionSupported)
  699. {
  700. error = [NSError errorWithDomain:iVersionErrorDomain
  701. code:iVersionErrorOSVersionNotSupported
  702. userInfo:@{NSLocalizedDescriptionKey: @"Current OS version is not supported."}];
  703. }
  704. //get version details
  705. NSString *releaseNotes = [self valueForKey:@"releaseNotes" inJSON:json];
  706. latestVersion = [self valueForKey:@"version" inJSON:json];
  707. if (latestVersion && osVersionSupported)
  708. {
  709. versions = @{latestVersion: releaseNotes ?: @""};
  710. }
  711. //get app id
  712. if (!self.appStoreID)
  713. {
  714. NSString *appStoreIDString = [self valueForKey:@"trackId" inJSON:json];
  715. [self performSelectorOnMainThread:@selector(setAppStoreIDOnMainThread:) withObject:appStoreIDString waitUntilDone:YES];
  716. if (self.verboseLogging)
  717. {
  718. NSLog(@"iVersion found the app on iTunes. The App Store ID is %@", appStoreIDString);
  719. }
  720. }
  721. //check for new version
  722. newerVersionAvailable = ([latestVersion compareVersion:self.applicationVersion] == NSOrderedDescending);
  723. if (self.verboseLogging)
  724. {
  725. if (newerVersionAvailable)
  726. {
  727. NSLog(@"iVersion found a new version (%@) of the app on iTunes. Current version is %@", latestVersion, self.applicationVersion);
  728. }
  729. else
  730. {
  731. NSLog(@"iVersion did not find a new version of the app on iTunes. Current version is %@, latest version is %@", self.applicationVersion, latestVersion);
  732. }
  733. }
  734. }
  735. else
  736. {
  737. if (self.verboseLogging)
  738. {
  739. 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));
  740. }
  741. error = [NSError errorWithDomain:iVersionErrorDomain
  742. code:iVersionErrorBundleIdDoesNotMatchAppStore
  743. userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Application bundle ID does not match expected value of %@", bundleID]}];
  744. }
  745. }
  746. else if (self.appStoreID || !self.remoteVersionsPlistURL)
  747. {
  748. if (self.verboseLogging)
  749. {
  750. 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");
  751. }
  752. error = [NSError errorWithDomain:iVersionErrorDomain
  753. code:iVersionErrorApplicationNotFoundOnAppStore
  754. userInfo:@{NSLocalizedDescriptionKey: @"The application could not be found on the App Store."}];
  755. }
  756. else if (!self.appStoreID && self.verboseLogging)
  757. {
  758. 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");
  759. }
  760. //now check plist for alternative release notes
  761. if (((self.appStoreID && newerVersionAvailable && osVersionSupported) || !self.appStoreID || self.previewMode) && self.remoteVersionsPlistURL)
  762. {
  763. if (self.verboseLogging)
  764. {
  765. NSLog(@"iVersion will check %@ for %@", self.remoteVersionsPlistURL, self.appStoreID? @"release notes": @"a new app version");
  766. }
  767. NSURL *url = [NSURL URLWithString:self.remoteVersionsPlistURL];
  768. NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:REQUEST_TIMEOUT];
  769. #pragma clang diagnostic push
  770. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  771. NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
  772. #pragma clang diagnostic pop
  773. if (data)
  774. {
  775. NSPropertyListFormat format;
  776. NSDictionary *plistVersions = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:&format error:&error];
  777. if (latestVersion)
  778. {
  779. //remove versions that are greater than latest in app store
  780. NSMutableDictionary *versions = [NSMutableDictionary dictionary];
  781. for (NSString *version in plistVersions)
  782. {
  783. if ([version compareVersion:latestVersion] != NSOrderedDescending)
  784. {
  785. versions[version] = plistVersions[version];
  786. }
  787. }
  788. plistVersions = versions;
  789. }
  790. if (!latestVersion || plistVersions[latestVersion] || !_useAppStoreDetailsIfNoPlistEntryFound)
  791. {
  792. versions = [plistVersions copy];
  793. }
  794. }
  795. else if (self.verboseLogging)
  796. {
  797. NSLog(@"iVersion was unable to download the user-specified release notes");
  798. }
  799. }
  800. }
  801. else if (statusCode >= 400)
  802. {
  803. //http error
  804. NSString *message = [NSString stringWithFormat:@"The server returned a %@ error", @(statusCode)];
  805. error = [NSError errorWithDomain:@"HTTPResponseErrorDomain" code:statusCode userInfo:@{NSLocalizedDescriptionKey: message}];
  806. }
  807. }
  808. [self performSelectorOnMainThread:@selector(setDownloadError:) withObject:error waitUntilDone:YES];
  809. [self performSelectorOnMainThread:@selector(setRemoteVersionsDict:) withObject:versions waitUntilDone:YES];
  810. [self performSelectorOnMainThread:@selector(setLastChecked:) withObject:[NSDate date] waitUntilDone:YES];
  811. [self performSelectorOnMainThread:@selector(downloadedVersionsData) withObject:nil waitUntilDone:YES];
  812. }
  813. }
  814. }
  815. - (void)checkForNewVersion
  816. {
  817. if (!self.checkingForNewVersion)
  818. {
  819. self.checkingForNewVersion = YES;
  820. [self performSelectorInBackground:@selector(checkForNewVersionInBackground) withObject:nil];
  821. }
  822. }
  823. - (void)checkIfNewVersion
  824. {
  825. #if !TARGET_OS_IPHONE
  826. //only show when main window is available
  827. if (self.onlyPromptIfMainWindowIsAvailable && ![[NSApplication sharedApplication] mainWindow])
  828. {
  829. [self performSelector:@selector(checkIfNewVersion) withObject:nil afterDelay:0.5];
  830. return;
  831. }
  832. #endif
  833. if (self.lastVersion != nil || self.showOnFirstLaunch || self.previewMode)
  834. {
  835. if ([self.applicationVersion compareVersion:self.lastVersion] == NSOrderedDescending || self.previewMode)
  836. {
  837. //clear reminder
  838. self.lastReminded = nil;
  839. //get version details
  840. BOOL showDetails = !!self.versionDetails;
  841. if (showDetails && [self.delegate respondsToSelector:@selector(iVersionShouldDisplayCurrentVersionDetails:)])
  842. {
  843. showDetails = [self.delegate iVersionShouldDisplayCurrentVersionDetails:self.versionDetails];
  844. }
  845. //show details
  846. if (showDetails && !self.visibleLocalAlert && !self.visibleRemoteAlert)
  847. {
  848. self.visibleLocalAlert = [self showAlertWithTitle:self.inThisVersionTitle
  849. details:self.versionDetails
  850. defaultButton:self.okButtonLabel
  851. ignoreButton:nil
  852. remindButton:nil];
  853. }
  854. }
  855. }
  856. else
  857. {
  858. //record this as last viewed release
  859. self.viewedVersionDetails = YES;
  860. }
  861. }
  862. - (BOOL)showIgnoreButton
  863. {
  864. return [self.ignoreButtonLabel length] && self.updatePriority < iVersionUpdatePriorityMedium;
  865. }
  866. - (BOOL)showRemindButton
  867. {
  868. return [self.remindButtonLabel length] && self.updatePriority < iVersionUpdatePriorityHigh;
  869. }
  870. - (id)showAlertWithTitle:(NSString *)title
  871. details:(NSString *)details
  872. defaultButton:(NSString *)defaultButton
  873. ignoreButton:(NSString *)ignoreButton
  874. remindButton:(NSString *)remindButton
  875. {
  876. #if TARGET_OS_IPHONE
  877. UIViewController *topController = [UIApplication sharedApplication].delegate.window.rootViewController;
  878. while (topController.presentedViewController)
  879. {
  880. topController = topController.presentedViewController;
  881. }
  882. if ([UIAlertController class] && topController && self.useUIAlertControllerIfAvailable)
  883. {
  884. UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:details preferredStyle:UIAlertControllerStyleAlert];
  885. //download/ok action
  886. [alert addAction:[UIAlertAction actionWithTitle:self.downloadButtonLabel style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
  887. [self didDismissAlert:alert withButtonAtIndex:0];
  888. }]];
  889. //ignore action
  890. if ([self showIgnoreButton])
  891. {
  892. [alert addAction:[UIAlertAction actionWithTitle:self.ignoreButtonLabel style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction *action) {
  893. [self didDismissAlert:alert withButtonAtIndex:1];
  894. }]];
  895. }
  896. //remind action
  897. if ([self showRemindButton])
  898. {
  899. [alert addAction:[UIAlertAction actionWithTitle:self.remindButtonLabel style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
  900. [self didDismissAlert:alert withButtonAtIndex:[self showIgnoreButton]? 2: 1];
  901. }]];
  902. }
  903. //get current view controller and present alert
  904. [topController presentViewController:alert animated:YES completion:NULL];
  905. return alert;
  906. }
  907. else
  908. {
  909. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
  910. message:details
  911. delegate:(id<UIAlertViewDelegate>)self
  912. cancelButtonTitle:nil
  913. otherButtonTitles:defaultButton, nil];
  914. if (ignoreButton)
  915. {
  916. [alert addButtonWithTitle:ignoreButton];
  917. alert.cancelButtonIndex = 1;
  918. }
  919. if (remindButton)
  920. {
  921. [alert addButtonWithTitle:remindButton];
  922. }
  923. [alert show];
  924. return alert;
  925. }
  926. #else
  927. NSAlert *alert = [[NSAlert alloc] init];
  928. alert.messageText = title;
  929. alert.informativeText = self.inThisVersionTitle;
  930. [alert addButtonWithTitle:defaultButton];
  931. NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 380.0, 15.0)];
  932. NSSize contentSize = [scrollview contentSize];
  933. scrollview.borderType = NSBezelBorder;
  934. scrollview.hasVerticalScroller = YES;
  935. scrollview.hasHorizontalScroller = NO;
  936. scrollview.autoresizingMask = (NSAutoresizingMaskOptions)(NSViewWidthSizable|NSViewHeightSizable);
  937. NSTextView *textView = [[NSTextView alloc] initWithFrame:NSMakeRect(0.0, 0.0, contentSize.width, contentSize.height)];
  938. textView.minSize = NSMakeSize(0.0, contentSize.height);
  939. textView.maxSize = NSMakeSize(FLT_MAX, FLT_MAX);
  940. textView.verticallyResizable = YES;
  941. textView.horizontallyResizable = NO;
  942. textView.autoresizingMask = NSViewWidthSizable;
  943. textView.textContainer.containerSize = NSMakeSize(contentSize.width, FLT_MAX);
  944. textView.textContainer.widthTracksTextView = YES;
  945. textView.string = details;
  946. textView.editable = NO;
  947. scrollview.documentView = textView;
  948. [textView sizeToFit];
  949. CGFloat height = MIN(200.0, [[scrollview documentView] frame].size.height) + 3.0;
  950. scrollview.frame = NSMakeRect(0.0, 0.0, scrollview.frame.size.width, height);
  951. alert.accessoryView = scrollview;
  952. if (ignoreButton)
  953. {
  954. [alert addButtonWithTitle:ignoreButton];
  955. }
  956. if (remindButton)
  957. {
  958. [alert addButtonWithTitle:remindButton];
  959. }
  960. #if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9
  961. if (![alert respondsToSelector:@selector(beginSheetModalForWindow:completionHandler:)])
  962. {
  963. [alert beginSheetModalForWindow:(id __nonnull)[NSApplication sharedApplication].mainWindow
  964. modalDelegate:self
  965. didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
  966. contextInfo:nil];
  967. }
  968. else
  969. #endif
  970. {
  971. [alert beginSheetModalForWindow:(id __nonnull)[NSApplication sharedApplication].mainWindow completionHandler:^(NSModalResponse returnCode) {
  972. [self didDismissAlert:alert withButtonAtIndex:returnCode - NSAlertFirstButtonReturn];
  973. }];
  974. }
  975. return alert;
  976. #endif
  977. }
  978. - (void)didDismissAlert:(id)alertView withButtonAtIndex:(NSInteger)buttonIndex
  979. {
  980. //get button indices
  981. NSInteger downloadButtonIndex = 0;
  982. NSInteger ignoreButtonIndex = [self showIgnoreButton]? 1: 0;
  983. NSInteger remindButtonIndex = [self showRemindButton]? ignoreButtonIndex + 1: 0;
  984. //latest version
  985. NSString *latestVersion = [self mostRecentVersionInDict:self.remoteVersionsDict];
  986. if (alertView == self.visibleLocalAlert)
  987. {
  988. //record that details have been viewed
  989. self.viewedVersionDetails = YES;
  990. //release alert
  991. self.visibleLocalAlert = nil;
  992. return;
  993. }
  994. if (buttonIndex == downloadButtonIndex)
  995. {
  996. //clear reminder
  997. self.lastReminded = nil;
  998. //log event
  999. if ([self.delegate respondsToSelector:@selector(iVersionUserDidAttemptToDownloadUpdate:)])
  1000. {
  1001. [self.delegate iVersionUserDidAttemptToDownloadUpdate:latestVersion];
  1002. }
  1003. if (![self.delegate respondsToSelector:@selector(iVersionShouldOpenAppStore)] ||
  1004. [self.delegate iVersionShouldOpenAppStore])
  1005. {
  1006. //go to download page
  1007. [self openAppPageInAppStore];
  1008. }
  1009. }
  1010. else if (buttonIndex == ignoreButtonIndex)
  1011. {
  1012. //ignore this version
  1013. self.ignoredVersion = latestVersion;
  1014. self.lastReminded = nil;
  1015. //log event
  1016. if ([self.delegate respondsToSelector:@selector(iVersionUserDidIgnoreUpdate:)])
  1017. {
  1018. [self.delegate iVersionUserDidIgnoreUpdate:latestVersion];
  1019. }
  1020. }
  1021. else if (buttonIndex == remindButtonIndex)
  1022. {
  1023. //remind later
  1024. self.lastReminded = [NSDate date];
  1025. //log event
  1026. if ([self.delegate respondsToSelector:@selector(iVersionUserDidRequestReminderForUpdate:)])
  1027. {
  1028. [self.delegate iVersionUserDidRequestReminderForUpdate:latestVersion];
  1029. }
  1030. }
  1031. //release alert
  1032. self.visibleRemoteAlert = nil;
  1033. }
  1034. #if TARGET_OS_IPHONE
  1035. - (BOOL)openAppPageInAppStore
  1036. {
  1037. if (!_updateURL && !self.appStoreID)
  1038. {
  1039. if (self.verboseLogging)
  1040. {
  1041. NSLog(@"iVersion was unable to open the App Store because the app store ID is not set.");
  1042. }
  1043. return NO;
  1044. }
  1045. #if defined(IVERSION_USE_STOREKIT) && IVERSION_USE_STOREKIT
  1046. if (!_updateURL && [SKStoreProductViewController class])
  1047. {
  1048. if (self.verboseLogging)
  1049. {
  1050. NSLog(@"iVersion will attempt to open the StoreKit in-app product page using the following app store ID: %@", @(self.appStoreID));
  1051. }
  1052. //create store view controller
  1053. SKStoreProductViewController *productController = [[SKStoreProductViewController alloc] init];
  1054. productController.delegate = (id<SKStoreProductViewControllerDelegate>)self;
  1055. //load product details
  1056. NSDictionary *productParameters = @{SKStoreProductParameterITunesItemIdentifier: [@(self.appStoreID) description]};
  1057. [productController loadProductWithParameters:productParameters completionBlock:NULL];
  1058. //get root view controller
  1059. UIWindow *window = [[UIApplication sharedApplication] delegate].window;
  1060. UIViewController *rootViewController = window.rootViewController;
  1061. if (!rootViewController)
  1062. {
  1063. if (self.verboseLogging)
  1064. {
  1065. NSLog(@"iVersion couldn't find a root view controller from which to display StoreKit product page");
  1066. }
  1067. }
  1068. else
  1069. {
  1070. while (rootViewController.presentedViewController)
  1071. {
  1072. rootViewController = rootViewController.presentedViewController;
  1073. }
  1074. //present product view controller
  1075. [rootViewController presentViewController:productController animated:YES completion:nil];
  1076. if ([self.delegate respondsToSelector:@selector(iVersionDidPresentStoreKitModal)])
  1077. {
  1078. [self.delegate iVersionDidPresentStoreKitModal];
  1079. }
  1080. return YES;
  1081. }
  1082. }
  1083. #endif
  1084. if (self.verboseLogging)
  1085. {
  1086. NSLog(@"iVersion will open the App Store using the following URL: %@", self.updateURL);
  1087. }
  1088. [[UIApplication sharedApplication] openURL:self.updateURL];
  1089. return YES;
  1090. }
  1091. - (void)productViewControllerDidFinish:(UIViewController *)controller
  1092. {
  1093. [controller.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
  1094. if ([self.delegate respondsToSelector:@selector(iVersionDidDismissStoreKitModal)])
  1095. {
  1096. [self.delegate iVersionDidDismissStoreKitModal];
  1097. }
  1098. }
  1099. - (void)resizeAlertView:(UIAlertView *)alertView
  1100. {
  1101. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone &&
  1102. UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation) &&
  1103. [[UIDevice currentDevice].systemVersion floatValue] < 7.0f)
  1104. {
  1105. CGFloat max = alertView.window.bounds.size.height - alertView.frame.size.height - 10.0f;
  1106. CGFloat offset = 0.0f;
  1107. for (UIView *view in alertView.subviews)
  1108. {
  1109. CGRect frame = view.frame;
  1110. if ([view isKindOfClass:[UILabel class]])
  1111. {
  1112. UILabel *label = (UILabel *)view;
  1113. if ([label.text isEqual:alertView.message])
  1114. {
  1115. label.lineBreakMode = NSLineBreakByWordWrapping;
  1116. label.numberOfLines = 0;
  1117. label.alpha = 1.0f;
  1118. [label sizeToFit];
  1119. offset = label.frame.size.height - frame.size.height;
  1120. frame.size.height = label.frame.size.height;
  1121. if (offset > max)
  1122. {
  1123. frame.size.height -= (offset - max);
  1124. offset = max;
  1125. }
  1126. if (offset > max - 10.0f)
  1127. {
  1128. frame.size.height -= (offset - max - 10);
  1129. frame.origin.y += (offset - max - 10) / 2.0f;
  1130. }
  1131. }
  1132. }
  1133. else if ([view isKindOfClass:[UITextView class]])
  1134. {
  1135. view.alpha = 0.0f;
  1136. }
  1137. else if ([view isKindOfClass:[UIControl class]])
  1138. {
  1139. frame.origin.y += offset;
  1140. }
  1141. view.frame = frame;
  1142. }
  1143. CGRect frame = alertView.frame;
  1144. frame.origin.y -= roundf(offset/2.0f);
  1145. frame.size.height += offset;
  1146. alertView.frame = frame;
  1147. }
  1148. }
  1149. - (void)didRotate
  1150. {
  1151. [self performSelectorOnMainThread:@selector(resizeAlertView:) withObject:self.visibleLocalAlert waitUntilDone:NO];
  1152. [self performSelectorOnMainThread:@selector(resizeAlertView:) withObject:self.visibleRemoteAlert waitUntilDone:NO];
  1153. }
  1154. - (void)willPresentAlertView:(UIAlertView *)alertView
  1155. {
  1156. [self resizeAlertView:alertView];
  1157. }
  1158. - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
  1159. {
  1160. [self didDismissAlert:alertView withButtonAtIndex:buttonIndex];
  1161. }
  1162. #else
  1163. - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(__unused void *)contextInfo
  1164. {
  1165. [self didDismissAlert:alert withButtonAtIndex:returnCode - NSAlertFirstButtonReturn];
  1166. }
  1167. - (void)openAppPageWhenAppStoreLaunched
  1168. {
  1169. //check if app store is running
  1170. for (NSRunningApplication *app in [[NSWorkspace sharedWorkspace] runningApplications])
  1171. {
  1172. if ([app.bundleIdentifier isEqualToString:iVersionMacAppStoreBundleID])
  1173. {
  1174. //open app page
  1175. [[NSWorkspace sharedWorkspace] performSelector:@selector(openURL:) withObject:self.updateURL afterDelay:MAC_APP_STORE_REFRESH_DELAY];
  1176. return;
  1177. }
  1178. }
  1179. //try again
  1180. [self performSelector:@selector(openAppPageWhenAppStoreLaunched) withObject:nil afterDelay:0.0];
  1181. }
  1182. - (BOOL)openAppPageInAppStore
  1183. {
  1184. if (!_updateURL && !self.appStoreID)
  1185. {
  1186. if (self.verboseLogging)
  1187. {
  1188. NSLog(@"iVersion was unable to open the App Store because the app store ID is not set.");
  1189. }
  1190. return NO;
  1191. }
  1192. if (self.verboseLogging)
  1193. {
  1194. NSLog(@"iVersion will open the App Store using the following URL: %@", self.updateURL);
  1195. }
  1196. [[NSWorkspace sharedWorkspace] openURL:self.updateURL];
  1197. if (!_updateURL) [self openAppPageWhenAppStoreLaunched];
  1198. return YES;
  1199. }
  1200. #endif
  1201. - (void)applicationLaunched
  1202. {
  1203. #if VERSION_DMG
  1204. return;
  1205. #endif
  1206. if (self.checkAtLaunch)
  1207. {
  1208. [self checkIfNewVersion];
  1209. if ([self shouldCheckForNewVersion]) {
  1210. [self checkForNewVersion];
  1211. }
  1212. }
  1213. else if (self.verboseLogging)
  1214. {
  1215. NSLog(@"iVersion will not check for updates because the checkAtLaunch option is disabled");
  1216. }
  1217. }
  1218. #if TARGET_OS_IPHONE
  1219. - (void)applicationWillEnterForeground
  1220. {
  1221. if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
  1222. {
  1223. if (self.checkAtLaunch)
  1224. {
  1225. if ([self shouldCheckForNewVersion]) [self checkForNewVersion];
  1226. }
  1227. else if (self.verboseLogging)
  1228. {
  1229. NSLog(@"iVersion will not check for updates because the checkAtLaunch option is disabled");
  1230. }
  1231. }
  1232. }
  1233. #endif
  1234. @end