iRate.m 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. //
  2. // iRate.m
  3. //
  4. // Version 1.7.4
  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/iRate
  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 "iRate.h"
  33. #if !VERSION_DMG
  34. #import <StoreKit/StoreKit.h>
  35. #endif
  36. #import <Availability.h>
  37. //#if !__has_feature(objc_arc)
  38. //#error This class requires automatic reference counting
  39. //#endif
  40. NSUInteger const iRateAppStoreGameGenreID = 6014;
  41. NSString *const iRateErrorDomain = @"iRateErrorDomain";
  42. static NSString *const iRateRatedVersionKey = @"iRateRatedVersionChecked";
  43. static NSString *const iRateDeclinedVersionKey = @"iRateDeclinedVersion";
  44. static NSString *const iRateLastRemindedKey = @"iRateLastReminded";
  45. static NSString *const iRateLastVersionUsedKey = @"iRateLastVersionUsed";
  46. static NSString *const iRateFirstUsedKey = @"iRateFirstUsed";
  47. static NSString *const iRateUseCountKey = @"iRateUseCount";
  48. static NSString *const iRateEventCountKey = @"iRateEventCount";
  49. static NSString *const iRateDeclinedCountKey = @"iRateDeclinedCount";
  50. static NSString *const iRateMacAppStoreBundleID = @"com.apple.appstore";
  51. static NSString *const iRateAppLookupURLFormat = @"http://itunes.apple.com/%@/lookup";
  52. static NSString *const iRateiOSAppStoreURLFormat = @"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=%u";
  53. static NSString *const iRateMacAppStoreURLFormat = @"macappstore://itunes.apple.com/app/id%u";
  54. #define SECONDS_IN_A_DAY 86400.0
  55. #define SECONDS_IN_A_WEEK 604800.0
  56. #define MAC_APP_STORE_REFRESH_DELAY 5.0
  57. #define REQUEST_TIMEOUT 60.0
  58. @interface iRate()
  59. @property (nonatomic, strong) id visibleAlert;
  60. @property (nonatomic, assign) int previousOrientation;
  61. @property (nonatomic, assign) BOOL currentlyChecking;
  62. @end
  63. @implementation iRate
  64. #pragma mark -
  65. #pragma mark Lifecycle methods
  66. + (void)load
  67. {
  68. [self performSelectorOnMainThread:@selector(sharedInstance) withObject:nil waitUntilDone:NO];
  69. }
  70. + (iRate *)sharedInstance
  71. {
  72. static iRate *sharedInstance = nil;
  73. if (sharedInstance == nil)
  74. {
  75. sharedInstance = [[iRate alloc] init];
  76. }
  77. return sharedInstance;
  78. }
  79. - (NSString *)localizedStringForKey:(NSString *)key withDefault:(NSString *)defaultString
  80. {
  81. static NSBundle *bundle = nil;
  82. if (bundle == nil)
  83. {
  84. NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"iRate" ofType:@"bundle"];
  85. bundle = [NSBundle bundleWithPath:bundlePath] ?: [NSBundle mainBundle];
  86. if (self.useAllAvailableLanguages)
  87. {
  88. //manually select the desired lproj folder
  89. for (NSString *language in [NSLocale preferredLanguages])
  90. {
  91. if ([[bundle localizations] containsObject:language])
  92. {
  93. bundlePath = [bundle pathForResource:language ofType:@"lproj"];
  94. bundle = [NSBundle bundleWithPath:bundlePath];
  95. break;
  96. }
  97. }
  98. }
  99. }
  100. defaultString = [bundle localizedStringForKey:key value:defaultString table:nil];
  101. return [[NSBundle mainBundle] localizedStringForKey:key value:defaultString table:nil];
  102. }
  103. - (iRate *)init
  104. {
  105. if ((self = [super init]))
  106. {
  107. #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
  108. //register for iphone application events
  109. if (&UIApplicationWillEnterForegroundNotification)
  110. {
  111. [[NSNotificationCenter defaultCenter] addObserver:self
  112. selector:@selector(applicationWillEnterForeground:)
  113. name:UIApplicationWillEnterForegroundNotification
  114. object:nil];
  115. }
  116. self.previousOrientation = [UIApplication sharedApplication].statusBarOrientation;
  117. [[NSNotificationCenter defaultCenter] addObserver:self
  118. selector:@selector(willRotate)
  119. name:UIDeviceOrientationDidChangeNotification
  120. object:nil];
  121. #endif
  122. //get country
  123. self.appStoreCountry = [(NSLocale *)[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
  124. //application version (use short version preferentially)
  125. self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
  126. if ([self.applicationVersion length] == 0)
  127. {
  128. self.applicationVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
  129. }
  130. //localised application name
  131. self.applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
  132. if ([self.applicationName length] == 0)
  133. {
  134. self.applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey];
  135. }
  136. //bundle id
  137. self.applicationBundleID = [[NSBundle mainBundle] bundleIdentifier];
  138. //default settings
  139. self.useAllAvailableLanguages = YES;
  140. self.disableAlertViewResizing = NO;
  141. self.onlyPromptIfLatestVersion = YES;
  142. self.onlyPromptIfMainWindowIsAvailable = YES;
  143. self.displayAppUsingStorekitIfAvailable = YES;
  144. self.promptAgainForEachNewVersion = YES;
  145. self.promptAtLaunch = NO;
  146. self.usesUntilPrompt = 3;
  147. self.eventsUntilPrompt = -1;
  148. self.daysUntilPrompt = 4.0f;
  149. self.usesPerWeekForPrompt = 0.0f;
  150. self.remindPeriod = 7.0f;
  151. self.verboseLogging = NO;
  152. self.previewMode = NO;
  153. #ifdef DEBUG
  154. //enable verbose logging in debug mode
  155. self.verboseLogging = YES;
  156. #endif
  157. //app launched
  158. [self performSelectorOnMainThread:@selector(applicationLaunched) withObject:nil waitUntilDone:NO];
  159. }
  160. return self;
  161. }
  162. - (id<iRateDelegate>)delegate
  163. {
  164. if (_delegate == nil)
  165. {
  166. #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
  167. _delegate = (id<iRateDelegate>)[[UIApplication sharedApplication] delegate];
  168. #else
  169. _delegate = (id<iRateDelegate>)[[NSApplication sharedApplication] delegate];
  170. #endif
  171. }
  172. return _delegate;
  173. }
  174. - (NSString *)messageTitle
  175. {
  176. return [_messageTitle ?: [self localizedStringForKey:iRateMessageTitleKey withDefault:NSLocalizedString(@"Rate %@", nil)] stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
  177. }
  178. - (NSString *)message
  179. {
  180. NSString *message = _message;
  181. if (!message)
  182. {
  183. NSString *defaultMessage = [NSString stringWithFormat:NSLocalizedString(@"Share your love to %@! Please take one minute to give us a great review on the App Store.", nil),self.applicationName];
  184. message = (self.appStoreGenreID == iRateAppStoreGameGenreID)? [self localizedStringForKey:iRateGameMessageKey withDefault:defaultMessage]: [self localizedStringForKey:iRateAppMessageKey withDefault:defaultMessage];
  185. }
  186. return [message stringByReplacingOccurrencesOfString:@"%@" withString:self.applicationName];
  187. }
  188. - (NSString *)cancelButtonLabel
  189. {
  190. return _cancelButtonLabel ?: [self localizedStringForKey:iRateCancelButtonKey withDefault:NSLocalizedString(@"Give a suggestion", nil)];
  191. }
  192. - (NSString *)rateButtonLabel
  193. {
  194. return _rateButtonLabel ?: [self localizedStringForKey:iRateRateButtonKey withDefault:NSLocalizedString(@"Give a 5-Star", nil)];
  195. }
  196. - (NSString *)remindButtonLabel
  197. {
  198. return _remindButtonLabel ?: [self localizedStringForKey:iRateRemindButtonKey withDefault:NSLocalizedString(@"Remind me later", nil)];
  199. }
  200. - (NSURL *)ratingsURL
  201. {
  202. if (_ratingsURL)
  203. {
  204. return _ratingsURL;
  205. }
  206. if (!self.appStoreID)
  207. {
  208. NSLog(@"iRate could not find the App Store ID for this application. If the application is not intended for App Store release then you must specify a custom ratingsURL.");
  209. }
  210. #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
  211. return [NSURL URLWithString:[NSString stringWithFormat:iRateiOSAppStoreURLFormat, (unsigned int)self.appStoreID]];
  212. #else
  213. return [NSURL URLWithString:[NSString stringWithFormat:iRateMacAppStoreURLFormat, (unsigned int)self.appStoreID]];
  214. #endif
  215. }
  216. - (NSDate *)firstUsed
  217. {
  218. return [[NSUserDefaults standardUserDefaults] objectForKey:iRateFirstUsedKey];
  219. }
  220. - (void)setFirstUsed:(NSDate *)date
  221. {
  222. [[NSUserDefaults standardUserDefaults] setObject:date forKey:iRateFirstUsedKey];
  223. [[NSUserDefaults standardUserDefaults] synchronize];
  224. }
  225. - (NSDate *)lastReminded
  226. {
  227. return [[NSUserDefaults standardUserDefaults] objectForKey:iRateLastRemindedKey];
  228. }
  229. - (void)setLastReminded:(NSDate *)date
  230. {
  231. [[NSUserDefaults standardUserDefaults] setObject:date forKey:iRateLastRemindedKey];
  232. [[NSUserDefaults standardUserDefaults] synchronize];
  233. }
  234. - (NSUInteger)usesCount
  235. {
  236. return [[NSUserDefaults standardUserDefaults] integerForKey:iRateUseCountKey];
  237. }
  238. - (void)setUsesCount:(NSUInteger)count
  239. {
  240. [[NSUserDefaults standardUserDefaults] setInteger:count forKey:iRateUseCountKey];
  241. [[NSUserDefaults standardUserDefaults] synchronize];
  242. }
  243. - (NSUInteger)eventCount;
  244. {
  245. return [[NSUserDefaults standardUserDefaults] integerForKey:iRateEventCountKey];
  246. }
  247. - (void)setEventCount:(NSUInteger)count
  248. {
  249. [[NSUserDefaults standardUserDefaults] setInteger:count forKey:iRateEventCountKey];
  250. [[NSUserDefaults standardUserDefaults] synchronize];
  251. }
  252. - (NSUInteger)declinedCount
  253. {
  254. return [[NSUserDefaults standardUserDefaults] integerForKey:iRateDeclinedCountKey];
  255. }
  256. - (void)setDeclinedCount:(NSUInteger)count
  257. {
  258. [[NSUserDefaults standardUserDefaults] setInteger:count forKey:iRateDeclinedCountKey];
  259. [[NSUserDefaults standardUserDefaults] synchronize];
  260. }
  261. - (float)usesPerWeek
  262. {
  263. return (float)self.usesCount / ([[NSDate date] timeIntervalSinceDate:self.firstUsed] / SECONDS_IN_A_WEEK);
  264. }
  265. - (BOOL)declinedThisVersion
  266. {
  267. return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateDeclinedVersionKey] isEqualToString:self.applicationVersion];
  268. }
  269. - (void)setDeclinedThisVersion:(BOOL)declined
  270. {
  271. [[NSUserDefaults standardUserDefaults] setObject:(declined? self.applicationVersion: nil) forKey:iRateDeclinedVersionKey];
  272. [[NSUserDefaults standardUserDefaults] synchronize];
  273. }
  274. - (BOOL)declinedAnyVersion
  275. {
  276. return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateDeclinedVersionKey] length];
  277. }
  278. - (BOOL)ratedThisVersion
  279. {
  280. return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateRatedVersionKey] isEqualToString:self.applicationVersion];
  281. }
  282. - (void)setRatedThisVersion:(BOOL)rated
  283. {
  284. [[NSUserDefaults standardUserDefaults] setObject:(rated? self.applicationVersion: nil) forKey:iRateRatedVersionKey];
  285. [[NSUserDefaults standardUserDefaults] synchronize];
  286. }
  287. - (BOOL)ratedAnyVersion
  288. {
  289. return [[[NSUserDefaults standardUserDefaults] objectForKey:iRateRatedVersionKey] length];
  290. }
  291. - (void)dealloc
  292. {
  293. [[NSNotificationCenter defaultCenter] removeObserver:self];
  294. }
  295. #pragma mark -
  296. #pragma mark Methods
  297. - (void)incrementUseCount
  298. {
  299. self.usesCount ++;
  300. }
  301. - (void)incrementEventCount
  302. {
  303. self.eventCount ++;
  304. }
  305. - (BOOL)shouldPromptForRating
  306. {
  307. //preview mode?
  308. if (self.previewMode)
  309. {
  310. NSLog(@"iRate preview mode is enabled - make sure you disable this for release");
  311. return YES;
  312. }
  313. //check if we've rated this version
  314. else if (self.ratedThisVersion)
  315. {
  316. if (self.verboseLogging)
  317. {
  318. NSLog(@"iRate did not prompt for rating because the user has already rated this version");
  319. }
  320. return NO;
  321. }
  322. //check if we've rated any version
  323. else if (!self.promptAgainForEachNewVersion && self.ratedAnyVersion)
  324. {
  325. if (self.verboseLogging)
  326. {
  327. NSLog(@"iRate did not prompt for rating because the user has already rated this app, and promptAgainForEachNewVersion is disabled");
  328. }
  329. return NO;
  330. }
  331. //check if we've declined to rate this version
  332. else if (self.declinedThisVersion)
  333. {
  334. if (self.verboseLogging)
  335. {
  336. NSLog(@"iRate did not prompt for rating because the user has declined to rate this version");
  337. }
  338. return NO;
  339. }
  340. //check for first launch
  341. else if ((self.daysUntilPrompt > 0.0f || self.usesPerWeekForPrompt) && self.firstUsed == nil)
  342. {
  343. if (self.verboseLogging)
  344. {
  345. NSLog(@"iRate did not prompt for rating because this is the first time the app has been launched");
  346. }
  347. return NO;
  348. }
  349. //check how long we've been using this version
  350. else if ([[NSDate date] timeIntervalSinceDate:self.firstUsed] < self.daysUntilPrompt * SECONDS_IN_A_DAY)
  351. {
  352. if (self.verboseLogging)
  353. {
  354. NSLog(@"iRate did not prompt for rating because the app was first used less than %g days ago", self.daysUntilPrompt);
  355. }
  356. return NO;
  357. }
  358. //check how many times we've used it and the number of significant events
  359. // else if (self.usesCount < self.usesUntilPrompt && self.eventCount < self.eventsUntilPrompt)
  360. else if (self.usesCount < self.usesUntilPrompt)
  361. {
  362. if (self.verboseLogging)
  363. {
  364. NSLog(@"iRate did not prompt for rating because the app has only been used %i times and only %i events have been logged", (int)self.usesCount, (int)self.eventCount);
  365. }
  366. return NO;
  367. }
  368. // //check if usage frequency is high enough
  369. // else if (self.usesPerWeek < self.usesPerWeekForPrompt)
  370. // {
  371. // if (self.verboseLogging)
  372. // {
  373. // NSLog(@"iRate did not prompt for rating because the app has only been used %g times per week on average since it was installed", self.usesPerWeek);
  374. // }
  375. // return NO;
  376. // }
  377. //check if within the reminder period
  378. else if (self.lastReminded != nil && [[NSDate date] timeIntervalSinceDate:self.lastReminded] < self.remindPeriod * SECONDS_IN_A_DAY)
  379. {
  380. if (self.verboseLogging)
  381. {
  382. NSLog(@"iRate did not prompt for rating because the user last asked to be reminded less than %g days ago", self.remindPeriod);
  383. }
  384. return NO;
  385. }
  386. //lets prompt!
  387. return YES;
  388. }
  389. - (NSString *)valueForKey:(NSString *)key inJSON:(NSString *)json
  390. {
  391. NSRange keyRange = [json rangeOfString:[NSString stringWithFormat:@"\"%@\"", key]];
  392. if (keyRange.location != NSNotFound)
  393. {
  394. NSInteger start = keyRange.location + keyRange.length;
  395. NSRange valueStart = [json rangeOfString:@":" options:0 range:NSMakeRange(start, [json length] - start)];
  396. if (valueStart.location != NSNotFound)
  397. {
  398. start = valueStart.location + 1;
  399. NSRange valueEnd = [json rangeOfString:@"," options:0 range:NSMakeRange(start, [json length] - start)];
  400. if (valueEnd.location != NSNotFound)
  401. {
  402. NSString *value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
  403. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  404. while ([value hasPrefix:@"\""] && ![value hasSuffix:@"\""])
  405. {
  406. if (valueEnd.location == NSNotFound)
  407. {
  408. break;
  409. }
  410. NSInteger newStart = valueEnd.location + 1;
  411. valueEnd = [json rangeOfString:@"," options:0 range:NSMakeRange(newStart, [json length] - newStart)];
  412. value = [json substringWithRange:NSMakeRange(start, valueEnd.location - start)];
  413. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  414. }
  415. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]];
  416. value = [value stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
  417. value = [value stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
  418. value = [value stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""];
  419. value = [value stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
  420. value = [value stringByReplacingOccurrencesOfString:@"\\r" withString:@"\r"];
  421. value = [value stringByReplacingOccurrencesOfString:@"\\t" withString:@"\t"];
  422. value = [value stringByReplacingOccurrencesOfString:@"\\f" withString:@"\f"];
  423. value = [value stringByReplacingOccurrencesOfString:@"\\b" withString:@"\f"];
  424. while (YES)
  425. {
  426. NSRange unicode = [value rangeOfString:@"\\u"];
  427. if (unicode.location == NSNotFound)
  428. {
  429. break;
  430. }
  431. uint32_t c = 0;
  432. NSString *hex = [value substringWithRange:NSMakeRange(unicode.location + 2, 4)];
  433. NSScanner *scanner = [NSScanner scannerWithString:hex];
  434. [scanner scanHexInt:&c];
  435. if (c <= 0xffff)
  436. {
  437. value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C", (unichar)c]];
  438. }
  439. else
  440. {
  441. //convert character to surrogate pair
  442. uint16_t x = (uint16_t)c;
  443. uint16_t u = (c >> 16) & ((1 << 5) - 1);
  444. uint16_t w = (uint16_t)u - 1;
  445. unichar high = 0xd800 | (w << 6) | x >> 10;
  446. unichar low = (uint16_t)(0xdc00 | (x & ((1 << 10) - 1)));
  447. value = [value stringByReplacingCharactersInRange:NSMakeRange(unicode.location, 6) withString:[NSString stringWithFormat:@"%C%C", high, low]];
  448. }
  449. }
  450. return value;
  451. }
  452. }
  453. }
  454. return nil;
  455. }
  456. - (void)setAppStoreIDOnMainThread:(NSString *)appStoreIDString
  457. {
  458. self.appStoreID = (NSUInteger)[appStoreIDString longLongValue];
  459. }
  460. - (void)connectionSucceeded
  461. {
  462. //no longer checking
  463. self.currentlyChecking = NO;
  464. if ([self ratingConditionsHaveBeenMet]) {
  465. //confirm with delegate
  466. if ([self.delegate respondsToSelector:@selector(iRateShouldPromptForRating)])
  467. {
  468. if (![self.delegate iRateShouldPromptForRating])
  469. {
  470. if (self.verboseLogging)
  471. {
  472. NSLog(@"iRate did not display the rating prompt because the iRateShouldPromptForRating delegate method returned NO");
  473. }
  474. return;
  475. }
  476. }
  477. //prompt user
  478. [self promptForRating];
  479. }
  480. }
  481. - (BOOL)ratingConditionsHaveBeenMet {
  482. // check if the app has been used enough
  483. NSInteger useCount = [self usesCount];
  484. if (useCount <= APPIRATER_USES_UNTIL_PROMPT) {
  485. return NO;
  486. } else if ((useCount % 5) == 0) {
  487. // Apple审核被拒,网页端显示 Free Download 按钮,每开10个文档后检测一次
  488. }
  489. NSDate *dateOfFirstLaunch = [self firstUsed];
  490. NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch];
  491. NSTimeInterval timeUntilRate = 60 * 60 * 24 * APPIRATER_DAYS_UNTIL_PROMPT;
  492. if (timeSinceFirstLaunch < timeUntilRate)
  493. return NO;
  494. // has the user previously declined to rate this version of the app?
  495. NSInteger declinedCount = [self declinedCount];
  496. if (declinedCount > APPIRATER_PROMPT_TIMES_UNTIL_REJECT)
  497. return NO;
  498. // has the user already rated the app?
  499. if (self.ratedThisVersion)
  500. return NO;
  501. // if the user wanted to be reminded later, has enough time passed?
  502. NSDate *reminderRequestDate = [self lastReminded];
  503. NSTimeInterval timeSinceReminderRequest = [[NSDate date] timeIntervalSinceDate:reminderRequestDate];
  504. NSTimeInterval timeUntilReminder = 60 * 60 * 24 * APPIRATER_TIME_BEFORE_REMINDING;
  505. if (timeSinceReminderRequest < timeUntilReminder)
  506. return NO;
  507. return YES;
  508. }
  509. - (void)connectionError:(NSError *)error
  510. {
  511. //no longer checking
  512. self.currentlyChecking = NO;
  513. //log the error
  514. if (error)
  515. {
  516. NSLog(@"iRate rating process failed because: %@", [error localizedDescription]);
  517. }
  518. else
  519. {
  520. NSLog(@"iRate rating process failed because an unknown error occured");
  521. }
  522. //could not connect
  523. if ([self.delegate respondsToSelector:@selector(iRateCouldNotConnectToAppStore:)])
  524. {
  525. [self.delegate iRateCouldNotConnectToAppStore:error];
  526. }
  527. }
  528. - (void)checkForConnectivityInBackground
  529. {
  530. @synchronized (self)
  531. {
  532. @autoreleasepool
  533. {
  534. //first check iTunes
  535. NSString *iTunesServiceURL = [NSString stringWithFormat:iRateAppLookupURLFormat, self.appStoreCountry];
  536. if (self.appStoreID)
  537. {
  538. iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?id=%u", (unsigned int)self.appStoreID];
  539. }
  540. else
  541. {
  542. iTunesServiceURL = [iTunesServiceURL stringByAppendingFormat:@"?bundleId=%@", self.applicationBundleID];
  543. }
  544. if (self.verboseLogging)
  545. {
  546. NSLog(@"iRate is checking %@ to retrieve the App Store details...", iTunesServiceURL);
  547. }
  548. NSError *error = nil;
  549. NSURLResponse *response = nil;
  550. NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:iTunesServiceURL] cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:REQUEST_TIMEOUT];
  551. NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
  552. if (data)
  553. {
  554. //convert to string
  555. NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  556. //check bundle ID matches
  557. NSString *bundleID = [self valueForKey:@"bundleId" inJSON:json];
  558. if (bundleID)
  559. {
  560. if ([bundleID isEqualToString:self.applicationBundleID])
  561. {
  562. //get genre
  563. if (self.appStoreGenreID == 0)
  564. {
  565. _appStoreGenreID = [[self valueForKey:@"primaryGenreId" inJSON:json] integerValue];
  566. }
  567. //get app id
  568. if (!self.appStoreID)
  569. {
  570. NSString *appStoreIDString = [self valueForKey:@"trackId" inJSON:json];
  571. [self performSelectorOnMainThread:@selector(setAppStoreIDOnMainThread:) withObject:appStoreIDString waitUntilDone:YES];
  572. if (self.verboseLogging)
  573. {
  574. NSLog(@"iRate found the app on iTunes. The App Store ID is %@", appStoreIDString);
  575. }
  576. }
  577. //check version
  578. if (self.onlyPromptIfLatestVersion && !self.previewMode)
  579. {
  580. NSString *latestVersion = [self valueForKey:@"version" inJSON:json];
  581. if ([latestVersion compare:self.applicationVersion options:NSNumericSearch] == NSOrderedDescending)
  582. {
  583. if (self.verboseLogging)
  584. {
  585. NSLog(@"iRate found that the installed application version (%@) is not the latest version on the App Store, which is %@",
  586. self.applicationVersion, latestVersion);
  587. }
  588. error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorApplicationIsNotLatestVersion userInfo:@{NSLocalizedDescriptionKey: @"Installed app is not the latest version available"}];
  589. }
  590. }
  591. }
  592. else
  593. {
  594. if (self.verboseLogging)
  595. {
  596. NSLog(@"iRate found that the application bundle ID (%@) does not match the bundle ID of the app found on iTunes (%@) with the specified App Store ID (%i)", self.applicationBundleID, bundleID, (int)self.appStoreID);
  597. }
  598. error = [NSError errorWithDomain:iRateErrorDomain code:iRateErrorBundleIdDoesNotMatchAppStore userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Application bundle ID does not match expected value of %@", bundleID]}];
  599. }
  600. }
  601. else if (self.appStoreID || !self.ratingsURL)
  602. {
  603. if (self.verboseLogging)
  604. {
  605. NSLog(@"iRate could not find this application on iTunes. If your app is not intended for App Store release then you must specify a custom ratingsURL. If this is the first release of your application then it's not a problem that it cannot be found on the store yet");
  606. }
  607. error = [NSError errorWithDomain:iRateErrorDomain
  608. code:iRateErrorApplicationNotFoundOnAppStore
  609. userInfo:@{NSLocalizedDescriptionKey: @"The application could not be found on the App Store."}];
  610. }
  611. else if (!self.appStoreID && self.verboseLogging)
  612. {
  613. NSLog(@"iRate 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");
  614. }
  615. }
  616. if (error && !(error.code == EPERM && [error.domain isEqualToString:NSPOSIXErrorDomain] && self.appStoreID))
  617. {
  618. [self performSelectorOnMainThread:@selector(connectionError:) withObject:error waitUntilDone:YES];
  619. }
  620. else if (self.appStoreID || self.previewMode)
  621. {
  622. //show prompt
  623. [self performSelectorOnMainThread:@selector(connectionSucceeded) withObject:nil waitUntilDone:YES];
  624. }
  625. }
  626. }
  627. }
  628. - (void)promptIfNetworkAvailable
  629. {
  630. if (!self.currentlyChecking)
  631. {
  632. self.currentlyChecking = YES;
  633. [self performSelectorInBackground:@selector(checkForConnectivityInBackground) withObject:nil];
  634. }
  635. }
  636. - (void)promptForRating
  637. {
  638. if (!self.visibleAlert)
  639. {
  640. #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
  641. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:self.messageTitle
  642. message:self.message
  643. delegate:(id <UIAlertViewDelegate>)self
  644. cancelButtonTitle:[self.cancelButtonLabel length] ? self.cancelButtonLabel: nil
  645. otherButtonTitles:self.rateButtonLabel, nil];
  646. // if ([self.remindButtonLabel length])
  647. // {
  648. // [alert addButtonWithTitle:self.remindButtonLabel];
  649. // }
  650. self.visibleAlert = alert;
  651. [self.visibleAlert show];
  652. #else
  653. //only show when main window is available
  654. if (self.onlyPromptIfMainWindowIsAvailable && ![[NSApplication sharedApplication] mainWindow])
  655. {
  656. [self performSelector:@selector(promptForRating) withObject:nil afterDelay:0.5];
  657. return;
  658. }
  659. self.visibleAlert = [NSAlert alertWithMessageText:self.messageTitle
  660. defaultButton:self.rateButtonLabel
  661. alternateButton:self.cancelButtonLabel
  662. otherButton:nil
  663. informativeTextWithFormat:@"%@", self.message];
  664. // if ([self.remindButtonLabel length])
  665. // {
  666. // [self.visibleAlert addButtonWithTitle:self.remindButtonLabel];
  667. // }
  668. [self.visibleAlert beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
  669. modalDelegate:self
  670. didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
  671. contextInfo:nil];
  672. #endif
  673. //inform about prompt
  674. if ([self.delegate respondsToSelector:@selector(iRateDidPromptForRating)])
  675. {
  676. [self.delegate iRateDidPromptForRating];
  677. }
  678. }
  679. }
  680. - (void)applicationLaunched
  681. {
  682. //check if this is a new version
  683. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  684. if (![[defaults objectForKey:iRateLastVersionUsedKey] isEqualToString:self.applicationVersion])
  685. {
  686. // has the user already rated the app?
  687. if (self.ratedAnyVersion)
  688. {
  689. NSDate *dateOfFirstLaunch = [self firstUsed];
  690. NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch];
  691. NSTimeInterval timeUntilRate = 60 * 60 * 24 * APPIRATER_TIME_BEFORE_REMINDING * (APPIRATER_PROMPT_TIMES_UNTIL_REJECT * 3);
  692. if (timeSinceFirstLaunch >= timeUntilRate)
  693. {
  694. [defaults removeObjectForKey:iRateRatedVersionKey];
  695. [defaults setObject:[NSDate date] forKey:iRateFirstUsedKey];
  696. }
  697. } else {
  698. [defaults setObject:[NSDate date] forKey:iRateFirstUsedKey];
  699. // has the user previously declined to rate this version of the app?
  700. NSInteger declinedCount = [self declinedCount];
  701. if (declinedCount > APPIRATER_PROMPT_TIMES_UNTIL_REJECT)
  702. {
  703. NSDate *dateOfFirstLaunch = [self lastReminded];
  704. NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch];
  705. NSTimeInterval timeUntilRate = 60 * 60 * 24 * APPIRATER_TIME_BEFORE_REMINDING * (APPIRATER_DAYS_UNTIL_PROMPT * 3);
  706. if (timeSinceFirstLaunch >= timeUntilRate)
  707. {
  708. [self setDeclinedCount:0];
  709. [defaults setObject:nil forKey:iRateLastRemindedKey];
  710. }
  711. } else {
  712. [defaults setObject:nil forKey:iRateLastRemindedKey];
  713. }
  714. }
  715. //reset counts
  716. [defaults setObject:self.applicationVersion forKey:iRateLastVersionUsedKey];
  717. [defaults setInteger:0 forKey:iRateUseCountKey];
  718. [defaults setInteger:0 forKey:iRateEventCountKey];
  719. [defaults synchronize];
  720. self.declinedThisVersion = NO;
  721. //inform about app update
  722. if ([self.delegate respondsToSelector:@selector(iRateDidDetectAppUpdate)])
  723. {
  724. [self.delegate iRateDidDetectAppUpdate];
  725. }
  726. }
  727. [self incrementUseCount];
  728. if (self.promptAtLaunch && [self shouldPromptForRating])
  729. {
  730. [self promptIfNetworkAvailable];
  731. }
  732. }
  733. - (void)appLaunched {
  734. [self incrementUseCount];
  735. if ([self shouldPromptForRating])
  736. {
  737. [self promptIfNetworkAvailable];
  738. }
  739. }
  740. #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
  741. - (void)applicationWillEnterForeground:(NSNotification *)notification
  742. {
  743. if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
  744. {
  745. [self incrementUseCount];
  746. if (self.promptAtLaunch && [self shouldPromptForRating])
  747. {
  748. [self promptIfNetworkAvailable];
  749. }
  750. }
  751. }
  752. #endif
  753. #pragma mark -
  754. #pragma mark UIAlertView methods
  755. #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
  756. - (void)openRatingsPageInAppStore
  757. {
  758. if (_displayAppUsingStorekitIfAvailable && [SKStoreProductViewController class])
  759. {
  760. if (self.verboseLogging)
  761. {
  762. NSLog(@"iRate will attempt to open the StoreKit in-app product page using the following app store ID: %i", self.appStoreID);
  763. }
  764. //create store view controller
  765. SKStoreProductViewController *productController = [[SKStoreProductViewController alloc] init];
  766. productController.delegate = (id<SKStoreProductViewControllerDelegate>)self;
  767. //load product details
  768. NSDictionary *productParameters = @{SKStoreProductParameterITunesItemIdentifier: [@(_appStoreID) description]};
  769. [productController loadProductWithParameters:productParameters completionBlock:^(BOOL result, NSError *error) {
  770. if (!result)
  771. {
  772. //log the error
  773. if (error)
  774. {
  775. NSLog(@"iRate rating process failed because: %@", [error localizedDescription]);
  776. }
  777. else
  778. {
  779. NSLog(@"iRate rating process failed because an unknown error occured");
  780. }
  781. self.ratedThisVersion = NO;
  782. if ([self.delegate respondsToSelector:@selector(iRateCouldNotConnectToAppStore:)])
  783. {
  784. [self.delegate iRateCouldNotConnectToAppStore:error];
  785. }
  786. }
  787. }];
  788. //get root view controller
  789. UIViewController *rootViewController = nil;
  790. id appDelegate = [[UIApplication sharedApplication] delegate];
  791. if ([appDelegate respondsToSelector:@selector(viewController)])
  792. {
  793. rootViewController = [appDelegate valueForKey:@"viewController"];
  794. }
  795. if (!rootViewController && [appDelegate respondsToSelector:@selector(window)])
  796. {
  797. UIWindow *window = [appDelegate valueForKey:@"window"];
  798. rootViewController = window.rootViewController;
  799. }
  800. if (!rootViewController)
  801. {
  802. UIWindow *window = [UIApplication sharedApplication].keyWindow;
  803. rootViewController = window.rootViewController;
  804. }
  805. if (!rootViewController)
  806. {
  807. if (self.verboseLogging)
  808. {
  809. NSLog(@"iRate couldn't find root view controller from which to display StoreKit product page");
  810. }
  811. }
  812. else
  813. {
  814. while (rootViewController.presentedViewController)
  815. {
  816. rootViewController = rootViewController.presentedViewController;
  817. }
  818. //present product view controller
  819. [rootViewController presentViewController:productController animated:YES completion:nil];
  820. if ([self.delegate respondsToSelector:@selector(iRateDidPresentStoreKitModal)])
  821. {
  822. [self.delegate iRateDidPresentStoreKitModal];
  823. }
  824. return;
  825. }
  826. }
  827. if (self.verboseLogging)
  828. {
  829. NSLog(@"iRate will open the App Store ratings page using the following URL: %@", self.ratingsURL);
  830. }
  831. [[UIApplication sharedApplication] openURL:self.ratingsURL];
  832. }
  833. - (void)productViewControllerDidFinish:(SKStoreProductViewController *)controller
  834. {
  835. [controller.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
  836. if ([self.delegate respondsToSelector:@selector(iRateDidDismissStoreKitModal)])
  837. {
  838. [self.delegate iRateDidDismissStoreKitModal];
  839. }
  840. }
  841. - (void)resizeAlertView:(UIAlertView *)alertView
  842. {
  843. if (!self.disableAlertViewResizing)
  844. {
  845. NSInteger imageCount = 0;
  846. CGFloat offset = 0.0f;
  847. CGFloat messageOffset = 0.0f;
  848. for (UIView *view in alertView.subviews)
  849. {
  850. CGRect frame = view.frame;
  851. if ([view isKindOfClass:[UILabel class]])
  852. {
  853. UILabel *label = (UILabel *)view;
  854. if ([label.text isEqualToString:alertView.title])
  855. {
  856. [label sizeToFit];
  857. offset = label.frame.size.height - fmax(0.0f, 45.f - label.frame.size.height);
  858. if (label.frame.size.height > frame.size.height)
  859. {
  860. offset = messageOffset = label.frame.size.height - frame.size.height;
  861. frame.size.height = label.frame.size.height;
  862. }
  863. }
  864. else if ([label.text isEqualToString:alertView.message])
  865. {
  866. label.lineBreakMode = NSLineBreakByWordWrapping;
  867. label.numberOfLines = 0;
  868. label.alpha = 1.0f;
  869. [label sizeToFit];
  870. offset += label.frame.size.height - frame.size.height;
  871. frame.origin.y += messageOffset;
  872. frame.size.height = label.frame.size.height;
  873. }
  874. }
  875. else if ([view isKindOfClass:[UITextView class]])
  876. {
  877. view.alpha = 0.0f;
  878. }
  879. else if ([view isKindOfClass:[UIImageView class]])
  880. {
  881. if (imageCount++ > 0)
  882. {
  883. view.alpha = 0.0f;
  884. }
  885. }
  886. else if ([view isKindOfClass:[UIControl class]])
  887. {
  888. frame.origin.y += offset;
  889. }
  890. view.frame = frame;
  891. }
  892. CGRect frame = alertView.frame;
  893. frame.origin.y -= roundf(offset/2.0f);
  894. frame.size.height += offset;
  895. alertView.frame = frame;
  896. }
  897. }
  898. - (void)willRotate
  899. {
  900. [self performSelectorOnMainThread:@selector(didRotate) withObject:nil waitUntilDone:NO];
  901. }
  902. - (void)didRotate
  903. {
  904. if (self.previousOrientation != [UIApplication sharedApplication].statusBarOrientation)
  905. {
  906. self.previousOrientation = [UIApplication sharedApplication].statusBarOrientation;
  907. [self resizeAlertView:self.visibleAlert];
  908. }
  909. }
  910. - (void)willPresentAlertView:(UIAlertView *)alertView
  911. {
  912. [self resizeAlertView:alertView];
  913. }
  914. - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
  915. {
  916. if (buttonIndex == alertView.cancelButtonIndex)
  917. {
  918. //ignore this version
  919. self.declinedThisVersion = YES;
  920. //log event
  921. if ([self.delegate respondsToSelector:@selector(iRateUserDidDeclineToRateApp)])
  922. {
  923. [self.delegate iRateUserDidDeclineToRateApp];
  924. }
  925. }
  926. else if (([self.cancelButtonLabel length] && buttonIndex == 2) ||
  927. ([self.cancelButtonLabel length] == 0 && buttonIndex == 1))
  928. {
  929. //remind later
  930. self.lastReminded = [NSDate date];
  931. //log event
  932. if ([self.delegate respondsToSelector:@selector(iRateUserDidRequestReminderToRateApp)])
  933. {
  934. [self.delegate iRateUserDidRequestReminderToRateApp];
  935. }
  936. }
  937. else
  938. {
  939. //mark as rated
  940. self.ratedThisVersion = YES;
  941. //log event
  942. if ([self.delegate respondsToSelector:@selector(iRateUserDidAttemptToRateApp)])
  943. {
  944. [self.delegate iRateUserDidAttemptToRateApp];
  945. }
  946. if (![self.delegate respondsToSelector:@selector(iRateShouldOpenAppStore)] || [_delegate iRateShouldOpenAppStore])
  947. {
  948. //go to ratings page
  949. [self openRatingsPageInAppStore];
  950. }
  951. }
  952. //release alert
  953. self.visibleAlert = nil;
  954. }
  955. #else
  956. - (void)openAppPageWhenAppStoreLaunched
  957. {
  958. //check if app store is running
  959. ProcessSerialNumber psn = { kNoProcess, kNoProcess };
  960. while (GetNextProcess(&psn) == noErr)
  961. {
  962. CFDictionaryRef cfDict = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
  963. NSString *bundleID = [(__bridge NSDictionary *)cfDict objectForKey:(__bridge NSString *)kCFBundleIdentifierKey];
  964. if ([iRateMacAppStoreBundleID isEqualToString:[bundleID lowercaseString]])
  965. {
  966. //open app page
  967. [[NSWorkspace sharedWorkspace] performSelector:@selector(openURL:) withObject:self.ratingsURL afterDelay:MAC_APP_STORE_REFRESH_DELAY];
  968. CFRelease(cfDict);
  969. return;
  970. }
  971. CFRelease(cfDict);
  972. }
  973. //try again
  974. [self performSelector:@selector(openAppPageWhenAppStoreLaunched) withObject:nil afterDelay:0.0];
  975. }
  976. - (void)openRatingsPageInAppStore
  977. {
  978. if (self.verboseLogging)
  979. {
  980. NSLog(@"iRate will open the App Store ratings page using the following URL: %@", self.ratingsURL);
  981. }
  982. [[NSWorkspace sharedWorkspace] openURL:self.ratingsURL];
  983. [self openAppPageWhenAppStoreLaunched];
  984. }
  985. - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
  986. {
  987. switch (returnCode)
  988. {
  989. case NSAlertAlternateReturn:
  990. {
  991. //remind later
  992. self.lastReminded = [NSDate date];
  993. NSInteger declinedCount = [self declinedCount];
  994. declinedCount++;
  995. [self setDeclinedCount:declinedCount];
  996. //log event
  997. if ([self.delegate respondsToSelector:@selector(iRateUserDidDeclineToRateApp)])
  998. {
  999. [self.delegate iRateUserDidDeclineToRateApp];
  1000. }
  1001. break;
  1002. }
  1003. case NSAlertDefaultReturn:
  1004. {
  1005. //remind later
  1006. self.lastReminded = [NSDate date];
  1007. //mark as rated
  1008. self.ratedThisVersion = YES;
  1009. //log event
  1010. if ([self.delegate respondsToSelector:@selector(iRateUserDidAttemptToRateApp)])
  1011. {
  1012. [self.delegate iRateUserDidAttemptToRateApp];
  1013. }
  1014. if (![self.delegate respondsToSelector:@selector(iRateShouldOpenAppStore)] || [_delegate iRateShouldOpenAppStore])
  1015. {
  1016. //launch mac app store
  1017. [self openRatingsPageInAppStore];
  1018. }
  1019. break;
  1020. }
  1021. default:
  1022. {
  1023. //remind later
  1024. self.lastReminded = [NSDate date];
  1025. //log event
  1026. if ([self.delegate respondsToSelector:@selector(iRateUserDidRequestReminderToRateApp)])
  1027. {
  1028. [self.delegate iRateUserDidRequestReminderToRateApp];
  1029. }
  1030. }
  1031. }
  1032. //release alert
  1033. self.visibleAlert = nil;
  1034. }
  1035. #endif
  1036. - (void)logEvent:(BOOL)deferPrompt
  1037. {
  1038. [self incrementEventCount];
  1039. if (!deferPrompt && [self shouldPromptForRating])
  1040. {
  1041. [self promptIfNetworkAvailable];
  1042. }
  1043. }
  1044. - (void)remindMeLater
  1045. {
  1046. //remind later
  1047. self.lastReminded = [NSDate date];
  1048. NSInteger declinedCount = [self declinedCount];
  1049. declinedCount++;
  1050. [self setDeclinedCount:declinedCount];
  1051. //log event
  1052. if ([self.delegate respondsToSelector:@selector(iRateUserDidDeclineToRateApp)])
  1053. {
  1054. [self.delegate iRateUserDidDeclineToRateApp];
  1055. }
  1056. }
  1057. @end