├── .gitignore ├── Appirater.h ├── Appirater.m ├── Appirater.podspec.json ├── Appirater.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── Arbeit.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── Appirater.xcscheme └── xcuserdata │ └── Arbeit.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── AppiraterDelegate.h ├── CHANGELOG.md ├── Info.plist ├── README.md ├── ar.lproj └── AppiraterLocalizable.strings ├── ca.lproj └── AppiraterLocalizable.strings ├── cs.lproj └── AppiraterLocalizable.strings ├── da.lproj └── AppiraterLocalizable.strings ├── de.lproj └── AppiraterLocalizable.strings ├── el.lproj └── AppiraterLocalizable.strings ├── en.lproj └── AppiraterLocalizable.strings ├── es.lproj └── AppiraterLocalizable.strings ├── fa.lproj └── AppiraterLocalizable.strings ├── fi.lproj └── AppiraterLocalizable.strings ├── fr.lproj └── AppiraterLocalizable.strings ├── he.lproj └── AppiraterLocalizable.strings ├── hu.lproj └── AppiraterLocalizable.strings ├── hy.lproj └── AppiraterLocalizable.strings ├── id.lproj └── AppiraterLocalizable.strings ├── it.lproj └── AppiraterLocalizable.strings ├── ja.lproj └── AppiraterLocalizable.strings ├── ko.lproj └── AppiraterLocalizable.strings ├── ms.lproj └── AppiraterLocalizable.strings ├── nb.lproj └── AppiraterLocalizable.strings ├── nl.lproj └── AppiraterLocalizable.strings ├── pl.lproj └── AppiraterLocalizable.strings ├── pt-BR.lproj └── AppiraterLocalizable.strings ├── pt.lproj └── AppiraterLocalizable.strings ├── ro.lproj └── AppiraterLocalizable.strings ├── ru.lproj └── AppiraterLocalizable.strings ├── sk.lproj └── AppiraterLocalizable.strings ├── sv.lproj └── AppiraterLocalizable.strings ├── th.lproj └── AppiraterLocalizable.strings ├── tr.lproj └── AppiraterLocalizable.strings ├── uk.lproj └── AppiraterLocalizable.strings ├── vi.lproj └── AppiraterLocalizable.strings ├── zh-Hans.lproj └── AppiraterLocalizable.strings └── zh-Hant.lproj └── AppiraterLocalizable.strings /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Appirater.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Appirater. 3 | 4 | Copyright (c) 2012, Arash Payan 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following 14 | conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | /* 29 | * Appirater.h 30 | * appirater 31 | * 32 | * Created by Arash Payan on 9/5/09. 33 | * http://arashpayan.com 34 | * Copyright 2012 Arash Payan. All rights reserved. 35 | */ 36 | 37 | #import 38 | #import 39 | #import "AppiraterDelegate.h" 40 | 41 | extern NSString *const kAppiraterFirstUseDate; 42 | extern NSString *const kAppiraterUseCount; 43 | extern NSString *const kAppiraterSignificantEventCount; 44 | extern NSString *const kAppiraterCurrentVersion; 45 | extern NSString *const kAppiraterRatedCurrentVersion; 46 | extern NSString *const kAppiraterDeclinedToRate; 47 | extern NSString *const kAppiraterReminderRequestDate; 48 | 49 | /*! 50 | Your localized app's name. 51 | */ 52 | #define APPIRATER_LOCALIZED_APP_NAME [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"] 53 | 54 | /*! 55 | Your app's name. 56 | */ 57 | #define APPIRATER_APP_NAME APPIRATER_LOCALIZED_APP_NAME ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"] 58 | 59 | /*! 60 | This is the message your users will see once they've passed the day+launches 61 | threshold. 62 | */ 63 | #define APPIRATER_LOCALIZED_MESSAGE NSLocalizedStringFromTableInBundle(@"If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!", @"AppiraterLocalizable", [Appirater bundle], nil) 64 | #define APPIRATER_MESSAGE [NSString stringWithFormat:APPIRATER_LOCALIZED_MESSAGE, APPIRATER_APP_NAME] 65 | 66 | /*! 67 | This is the title of the message alert that users will see. 68 | */ 69 | #define APPIRATER_LOCALIZED_MESSAGE_TITLE NSLocalizedStringFromTableInBundle(@"Rate %@", @"AppiraterLocalizable", [Appirater bundle], nil) 70 | #define APPIRATER_MESSAGE_TITLE [NSString stringWithFormat:APPIRATER_LOCALIZED_MESSAGE_TITLE, APPIRATER_APP_NAME] 71 | 72 | /*! 73 | The text of the button that rejects reviewing the app. 74 | */ 75 | #define APPIRATER_CANCEL_BUTTON NSLocalizedStringFromTableInBundle(@"No, Thanks", @"AppiraterLocalizable", [Appirater bundle], nil) 76 | 77 | /*! 78 | Text of button that will send user to app review page. 79 | */ 80 | #define APPIRATER_LOCALIZED_RATE_BUTTON NSLocalizedStringFromTableInBundle(@"Rate %@", @"AppiraterLocalizable", [Appirater bundle], nil) 81 | #define APPIRATER_RATE_BUTTON [NSString stringWithFormat:APPIRATER_LOCALIZED_RATE_BUTTON, APPIRATER_APP_NAME] 82 | 83 | /*! 84 | Text for button to remind the user to review later. 85 | */ 86 | #define APPIRATER_RATE_LATER NSLocalizedStringFromTableInBundle(@"Remind me later", @"AppiraterLocalizable", [Appirater bundle], nil) 87 | 88 | @interface Appirater : NSObject 89 | 90 | /*! 91 | UIAlertController for iOS 8 and later, otherwise UIAlertView 92 | */ 93 | @property(nonatomic, strong) id ratingAlert; 94 | @property(nonatomic) BOOL openInAppStore; 95 | #if __has_feature(objc_arc_weak) 96 | @property(nonatomic, weak) NSObject *delegate; 97 | #else 98 | @property(nonatomic, unsafe_unretained) NSObject *delegate; 99 | #endif 100 | 101 | /*! 102 | Tells Appirater that the app has launched, and on devices that do NOT 103 | support multitasking, the 'uses' count will be incremented. You should 104 | call this method at the end of your application delegate's 105 | application:didFinishLaunchingWithOptions: method. 106 | 107 | If the app has been used enough to be rated (and enough significant events), 108 | you can suppress the rating alert 109 | by passing NO for canPromptForRating. The rating alert will simply be postponed 110 | until it is called again with YES for canPromptForRating. The rating alert 111 | can also be triggered by appEnteredForeground: and userDidSignificantEvent: 112 | (as long as you pass YES for canPromptForRating in those methods). 113 | */ 114 | + (void)appLaunched:(BOOL)canPromptForRating; 115 | 116 | /*! 117 | Tells Appirater that the app was brought to the foreground on multitasking 118 | devices. You should call this method from the application delegate's 119 | applicationWillEnterForeground: method. 120 | 121 | If the app has been used enough to be rated (and enough significant events), 122 | you can suppress the rating alert 123 | by passing NO for canPromptForRating. The rating alert will simply be postponed 124 | until it is called again with YES for canPromptForRating. The rating alert 125 | can also be triggered by appLaunched: and userDidSignificantEvent: 126 | (as long as you pass YES for canPromptForRating in those methods). 127 | */ 128 | + (void)appEnteredForeground:(BOOL)canPromptForRating; 129 | 130 | /*! 131 | Tells Appirater that the user performed a significant event. A significant 132 | event is whatever you want it to be. If you're app is used to make VoIP 133 | calls, then you might want to call this method whenever the user places 134 | a call. If it's a game, you might want to call this whenever the user 135 | beats a level boss. 136 | 137 | If the user has performed enough significant events and used the app enough, 138 | you can suppress the rating alert by passing NO for canPromptForRating. The 139 | rating alert will simply be postponed until it is called again with YES for 140 | canPromptForRating. The rating alert can also be triggered by appLaunched: 141 | and appEnteredForeground: (as long as you pass YES for canPromptForRating 142 | in those methods). 143 | */ 144 | + (void)userDidSignificantEvent:(BOOL)canPromptForRating; 145 | 146 | /*! 147 | Tells Appirater to try and show the prompt (a rating alert). The prompt will be showed 148 | if there is connection available, the user hasn't declined to rate 149 | or hasn't rated current version. 150 | 151 | You could call to show the prompt regardless Appirater settings, 152 | e.g., in case of some special event in your app. 153 | */ 154 | + (void)tryToShowPrompt; 155 | 156 | /*! 157 | Tells Appirater to show the prompt (a rating alert). 158 | Similar to tryToShowPrompt, but without checks (the prompt is always displayed). 159 | Passing false will hide the rate later button on the prompt. 160 | 161 | The only case where you should call this is if your app has an 162 | explicit "Rate this app" command somewhere. This is similar to rateApp, 163 | but instead of jumping to the review directly, an intermediary prompt is displayed. 164 | */ 165 | + (void)forceShowPrompt:(BOOL)displayRateLaterButton; 166 | 167 | /*! 168 | Tells Appirater to open the App Store page where the user can specify a 169 | rating for the app. Also records the fact that this has happened, so the 170 | user won't be prompted again to rate the app. 171 | 172 | The only case where you should call this directly is if your app has an 173 | explicit "Rate this app" command somewhere. In all other cases, don't worry 174 | about calling this -- instead, just call the other functions listed above, 175 | and let Appirater handle the bookkeeping of deciding when to ask the user 176 | whether to rate the app. 177 | */ 178 | + (void)rateApp; 179 | 180 | /*! 181 | Tells Appirater to immediately close any open rating modals (e.g. StoreKit rating VCs). 182 | */ 183 | + (void)closeModal; 184 | 185 | /*! 186 | Asks Appirater if the user has declined to rate; 187 | */ 188 | - (BOOL)userHasDeclinedToRate; 189 | 190 | /*! 191 | Asks Appirater if the user has rated the current version. 192 | Note that this is not a guarantee that the user has actually rated the app in the 193 | app store, but they've just clicked the rate button on the Appirater dialog. 194 | */ 195 | - (BOOL)userHasRatedCurrentVersion; 196 | 197 | @end 198 | 199 | @interface Appirater(Configuration) 200 | 201 | /*! 202 | Set your Apple generated software id here. 203 | */ 204 | + (void) setAppId:(NSString*)appId; 205 | 206 | /*! 207 | Users will need to have the same version of your app installed for this many 208 | days before they will be prompted to rate it. 209 | */ 210 | + (void) setDaysUntilPrompt:(double)value; 211 | 212 | /*! 213 | An example of a 'use' would be if the user launched the app. Bringing the app 214 | into the foreground (on devices that support it) would also be considered 215 | a 'use'. You tell Appirater about these events using the two methods: 216 | [Appirater appLaunched:] 217 | [Appirater appEnteredForeground:] 218 | 219 | Users need to 'use' the same version of the app this many times before 220 | before they will be prompted to rate it. 221 | */ 222 | + (void) setUsesUntilPrompt:(NSInteger)value; 223 | 224 | /*! 225 | A significant event can be anything you want to be in your app. In a 226 | telephone app, a significant event might be placing or receiving a call. 227 | In a game, it might be beating a level or a boss. This is just another 228 | layer of filtering that can be used to make sure that only the most 229 | loyal of your users are being prompted to rate you on the app store. 230 | If you leave this at a value of -1, then this won't be a criterion 231 | used for rating. To tell Appirater that the user has performed 232 | a significant event, call the method: 233 | [Appirater userDidSignificantEvent:]; 234 | */ 235 | + (void) setSignificantEventsUntilPrompt:(NSInteger)value; 236 | 237 | 238 | /*! 239 | Once the rating alert is presented to the user, they might select 240 | 'Remind me later'. This value specifies how long (in days) Appirater 241 | will wait before reminding them. 242 | */ 243 | + (void) setTimeBeforeReminding:(double)value; 244 | 245 | /*! 246 | Set customized title for alert view. 247 | */ 248 | + (void) setCustomAlertTitle:(NSString *)title; 249 | 250 | /*! 251 | Set customized message for alert view. 252 | */ 253 | + (void) setCustomAlertMessage:(NSString *)message; 254 | 255 | /*! 256 | Set customized cancel button title for alert view. 257 | */ 258 | + (void) setCustomAlertCancelButtonTitle:(NSString *)cancelTitle; 259 | 260 | /*! 261 | Set customized rate button title for alert view. 262 | */ 263 | + (void) setCustomAlertRateButtonTitle:(NSString *)rateTitle; 264 | 265 | /*! 266 | Set customized rate later button title for alert view. 267 | */ 268 | + (void) setCustomAlertRateLaterButtonTitle:(NSString *)rateLaterTitle; 269 | 270 | /*! 271 | 'YES' will show the Appirater alert everytime. Useful for testing how your message 272 | looks and making sure the link to your app's review page works. 273 | */ 274 | + (void) setDebug:(BOOL)debug; 275 | 276 | /*! 277 | Set the delegate if you want to know when Appirater does something 278 | */ 279 | + (void)setDelegate:(id)delegate; 280 | 281 | /*! 282 | Set whether or not Appirater uses animation (currently respected when pushing modal StoreKit rating VCs). 283 | */ 284 | + (void)setUsesAnimation:(BOOL)animation; 285 | 286 | /*! 287 | If set to YES, Appirater will open App Store link (instead of SKStoreProductViewController on iOS 6). Default YES. 288 | */ 289 | + (void)setOpenInAppStore:(BOOL)openInAppStore; 290 | 291 | /*! 292 | If set to YES, the main bundle will always be used to load localized strings. 293 | Set this to YES if you have provided your own custom localizations in AppiraterLocalizable.strings 294 | in your main bundle. Default is NO. 295 | */ 296 | + (void)setAlwaysUseMainBundle:(BOOL)useMainBundle; 297 | 298 | @end 299 | 300 | 301 | /*! 302 | Methods in this interface are public out of necessity, but may change without notice 303 | */ 304 | @interface Appirater(Unsafe) 305 | 306 | /*! 307 | The bundle localized strings will be loaded from. 308 | */ 309 | +(NSBundle *)bundle; 310 | 311 | @end 312 | 313 | @interface Appirater(Deprecated) 314 | 315 | /*! 316 | DEPRECATED: While still functional, it's better to use 317 | appLaunched:(BOOL)canPromptForRating instead. 318 | 319 | Calls [Appirater appLaunched:YES]. See appLaunched: for details of functionality. 320 | */ 321 | + (void)appLaunched __attribute__((deprecated)); 322 | 323 | /*! 324 | DEPRECATED: While still functional, it's better to use 325 | tryToShowPrompt instead. 326 | 327 | Calls [Appirater tryToShowPrompt]. See tryToShowPrompt for details of functionality. 328 | */ 329 | + (void)showPrompt __attribute__((deprecated)); 330 | 331 | @end 332 | -------------------------------------------------------------------------------- /Appirater.m: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Appirater. 3 | 4 | Copyright (c) 2012, Arash Payan 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following 14 | conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | /* 29 | * Appirater.m 30 | * appirater 31 | * 32 | * Created by Arash Payan on 9/5/09. 33 | * http://arashpayan.com 34 | * Copyright 2012 Arash Payan. All rights reserved. 35 | */ 36 | 37 | #import 38 | #import 39 | #import "Appirater.h" 40 | #include 41 | 42 | #if ! __has_feature(objc_arc) 43 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 44 | #endif 45 | 46 | NSString *const kAppiraterFirstUseDate = @"kAppiraterFirstUseDate"; 47 | NSString *const kAppiraterUseCount = @"kAppiraterUseCount"; 48 | NSString *const kAppiraterSignificantEventCount = @"kAppiraterSignificantEventCount"; 49 | NSString *const kAppiraterCurrentVersion = @"kAppiraterCurrentVersion"; 50 | NSString *const kAppiraterRatedCurrentVersion = @"kAppiraterRatedCurrentVersion"; 51 | NSString *const kAppiraterDeclinedToRate = @"kAppiraterDeclinedToRate"; 52 | NSString *const kAppiraterReminderRequestDate = @"kAppiraterReminderRequestDate"; 53 | 54 | NSString *templateReviewURL = @"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID"; 55 | NSString *templateReviewURLiOS7 = @"itms-apps://itunes.apple.com/app/idAPP_ID"; 56 | NSString *templateReviewURLiOS8 = @"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software"; 57 | 58 | static NSString *_appId; 59 | static double _daysUntilPrompt = 30; 60 | static NSInteger _usesUntilPrompt = 20; 61 | static NSInteger _significantEventsUntilPrompt = -1; 62 | static double _timeBeforeReminding = 1; 63 | static BOOL _debug = NO; 64 | static BOOL _usesAnimation = TRUE; 65 | static UIStatusBarStyle _statusBarStyle; 66 | static BOOL _modalOpen = false; 67 | static BOOL _alwaysUseMainBundle = NO; 68 | 69 | @interface Appirater () 70 | @property (nonatomic, copy) NSString *alertTitle; 71 | @property (nonatomic, copy) NSString *alertMessage; 72 | @property (nonatomic, copy) NSString *alertCancelTitle; 73 | @property (nonatomic, copy) NSString *alertRateTitle; 74 | @property (nonatomic, copy) NSString *alertRateLaterTitle; 75 | @property (nonatomic, strong) NSOperationQueue *eventQueue; 76 | - (BOOL)connectedToNetwork; 77 | + (Appirater*)sharedInstance; 78 | - (void)showPromptWithChecks:(BOOL)withChecks 79 | displayRateLaterButton:(BOOL)displayRateLaterButton; 80 | - (void)showRatingAlert:(BOOL)displayRateLaterButton; 81 | - (void)showRatingAlert; 82 | - (BOOL)ratingAlertIsAppropriate; 83 | - (BOOL)ratingConditionsHaveBeenMet; 84 | - (void)incrementUseCount; 85 | - (void)hideRatingAlert; 86 | @end 87 | 88 | @implementation Appirater 89 | 90 | + (void) setAppId:(NSString *)appId { 91 | _appId = appId; 92 | } 93 | 94 | + (void) setDaysUntilPrompt:(double)value { 95 | _daysUntilPrompt = value; 96 | } 97 | 98 | + (void) setUsesUntilPrompt:(NSInteger)value { 99 | _usesUntilPrompt = value; 100 | } 101 | 102 | + (void) setSignificantEventsUntilPrompt:(NSInteger)value { 103 | _significantEventsUntilPrompt = value; 104 | } 105 | 106 | + (void) setTimeBeforeReminding:(double)value { 107 | _timeBeforeReminding = value; 108 | } 109 | 110 | + (void) setCustomAlertTitle:(NSString *)title 111 | { 112 | [self sharedInstance].alertTitle = title; 113 | } 114 | 115 | + (void) setCustomAlertMessage:(NSString *)message 116 | { 117 | [self sharedInstance].alertMessage = message; 118 | } 119 | 120 | + (void) setCustomAlertCancelButtonTitle:(NSString *)cancelTitle 121 | { 122 | [self sharedInstance].alertCancelTitle = cancelTitle; 123 | } 124 | 125 | + (void) setCustomAlertRateButtonTitle:(NSString *)rateTitle 126 | { 127 | [self sharedInstance].alertRateTitle = rateTitle; 128 | } 129 | 130 | + (void) setCustomAlertRateLaterButtonTitle:(NSString *)rateLaterTitle 131 | { 132 | [self sharedInstance].alertRateLaterTitle = rateLaterTitle; 133 | } 134 | 135 | + (void) setDebug:(BOOL)debug { 136 | _debug = debug; 137 | } 138 | + (void)setDelegate:(id)delegate{ 139 | Appirater.sharedInstance.delegate = delegate; 140 | } 141 | + (void)setUsesAnimation:(BOOL)animation { 142 | _usesAnimation = animation; 143 | } 144 | + (void)setOpenInAppStore:(BOOL)openInAppStore { 145 | [Appirater sharedInstance].openInAppStore = openInAppStore; 146 | } 147 | + (void)setStatusBarStyle:(UIStatusBarStyle)style { 148 | _statusBarStyle = style; 149 | } 150 | + (void)setModalOpen:(BOOL)open { 151 | _modalOpen = open; 152 | } 153 | + (void)setAlwaysUseMainBundle:(BOOL)alwaysUseMainBundle { 154 | _alwaysUseMainBundle = alwaysUseMainBundle; 155 | } 156 | 157 | + (NSBundle *)bundle 158 | { 159 | NSBundle *bundle; 160 | 161 | if (_alwaysUseMainBundle) { 162 | bundle = [NSBundle mainBundle]; 163 | } else { 164 | NSURL *appiraterBundleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"Appirater" withExtension:@"bundle"]; 165 | 166 | if (appiraterBundleURL) { 167 | // Appirater.bundle will likely only exist when used via CocoaPods 168 | bundle = [NSBundle bundleWithURL:appiraterBundleURL]; 169 | } else { 170 | bundle = [NSBundle mainBundle]; 171 | } 172 | } 173 | 174 | return bundle; 175 | } 176 | 177 | - (NSString *)alertTitle 178 | { 179 | return _alertTitle ? _alertTitle : APPIRATER_MESSAGE_TITLE; 180 | } 181 | 182 | - (NSString *)alertMessage 183 | { 184 | return _alertMessage ? _alertMessage : APPIRATER_MESSAGE; 185 | } 186 | 187 | - (NSString *)alertCancelTitle 188 | { 189 | return _alertCancelTitle ? _alertCancelTitle : APPIRATER_CANCEL_BUTTON; 190 | } 191 | 192 | - (NSString *)alertRateTitle 193 | { 194 | return _alertRateTitle ? _alertRateTitle : APPIRATER_RATE_BUTTON; 195 | } 196 | 197 | - (NSString *)alertRateLaterTitle 198 | { 199 | return _alertRateLaterTitle ? _alertRateLaterTitle : APPIRATER_RATE_LATER; 200 | } 201 | 202 | - (void)dealloc { 203 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 204 | } 205 | 206 | - (id)init { 207 | self = [super init]; 208 | if (self) { 209 | if ([[UIDevice currentDevice].systemVersion floatValue] >= 7.0) { 210 | self.openInAppStore = YES; 211 | } else { 212 | self.openInAppStore = NO; 213 | } 214 | } 215 | 216 | return self; 217 | } 218 | 219 | - (BOOL)connectedToNetwork { 220 | // Create zero addy 221 | struct sockaddr_in zeroAddress; 222 | bzero(&zeroAddress, sizeof(zeroAddress)); 223 | zeroAddress.sin_len = sizeof(zeroAddress); 224 | zeroAddress.sin_family = AF_INET; 225 | 226 | // Recover reachability flags 227 | SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress); 228 | SCNetworkReachabilityFlags flags; 229 | 230 | Boolean didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags); 231 | CFRelease(defaultRouteReachability); 232 | 233 | if (!didRetrieveFlags) 234 | { 235 | NSLog(@"Error. Could not recover network reachability flags"); 236 | return NO; 237 | } 238 | 239 | BOOL isReachable = flags & kSCNetworkFlagsReachable; 240 | BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired; 241 | BOOL nonWiFi = flags & kSCNetworkReachabilityFlagsTransientConnection; 242 | 243 | NSURL *testURL = [NSURL URLWithString:@"http://www.apple.com/"]; 244 | 245 | NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; 246 | sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; 247 | sessionConfiguration.timeoutIntervalForRequest = 20.0; 248 | 249 | NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; 250 | 251 | NSURLSessionTask *task = [session dataTaskWithURL:testURL]; 252 | [task resume]; 253 | 254 | return ((isReachable && !needsConnection) || nonWiFi) ? ( (task.state != NSURLSessionTaskStateSuspended) ? YES : NO ) : NO; 255 | } 256 | 257 | + (Appirater*)sharedInstance { 258 | static Appirater *appirater = nil; 259 | if (appirater == nil) 260 | { 261 | static dispatch_once_t onceToken; 262 | dispatch_once(&onceToken, ^{ 263 | appirater = [[Appirater alloc] init]; 264 | appirater.eventQueue = [[NSOperationQueue alloc] init]; 265 | appirater.eventQueue.maxConcurrentOperationCount = 1; 266 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive) name: 267 | UIApplicationWillResignActiveNotification object:nil]; 268 | }); 269 | } 270 | 271 | return appirater; 272 | } 273 | 274 | - (void)showRatingAlert:(BOOL)displayRateLaterButton { 275 | id delegate = _delegate; 276 | 277 | if(delegate && [delegate respondsToSelector:@selector(appiraterShouldDisplayAlert:)] && ![delegate appiraterShouldDisplayAlert:self]) { 278 | return; 279 | } 280 | 281 | #pragma clang diagnostic push 282 | #pragma clang diagnostic ignored "-Wunguarded-availability" 283 | if (NSStringFromClass([SKStoreReviewController class]) != nil) { 284 | #pragma clang diagnostic pop 285 | [Appirater rateApp]; 286 | } else { 287 | // Otherwise show a custom Alert 288 | NSMutableArray *buttons = [[NSMutableArray alloc] initWithObjects:self.alertRateTitle, nil]; 289 | if (displayRateLaterButton) { 290 | [buttons addObject:self.alertRateLaterTitle]; 291 | } 292 | if (NSStringFromClass([UIAlertController class]) != nil) { 293 | [buttons addObject:self.alertCancelTitle]; 294 | 295 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.alertTitle message:self.alertMessage preferredStyle:UIAlertControllerStyleAlert]; 296 | for (NSInteger i = 0; i < buttons.count; i++) { 297 | UIAlertActionStyle style = i == buttons.count - 1 ? UIAlertActionStyleCancel : UIAlertActionStyleDefault; 298 | [alert addAction:[UIAlertAction actionWithTitle:buttons[i] style:style handler:^(UIAlertAction * _Nonnull action) { 299 | NSString *title = action.title; 300 | NSInteger buttonIndex = -1; 301 | if ([title isEqual:self.alertCancelTitle]) { 302 | buttonIndex = 0; 303 | } else if ([title isEqual:self.alertRateTitle]) { 304 | buttonIndex = 1; 305 | } else if ([title isEqual:self.alertRateLaterTitle]) { 306 | buttonIndex = 2; 307 | } 308 | 309 | [self alertViewDidDismissWithButtonIndex:buttonIndex]; 310 | }]]; 311 | } 312 | [[Appirater getRootViewController] presentViewController:alert animated:YES completion:nil]; 313 | self.ratingAlert = alert; 314 | } else { 315 | #pragma clang diagnostic push 316 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 317 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:self.alertTitle 318 | message:self.alertMessage 319 | delegate:self 320 | cancelButtonTitle:self.alertCancelTitle 321 | otherButtonTitles:nil]; 322 | for (NSString *button in buttons) { 323 | [alertView addButtonWithTitle:button]; 324 | } 325 | self.ratingAlert = alertView; 326 | [alertView show]; 327 | #pragma clang diagnostic pop 328 | } 329 | } 330 | 331 | if (delegate && [delegate respondsToSelector:@selector(appiraterDidDisplayAlert:)]) { 332 | [delegate appiraterDidDisplayAlert:self]; 333 | } 334 | } 335 | 336 | - (void)showRatingAlert 337 | { 338 | [self showRatingAlert:true]; 339 | } 340 | 341 | // is this an ok time to show the alert? (regardless of whether the rating conditions have been met) 342 | // 343 | // things checked here: 344 | // * connectivity with network 345 | // * whether user has rated before 346 | // * whether user has declined to rate 347 | // * whether rating alert is currently showing visibly 348 | // things NOT checked here: 349 | // * time since first launch 350 | // * number of uses of app 351 | // * number of significant events 352 | // * time since last reminder 353 | - (BOOL)ratingAlertIsAppropriate { 354 | return ([self connectedToNetwork] 355 | && ![self userHasDeclinedToRate] 356 | && ![self isRatingAlertVisible] 357 | && ![self userHasRatedCurrentVersion]); 358 | } 359 | 360 | // have the rating conditions been met/earned? (regardless of whether this would be a moment when it's appropriate to show a new rating alert) 361 | // 362 | // things checked here: 363 | // * time since first launch 364 | // * number of uses of app 365 | // * number of significant events 366 | // * time since last reminder 367 | // things NOT checked here: 368 | // * connectivity with network 369 | // * whether user has rated before 370 | // * whether user has declined to rate 371 | // * whether rating alert is currently showing visibly 372 | - (BOOL)ratingConditionsHaveBeenMet { 373 | if (_debug) 374 | return YES; 375 | 376 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 377 | 378 | NSDate *dateOfFirstLaunch = [NSDate dateWithTimeIntervalSince1970:[userDefaults doubleForKey:kAppiraterFirstUseDate]]; 379 | NSTimeInterval timeSinceFirstLaunch = [[NSDate date] timeIntervalSinceDate:dateOfFirstLaunch]; 380 | NSTimeInterval timeUntilRate = 60 * 60 * 24 * _daysUntilPrompt; 381 | if (timeSinceFirstLaunch < timeUntilRate) 382 | return NO; 383 | 384 | // check if the app has been used enough 385 | NSInteger useCount = [userDefaults integerForKey:kAppiraterUseCount]; 386 | if (useCount < _usesUntilPrompt) 387 | return NO; 388 | 389 | // check if the user has done enough significant events 390 | NSInteger sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount]; 391 | if (sigEventCount < _significantEventsUntilPrompt) 392 | return NO; 393 | 394 | // if the user wanted to be reminded later, has enough time passed? 395 | NSDate *reminderRequestDate = [NSDate dateWithTimeIntervalSince1970:[userDefaults doubleForKey:kAppiraterReminderRequestDate]]; 396 | NSTimeInterval timeSinceReminderRequest = [[NSDate date] timeIntervalSinceDate:reminderRequestDate]; 397 | NSTimeInterval timeUntilReminder = 60 * 60 * 24 * _timeBeforeReminding; 398 | if (timeSinceReminderRequest < timeUntilReminder) 399 | return NO; 400 | 401 | return YES; 402 | } 403 | 404 | - (void)incrementUseCount { 405 | // get the app's version 406 | NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey]; 407 | 408 | // get the version number that we've been tracking 409 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 410 | NSString *trackingVersion = [userDefaults stringForKey:kAppiraterCurrentVersion]; 411 | if (trackingVersion == nil) 412 | { 413 | trackingVersion = version; 414 | [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; 415 | } 416 | 417 | if (_debug) 418 | NSLog(@"APPIRATER Tracking version: %@", trackingVersion); 419 | 420 | if ([trackingVersion isEqualToString:version]) 421 | { 422 | // check if the first use date has been set. if not, set it. 423 | NSTimeInterval timeInterval = [userDefaults doubleForKey:kAppiraterFirstUseDate]; 424 | if (timeInterval == 0) 425 | { 426 | timeInterval = [[NSDate date] timeIntervalSince1970]; 427 | [userDefaults setDouble:timeInterval forKey:kAppiraterFirstUseDate]; 428 | } 429 | 430 | // increment the use count 431 | NSInteger useCount = [userDefaults integerForKey:kAppiraterUseCount]; 432 | useCount++; 433 | [userDefaults setInteger:useCount forKey:kAppiraterUseCount]; 434 | if (_debug) 435 | NSLog(@"APPIRATER Use count: %@", @(useCount)); 436 | } 437 | else 438 | { 439 | // it's a new version of the app, so restart tracking 440 | [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; 441 | [userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterFirstUseDate]; 442 | [userDefaults setInteger:1 forKey:kAppiraterUseCount]; 443 | [userDefaults setInteger:0 forKey:kAppiraterSignificantEventCount]; 444 | [userDefaults setBool:NO forKey:kAppiraterRatedCurrentVersion]; 445 | [userDefaults setBool:NO forKey:kAppiraterDeclinedToRate]; 446 | [userDefaults setDouble:0 forKey:kAppiraterReminderRequestDate]; 447 | } 448 | 449 | [userDefaults synchronize]; 450 | } 451 | 452 | - (void)incrementSignificantEventCount { 453 | // get the app's version 454 | NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey]; 455 | 456 | // get the version number that we've been tracking 457 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 458 | NSString *trackingVersion = [userDefaults stringForKey:kAppiraterCurrentVersion]; 459 | if (trackingVersion == nil) 460 | { 461 | trackingVersion = version; 462 | [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; 463 | } 464 | 465 | if (_debug) 466 | NSLog(@"APPIRATER Tracking version: %@", trackingVersion); 467 | 468 | if ([trackingVersion isEqualToString:version]) 469 | { 470 | // check if the first use date has been set. if not, set it. 471 | NSTimeInterval timeInterval = [userDefaults doubleForKey:kAppiraterFirstUseDate]; 472 | if (timeInterval == 0) 473 | { 474 | timeInterval = [[NSDate date] timeIntervalSince1970]; 475 | [userDefaults setDouble:timeInterval forKey:kAppiraterFirstUseDate]; 476 | } 477 | 478 | // increment the significant event count 479 | NSInteger sigEventCount = [userDefaults integerForKey:kAppiraterSignificantEventCount]; 480 | sigEventCount++; 481 | [userDefaults setInteger:sigEventCount forKey:kAppiraterSignificantEventCount]; 482 | if (_debug) 483 | NSLog(@"APPIRATER Significant event count: %@", @(sigEventCount)); 484 | } 485 | else 486 | { 487 | // it's a new version of the app, so restart tracking 488 | [userDefaults setObject:version forKey:kAppiraterCurrentVersion]; 489 | [userDefaults setDouble:0 forKey:kAppiraterFirstUseDate]; 490 | [userDefaults setInteger:0 forKey:kAppiraterUseCount]; 491 | [userDefaults setInteger:1 forKey:kAppiraterSignificantEventCount]; 492 | [userDefaults setBool:NO forKey:kAppiraterRatedCurrentVersion]; 493 | [userDefaults setBool:NO forKey:kAppiraterDeclinedToRate]; 494 | [userDefaults setDouble:0 forKey:kAppiraterReminderRequestDate]; 495 | } 496 | 497 | [userDefaults synchronize]; 498 | } 499 | 500 | - (void)incrementAndRate:(BOOL)canPromptForRating { 501 | [self incrementUseCount]; 502 | 503 | if (canPromptForRating && 504 | [self ratingConditionsHaveBeenMet] && 505 | [self ratingAlertIsAppropriate]) 506 | { 507 | dispatch_async(dispatch_get_main_queue(), 508 | ^{ 509 | [self showRatingAlert]; 510 | }); 511 | } 512 | } 513 | 514 | - (void)incrementSignificantEventAndRate:(BOOL)canPromptForRating { 515 | [self incrementSignificantEventCount]; 516 | 517 | if (canPromptForRating && 518 | [self ratingConditionsHaveBeenMet] && 519 | [self ratingAlertIsAppropriate]) 520 | { 521 | dispatch_async(dispatch_get_main_queue(), 522 | ^{ 523 | [self showRatingAlert]; 524 | }); 525 | } 526 | } 527 | 528 | - (BOOL)userHasDeclinedToRate { 529 | return [[NSUserDefaults standardUserDefaults] boolForKey:kAppiraterDeclinedToRate]; 530 | } 531 | 532 | - (BOOL)userHasRatedCurrentVersion { 533 | return [[NSUserDefaults standardUserDefaults] boolForKey:kAppiraterRatedCurrentVersion]; 534 | } 535 | 536 | #pragma GCC diagnostic push 537 | #pragma GCC diagnostic ignored "-Wdeprecated-implementations" 538 | + (void)appLaunched { 539 | [Appirater appLaunched:YES]; 540 | } 541 | #pragma GCC diagnostic pop 542 | 543 | + (void)appLaunched:(BOOL)canPromptForRating { 544 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), 545 | ^{ 546 | Appirater *a = [Appirater sharedInstance]; 547 | if (_debug) { 548 | dispatch_async(dispatch_get_main_queue(), 549 | ^{ 550 | [a showRatingAlert]; 551 | }); 552 | } else { 553 | [a incrementAndRate:canPromptForRating]; 554 | } 555 | }); 556 | } 557 | 558 | - (BOOL)isRatingAlertVisible { 559 | if (NSStringFromClass([UIAlertController class]) != nil) { 560 | return ((UIAlertController *)self.ratingAlert).view.superview != nil; 561 | } else { 562 | #pragma clang diagnostic push 563 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 564 | return ((UIAlertView *)self.ratingAlert).visible; 565 | #pragma clang diagnostic pop 566 | } 567 | } 568 | 569 | - (void)hideRatingAlert { 570 | if ([self isRatingAlertVisible]) { 571 | if (_debug) { 572 | NSLog(@"APPIRATER Hiding Alert"); 573 | } 574 | if ([self.ratingAlert respondsToSelector:@selector(dismissWithClickedButtonIndex:animated:)]) { 575 | [self.ratingAlert dismissWithClickedButtonIndex:-1 animated:NO]; 576 | } else { 577 | [self.ratingAlert dismissViewControllerAnimated:NO completion:nil]; 578 | } 579 | } 580 | } 581 | 582 | + (void)appWillResignActive { 583 | if (_debug) 584 | NSLog(@"APPIRATER appWillResignActive"); 585 | [[Appirater sharedInstance] hideRatingAlert]; 586 | } 587 | 588 | + (void)appEnteredForeground:(BOOL)canPromptForRating { 589 | Appirater *a = [Appirater sharedInstance]; 590 | [a.eventQueue addOperationWithBlock:^{ 591 | [[Appirater sharedInstance] incrementAndRate:canPromptForRating]; 592 | }]; 593 | } 594 | 595 | + (void)userDidSignificantEvent:(BOOL)canPromptForRating { 596 | Appirater *a = [Appirater sharedInstance]; 597 | [a.eventQueue addOperationWithBlock:^{ 598 | [[Appirater sharedInstance] incrementSignificantEventAndRate:canPromptForRating]; 599 | }]; 600 | } 601 | 602 | #pragma GCC diagnostic push 603 | #pragma GCC diagnostic ignored "-Wdeprecated-implementations" 604 | + (void)showPrompt { 605 | [Appirater tryToShowPrompt]; 606 | } 607 | #pragma GCC diagnostic pop 608 | 609 | + (void)tryToShowPrompt { 610 | [[Appirater sharedInstance] showPromptWithChecks:true 611 | displayRateLaterButton:true]; 612 | } 613 | 614 | + (void)forceShowPrompt:(BOOL)displayRateLaterButton { 615 | [[Appirater sharedInstance] showPromptWithChecks:false 616 | displayRateLaterButton:displayRateLaterButton]; 617 | } 618 | 619 | - (void)showPromptWithChecks:(BOOL)withChecks 620 | displayRateLaterButton:(BOOL)displayRateLaterButton { 621 | if (withChecks == NO || [self ratingAlertIsAppropriate]) { 622 | [self showRatingAlert:displayRateLaterButton]; 623 | } 624 | } 625 | 626 | + (id)getRootViewController { 627 | UIWindow *window = [[UIApplication sharedApplication] keyWindow]; 628 | if (window.windowLevel != UIWindowLevelNormal) { 629 | NSArray *windows = [[UIApplication sharedApplication] windows]; 630 | for(window in windows) { 631 | if (window.windowLevel == UIWindowLevelNormal) { 632 | break; 633 | } 634 | } 635 | } 636 | 637 | return [Appirater iterateSubViewsForViewController:window]; // iOS 8+ deep traverse 638 | } 639 | 640 | + (id)iterateSubViewsForViewController:(UIView *) parentView { 641 | for (UIView *subView in [parentView subviews]) { 642 | UIResponder *responder = [subView nextResponder]; 643 | if([responder isKindOfClass:[UIViewController class]]) { 644 | return [self topMostViewController: (UIViewController *) responder]; 645 | } 646 | id found = [Appirater iterateSubViewsForViewController:subView]; 647 | if( nil != found) { 648 | return found; 649 | } 650 | } 651 | return nil; 652 | } 653 | 654 | + (UIViewController *) topMostViewController: (UIViewController *) controller { 655 | BOOL isPresenting = NO; 656 | do { 657 | // this path is called only on iOS 6+, so -presentedViewController is fine here. 658 | UIViewController *presented = [controller presentedViewController]; 659 | isPresenting = presented != nil; 660 | if(presented != nil) { 661 | controller = presented; 662 | } 663 | 664 | } while (isPresenting); 665 | 666 | return controller; 667 | } 668 | 669 | + (void)rateApp { 670 | 671 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 672 | [userDefaults setBool:YES forKey:kAppiraterRatedCurrentVersion]; 673 | [userDefaults synchronize]; 674 | 675 | // Use the built SKStoreReviewController if available (available from iOS 10.3 upwards) 676 | #pragma clang diagnostic push 677 | #pragma clang diagnostic ignored "-Wunguarded-availability" 678 | if (NSStringFromClass([SKStoreReviewController class]) != nil) { 679 | [SKStoreReviewController requestReview]; 680 | #pragma clang diagnostic pop 681 | return; 682 | } 683 | 684 | //Use the in-app StoreKit view if available (iOS 6) and imported. This works in the simulator. 685 | if (![Appirater sharedInstance].openInAppStore && NSStringFromClass([SKStoreProductViewController class]) != nil) { 686 | 687 | SKStoreProductViewController *storeViewController = [[SKStoreProductViewController alloc] init]; 688 | NSNumber *appId = [NSNumber numberWithInteger:_appId.integerValue]; 689 | [storeViewController loadProductWithParameters:@{SKStoreProductParameterITunesItemIdentifier:appId} completionBlock:nil]; 690 | storeViewController.delegate = self.sharedInstance; 691 | 692 | id delegate = self.sharedInstance.delegate; 693 | if ([delegate respondsToSelector:@selector(appiraterWillPresentModalView:animated:)]) { 694 | [delegate appiraterWillPresentModalView:self.sharedInstance animated:_usesAnimation]; 695 | } 696 | [[self getRootViewController] presentViewController:storeViewController animated:_usesAnimation completion:^{ 697 | [self setModalOpen:YES]; 698 | }]; 699 | 700 | //Use the standard openUrl method if StoreKit is unavailable. 701 | } else { 702 | 703 | #if TARGET_IPHONE_SIMULATOR 704 | NSLog(@"APPIRATER NOTE: iTunes App Store is not supported on the iOS simulator. Unable to open App Store page."); 705 | #else 706 | NSString *reviewURL = [templateReviewURL stringByReplacingOccurrencesOfString:@"APP_ID" withString:_appId]; 707 | 708 | // iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131 709 | // Fixes condition @see https://github.com/arashpayan/appirater/issues/205 710 | if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) { 711 | reviewURL = [templateReviewURLiOS7 stringByReplacingOccurrencesOfString:@"APP_ID" withString:_appId]; 712 | } 713 | // iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182 714 | else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) 715 | { 716 | reviewURL = [templateReviewURLiOS8 stringByReplacingOccurrencesOfString:@"APP_ID" withString:_appId]; 717 | } 718 | 719 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:reviewURL]]; 720 | #endif 721 | } 722 | } 723 | 724 | #pragma clang diagnostic push 725 | #pragma clang diagnostic ignored "-Wdeprecated-implementations" 726 | - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { 727 | [self alertViewDidDismissWithButtonIndex:buttonIndex]; 728 | } 729 | #pragma clang diagnostic pop 730 | 731 | - (void)alertViewDidDismissWithButtonIndex:(NSInteger)buttonIndex { 732 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 733 | 734 | id delegate = _delegate; 735 | 736 | switch (buttonIndex) { 737 | case 0: 738 | { 739 | // they don't want to rate it 740 | [userDefaults setBool:YES forKey:kAppiraterDeclinedToRate]; 741 | [userDefaults synchronize]; 742 | if(delegate && [delegate respondsToSelector:@selector(appiraterDidDeclineToRate:)]){ 743 | [delegate appiraterDidDeclineToRate:self]; 744 | } 745 | break; 746 | } 747 | case 1: 748 | { 749 | // they want to rate it 750 | [Appirater rateApp]; 751 | if(delegate&& [delegate respondsToSelector:@selector(appiraterDidOptToRate:)]){ 752 | [delegate appiraterDidOptToRate:self]; 753 | } 754 | break; 755 | } 756 | case 2: 757 | // remind them later 758 | [userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterReminderRequestDate]; 759 | [userDefaults synchronize]; 760 | if(delegate && [delegate respondsToSelector:@selector(appiraterDidOptToRemindLater:)]){ 761 | [delegate appiraterDidOptToRemindLater:self]; 762 | } 763 | break; 764 | default: 765 | break; 766 | } 767 | 768 | self.ratingAlert = nil; 769 | } 770 | 771 | //Delegate call from the StoreKit view. 772 | - (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController { 773 | [Appirater closeModal]; 774 | } 775 | 776 | //Close the in-app rating (StoreKit) view and restore the previous status bar style. 777 | + (void)closeModal { 778 | if (_modalOpen) { 779 | BOOL usedAnimation = _usesAnimation; 780 | [self setModalOpen:NO]; 781 | 782 | // get the top most controller (= the StoreKit Controller) and dismiss it 783 | UIViewController *presentingController = [UIApplication sharedApplication].keyWindow.rootViewController; 784 | presentingController = [self topMostViewController: presentingController]; 785 | [presentingController dismissViewControllerAnimated:_usesAnimation completion:^{ 786 | id delegate = self.sharedInstance.delegate; 787 | if ([delegate respondsToSelector:@selector(appiraterDidDismissModalView:animated:)]) { 788 | [delegate appiraterDidDismissModalView:(Appirater *)self animated:usedAnimation]; 789 | } 790 | }]; 791 | [self.class setStatusBarStyle:(UIStatusBarStyle)nil]; 792 | } 793 | } 794 | 795 | @end 796 | -------------------------------------------------------------------------------- /Appirater.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Appirater", 3 | "version": "2.3.0", 4 | "platforms": { 5 | "ios": "5.0" 6 | }, 7 | "summary": "A utility that reminds your iPhone app's users to review the app.", 8 | "homepage": "https://arashpayan.com/blog/2009/09/07/presenting-appirater/", 9 | "authors": { 10 | "Arash Payan": "arash@ara.sh" 11 | }, 12 | "source": { 13 | "git": "https://github.com/arashpayan/appirater.git", 14 | "tag": "2.3.0" 15 | }, 16 | "source_files": "*.{h,m}", 17 | "resource_bundles": { 18 | "Appirater": [ 19 | "*.lproj" 20 | ] 21 | }, 22 | "requires_arc": true, 23 | "frameworks": [ 24 | "CFNetwork", 25 | "SystemConfiguration" 26 | ], 27 | "weak_frameworks": "StoreKit", 28 | "license": { 29 | "type": "MIT", 30 | "text": "Copyright 2017. Arash Payan. This library is distributed under the terms of the MIT/X11." 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Appirater.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 827211DF1EB3639D00DBFBEB /* Appirater.m in Sources */ = {isa = PBXBuildFile; fileRef = 827211DE1EB3639D00DBFBEB /* Appirater.m */; }; 11 | 827211E11EB363A700DBFBEB /* AppiraterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 827211E01EB363A700DBFBEB /* AppiraterDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | 8272126A1EB3640400DBFBEB /* AppiraterLocalizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8272126C1EB3640400DBFBEB /* AppiraterLocalizable.strings */; }; 13 | 8272128E1EB3650300DBFBEB /* Appirater.h in Headers */ = {isa = PBXBuildFile; fileRef = 8272128D1EB3650300DBFBEB /* Appirater.h */; settings = {ATTRIBUTES = (Public, ); }; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXFileReference section */ 17 | 827211D31EB3633600DBFBEB /* Appirater.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Appirater.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | 827211D71EB3633600DBFBEB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 19 | 827211DE1EB3639D00DBFBEB /* Appirater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Appirater.m; sourceTree = SOURCE_ROOT; }; 20 | 827211E01EB363A700DBFBEB /* AppiraterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppiraterDelegate.h; sourceTree = SOURCE_ROOT; }; 21 | 8272126B1EB3640400DBFBEB /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 22 | 8272126D1EB3640800DBFBEB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 23 | 8272126E1EB3640A00DBFBEB /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 24 | 8272126F1EB3640B00DBFBEB /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 25 | 827212701EB3640D00DBFBEB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 26 | 827212711EB3640E00DBFBEB /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 27 | 827212721EB3640F00DBFBEB /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 28 | 827212731EB3641000DBFBEB /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 29 | 827212741EB3641100DBFBEB /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 30 | 827212751EB3641200DBFBEB /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 31 | 827212761EB3641300DBFBEB /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 32 | 827212771EB3641400DBFBEB /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 33 | 827212781EB3641600DBFBEB /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 34 | 827212791EB3641700DBFBEB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 35 | 8272127A1EB3641800DBFBEB /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 36 | 8272127B1EB3641900DBFBEB /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 37 | 8272127C1EB3641B00DBFBEB /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 38 | 8272127D1EB3641C00DBFBEB /* ms */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ms; path = ms.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 39 | 8272127E1EB3641E00DBFBEB /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 40 | 8272127F1EB3641F00DBFBEB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 41 | 827212801EB3642000DBFBEB /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 42 | 827212811EB3642100DBFBEB /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/AppiraterLocalizable.strings"; sourceTree = ""; }; 43 | 827212821EB3642200DBFBEB /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 44 | 827212831EB3642300DBFBEB /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 45 | 827212841EB3642400DBFBEB /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 46 | 827212851EB3642500DBFBEB /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 47 | 827212861EB3642700DBFBEB /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 48 | 827212871EB3642800DBFBEB /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 49 | 827212881EB3642900DBFBEB /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 50 | 827212891EB3642A00DBFBEB /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 51 | 8272128A1EB3642B00DBFBEB /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/AppiraterLocalizable.strings; sourceTree = ""; }; 52 | 8272128B1EB3642D00DBFBEB /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/AppiraterLocalizable.strings"; sourceTree = ""; }; 53 | 8272128C1EB3642E00DBFBEB /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/AppiraterLocalizable.strings"; sourceTree = ""; }; 54 | 8272128D1EB3650300DBFBEB /* Appirater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Appirater.h; sourceTree = SOURCE_ROOT; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | 827211CF1EB3633600DBFBEB /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 827211C91EB3633600DBFBEB = { 69 | isa = PBXGroup; 70 | children = ( 71 | 827211D51EB3633600DBFBEB /* Appirater */, 72 | 827211D41EB3633600DBFBEB /* Products */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 827211D41EB3633600DBFBEB /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 827211D31EB3633600DBFBEB /* Appirater.framework */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 827211D51EB3633600DBFBEB /* Appirater */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 8272128D1EB3650300DBFBEB /* Appirater.h */, 88 | 827211DE1EB3639D00DBFBEB /* Appirater.m */, 89 | 827211E01EB363A700DBFBEB /* AppiraterDelegate.h */, 90 | 8272126C1EB3640400DBFBEB /* AppiraterLocalizable.strings */, 91 | 827211D71EB3633600DBFBEB /* Info.plist */, 92 | ); 93 | path = Appirater; 94 | sourceTree = ""; 95 | }; 96 | /* End PBXGroup section */ 97 | 98 | /* Begin PBXHeadersBuildPhase section */ 99 | 827211D01EB3633600DBFBEB /* Headers */ = { 100 | isa = PBXHeadersBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | 827211E11EB363A700DBFBEB /* AppiraterDelegate.h in Headers */, 104 | 8272128E1EB3650300DBFBEB /* Appirater.h in Headers */, 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | /* End PBXHeadersBuildPhase section */ 109 | 110 | /* Begin PBXNativeTarget section */ 111 | 827211D21EB3633600DBFBEB /* Appirater */ = { 112 | isa = PBXNativeTarget; 113 | buildConfigurationList = 827211DB1EB3633600DBFBEB /* Build configuration list for PBXNativeTarget "Appirater" */; 114 | buildPhases = ( 115 | 827211CE1EB3633600DBFBEB /* Sources */, 116 | 827211CF1EB3633600DBFBEB /* Frameworks */, 117 | 827211D01EB3633600DBFBEB /* Headers */, 118 | 827211D11EB3633600DBFBEB /* Resources */, 119 | ); 120 | buildRules = ( 121 | ); 122 | dependencies = ( 123 | ); 124 | name = Appirater; 125 | productName = Appirater; 126 | productReference = 827211D31EB3633600DBFBEB /* Appirater.framework */; 127 | productType = "com.apple.product-type.framework"; 128 | }; 129 | /* End PBXNativeTarget section */ 130 | 131 | /* Begin PBXProject section */ 132 | 827211CA1EB3633600DBFBEB /* Project object */ = { 133 | isa = PBXProject; 134 | attributes = { 135 | LastUpgradeCheck = 0830; 136 | ORGANIZATIONNAME = "Arash Payan"; 137 | TargetAttributes = { 138 | 827211D21EB3633600DBFBEB = { 139 | CreatedOnToolsVersion = 8.3.2; 140 | ProvisioningStyle = Automatic; 141 | }; 142 | }; 143 | }; 144 | buildConfigurationList = 827211CD1EB3633600DBFBEB /* Build configuration list for PBXProject "Appirater" */; 145 | compatibilityVersion = "Xcode 3.2"; 146 | developmentRegion = English; 147 | hasScannedForEncodings = 0; 148 | knownRegions = ( 149 | en, 150 | ar, 151 | ca, 152 | cs, 153 | da, 154 | de, 155 | el, 156 | es, 157 | fa, 158 | fi, 159 | fr, 160 | he, 161 | hu, 162 | id, 163 | it, 164 | ja, 165 | ko, 166 | ms, 167 | nb, 168 | nl, 169 | pl, 170 | "pt-BR", 171 | pt, 172 | ro, 173 | ru, 174 | sk, 175 | sv, 176 | th, 177 | tr, 178 | uk, 179 | vi, 180 | "zh-Hans", 181 | "zh-Hant", 182 | ); 183 | mainGroup = 827211C91EB3633600DBFBEB; 184 | productRefGroup = 827211D41EB3633600DBFBEB /* Products */; 185 | projectDirPath = ""; 186 | projectRoot = ""; 187 | targets = ( 188 | 827211D21EB3633600DBFBEB /* Appirater */, 189 | ); 190 | }; 191 | /* End PBXProject section */ 192 | 193 | /* Begin PBXResourcesBuildPhase section */ 194 | 827211D11EB3633600DBFBEB /* Resources */ = { 195 | isa = PBXResourcesBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | 8272126A1EB3640400DBFBEB /* AppiraterLocalizable.strings in Resources */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXResourcesBuildPhase section */ 203 | 204 | /* Begin PBXSourcesBuildPhase section */ 205 | 827211CE1EB3633600DBFBEB /* Sources */ = { 206 | isa = PBXSourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | 827211DF1EB3639D00DBFBEB /* Appirater.m in Sources */, 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | /* End PBXSourcesBuildPhase section */ 214 | 215 | /* Begin PBXVariantGroup section */ 216 | 8272126C1EB3640400DBFBEB /* AppiraterLocalizable.strings */ = { 217 | isa = PBXVariantGroup; 218 | children = ( 219 | 8272126B1EB3640400DBFBEB /* ja */, 220 | 8272126D1EB3640800DBFBEB /* en */, 221 | 8272126E1EB3640A00DBFBEB /* ar */, 222 | 8272126F1EB3640B00DBFBEB /* ca */, 223 | 827212701EB3640D00DBFBEB /* cs */, 224 | 827212711EB3640E00DBFBEB /* da */, 225 | 827212721EB3640F00DBFBEB /* de */, 226 | 827212731EB3641000DBFBEB /* el */, 227 | 827212741EB3641100DBFBEB /* es */, 228 | 827212751EB3641200DBFBEB /* fa */, 229 | 827212761EB3641300DBFBEB /* fi */, 230 | 827212771EB3641400DBFBEB /* fr */, 231 | 827212781EB3641600DBFBEB /* he */, 232 | 827212791EB3641700DBFBEB /* hu */, 233 | 8272127A1EB3641800DBFBEB /* id */, 234 | 8272127B1EB3641900DBFBEB /* it */, 235 | 8272127C1EB3641B00DBFBEB /* ko */, 236 | 8272127D1EB3641C00DBFBEB /* ms */, 237 | 8272127E1EB3641E00DBFBEB /* nb */, 238 | 8272127F1EB3641F00DBFBEB /* nl */, 239 | 827212801EB3642000DBFBEB /* pl */, 240 | 827212811EB3642100DBFBEB /* pt-BR */, 241 | 827212821EB3642200DBFBEB /* pt */, 242 | 827212831EB3642300DBFBEB /* ro */, 243 | 827212841EB3642400DBFBEB /* ru */, 244 | 827212851EB3642500DBFBEB /* sk */, 245 | 827212861EB3642700DBFBEB /* sv */, 246 | 827212871EB3642800DBFBEB /* th */, 247 | 827212881EB3642900DBFBEB /* tr */, 248 | 827212891EB3642A00DBFBEB /* uk */, 249 | 8272128A1EB3642B00DBFBEB /* vi */, 250 | 8272128B1EB3642D00DBFBEB /* zh-Hans */, 251 | 8272128C1EB3642E00DBFBEB /* zh-Hant */, 252 | ); 253 | name = AppiraterLocalizable.strings; 254 | sourceTree = SOURCE_ROOT; 255 | }; 256 | /* End PBXVariantGroup section */ 257 | 258 | /* Begin XCBuildConfiguration section */ 259 | 827211D91EB3633600DBFBEB /* Debug */ = { 260 | isa = XCBuildConfiguration; 261 | buildSettings = { 262 | ALWAYS_SEARCH_USER_PATHS = NO; 263 | CLANG_ANALYZER_NONNULL = YES; 264 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 265 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 266 | CLANG_CXX_LIBRARY = "libc++"; 267 | CLANG_ENABLE_MODULES = YES; 268 | CLANG_ENABLE_OBJC_ARC = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_CONSTANT_CONVERSION = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INFINITE_RECURSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 279 | CLANG_WARN_UNREACHABLE_CODE = YES; 280 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 281 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 282 | COPY_PHASE_STRIP = NO; 283 | CURRENT_PROJECT_VERSION = 1; 284 | DEBUG_INFORMATION_FORMAT = dwarf; 285 | ENABLE_STRICT_OBJC_MSGSEND = YES; 286 | ENABLE_TESTABILITY = YES; 287 | GCC_C_LANGUAGE_STANDARD = gnu99; 288 | GCC_DYNAMIC_NO_PIC = NO; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_OPTIMIZATION_LEVEL = 0; 291 | GCC_PREPROCESSOR_DEFINITIONS = ( 292 | "DEBUG=1", 293 | "$(inherited)", 294 | ); 295 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 296 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 297 | GCC_WARN_UNDECLARED_SELECTOR = YES; 298 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 299 | GCC_WARN_UNUSED_FUNCTION = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 302 | MTL_ENABLE_DEBUG_INFO = YES; 303 | ONLY_ACTIVE_ARCH = YES; 304 | SDKROOT = iphoneos; 305 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 306 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 307 | TARGETED_DEVICE_FAMILY = "1,2"; 308 | VERSIONING_SYSTEM = "apple-generic"; 309 | VERSION_INFO_PREFIX = ""; 310 | }; 311 | name = Debug; 312 | }; 313 | 827211DA1EB3633600DBFBEB /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ALWAYS_SEARCH_USER_PATHS = NO; 317 | CLANG_ANALYZER_NONNULL = YES; 318 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 319 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 320 | CLANG_CXX_LIBRARY = "libc++"; 321 | CLANG_ENABLE_MODULES = YES; 322 | CLANG_ENABLE_OBJC_ARC = YES; 323 | CLANG_WARN_BOOL_CONVERSION = YES; 324 | CLANG_WARN_CONSTANT_CONVERSION = YES; 325 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 326 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 327 | CLANG_WARN_EMPTY_BODY = YES; 328 | CLANG_WARN_ENUM_CONVERSION = YES; 329 | CLANG_WARN_INFINITE_RECURSION = YES; 330 | CLANG_WARN_INT_CONVERSION = YES; 331 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | CURRENT_PROJECT_VERSION = 1; 338 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 339 | ENABLE_NS_ASSERTIONS = NO; 340 | ENABLE_STRICT_OBJC_MSGSEND = YES; 341 | GCC_C_LANGUAGE_STANDARD = gnu99; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 350 | MTL_ENABLE_DEBUG_INFO = NO; 351 | SDKROOT = iphoneos; 352 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | VALIDATE_PRODUCT = YES; 355 | VERSIONING_SYSTEM = "apple-generic"; 356 | VERSION_INFO_PREFIX = ""; 357 | }; 358 | name = Release; 359 | }; 360 | 827211DC1EB3633600DBFBEB /* Debug */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | CODE_SIGN_IDENTITY = ""; 364 | DEFINES_MODULE = YES; 365 | DYLIB_COMPATIBILITY_VERSION = 1; 366 | DYLIB_CURRENT_VERSION = 1; 367 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 368 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 369 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 370 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 371 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 372 | PRODUCT_BUNDLE_IDENTIFIER = com.ap.Appirater; 373 | PRODUCT_NAME = "$(TARGET_NAME)"; 374 | SKIP_INSTALL = YES; 375 | SWIFT_VERSION = 3.0; 376 | }; 377 | name = Debug; 378 | }; 379 | 827211DD1EB3633600DBFBEB /* Release */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | CODE_SIGN_IDENTITY = ""; 383 | DEFINES_MODULE = YES; 384 | DYLIB_COMPATIBILITY_VERSION = 1; 385 | DYLIB_CURRENT_VERSION = 1; 386 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 387 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 388 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 389 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 390 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 391 | PRODUCT_BUNDLE_IDENTIFIER = com.ap.Appirater; 392 | PRODUCT_NAME = "$(TARGET_NAME)"; 393 | SKIP_INSTALL = YES; 394 | SWIFT_VERSION = 3.0; 395 | }; 396 | name = Release; 397 | }; 398 | /* End XCBuildConfiguration section */ 399 | 400 | /* Begin XCConfigurationList section */ 401 | 827211CD1EB3633600DBFBEB /* Build configuration list for PBXProject "Appirater" */ = { 402 | isa = XCConfigurationList; 403 | buildConfigurations = ( 404 | 827211D91EB3633600DBFBEB /* Debug */, 405 | 827211DA1EB3633600DBFBEB /* Release */, 406 | ); 407 | defaultConfigurationIsVisible = 0; 408 | defaultConfigurationName = Release; 409 | }; 410 | 827211DB1EB3633600DBFBEB /* Build configuration list for PBXNativeTarget "Appirater" */ = { 411 | isa = XCConfigurationList; 412 | buildConfigurations = ( 413 | 827211DC1EB3633600DBFBEB /* Debug */, 414 | 827211DD1EB3633600DBFBEB /* Release */, 415 | ); 416 | defaultConfigurationIsVisible = 0; 417 | defaultConfigurationName = Release; 418 | }; 419 | /* End XCConfigurationList section */ 420 | }; 421 | rootObject = 827211CA1EB3633600DBFBEB /* Project object */; 422 | } 423 | -------------------------------------------------------------------------------- /Appirater.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Appirater.xcodeproj/project.xcworkspace/xcuserdata/Arbeit.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arashpayan/appirater/400479259fc55c5611f60c586491c504c0c8fb3c/Appirater.xcodeproj/project.xcworkspace/xcuserdata/Arbeit.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Appirater.xcodeproj/xcshareddata/xcschemes/Appirater.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Appirater.xcodeproj/xcuserdata/Arbeit.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Appirater.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 827211D21EB3633600DBFBEB 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /AppiraterDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppiraterDelegate.h 3 | // Banana Stand 4 | // 5 | // Created by Robert Haining on 9/25/12. 6 | // Copyright (c) 2012 News.me. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class Appirater; 12 | 13 | @protocol AppiraterDelegate 14 | 15 | @optional 16 | -(BOOL)appiraterShouldDisplayAlert:(Appirater *)appirater; 17 | -(void)appiraterDidDisplayAlert:(Appirater *)appirater; 18 | -(void)appiraterDidDeclineToRate:(Appirater *)appirater; 19 | -(void)appiraterDidOptToRate:(Appirater *)appirater; 20 | -(void)appiraterDidOptToRemindLater:(Appirater *)appirater; 21 | -(void)appiraterWillPresentModalView:(Appirater *)appirater animated:(BOOL)animated; 22 | -(void)appiraterDidDismissModalView:(Appirater *)appirater animated:(BOOL)animated; 23 | @end 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | Version 2.3.0 *(2017-10-30)* 5 | ---------------------------- 6 | * if (iOS >= 8 && iOS <= 10.3) { use UIAlertController } 7 | * Note when the SKStoreReviewController is called, even though we don't get feedback 8 | 9 | Version 2.2.0 *(2017-09-23)* 10 | ---------------------------- 11 | * Use SKStoreReviewController if available 12 | * Available on iOS > 10.3 13 | * You'll need to link the StoreKit Framework 14 | * Armenian localization 15 | * Fix delegate not being set after Appirater initialization (Issue #215) 16 | 17 | Version 2.1.0 *(2016-11-04)* 18 | ---------------------------- 19 | * Fix and suppress various Xcode warnings 20 | * Switch to NSURLSession 21 | * Serialize incrementing events 22 | 23 | Version 2.0.4 *(2014-09-18)* 24 | ---------------------------- 25 | * Change: Better URL for iOS 7.1 and 8 support 26 | 27 | Version 2.0.3 *(2014-05-14)* 28 | ---------------------------- 29 | * New: Make alert content customizable 30 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | --------------- 3 | 4 | Appirater is a class that you can drop into any iPhone app (iOS 4.0 or later) that will help remind your users to review your app on the App Store. The code is released under the MIT/X11, so feel free to modify and share your changes with the world. Read on below for how to get started. If you need any help using, the library, post your questions on [Stack Overflow](https://stackoverflow.com/) under the `appirater` tag. 5 | 6 | Getting Started 7 | --------------- 8 | 9 | ### CocoaPods 10 | To add Appirater to your app, add `pod "Appirater"` to your Podfile. 11 | 12 | Configuration 13 | ------------- 14 | 1. Appirater provides class methods to configure its behavior. See [`Appirater.h`] [Appirater.h] for more information. 15 | 16 | ```objc 17 | [Appirater setAppId:@"552035781"]; 18 | [Appirater setDaysUntilPrompt:1]; 19 | [Appirater setUsesUntilPrompt:10]; 20 | [Appirater setSignificantEventsUntilPrompt:-1]; 21 | [Appirater setTimeBeforeReminding:2]; 22 | [Appirater setDebug:YES]; 23 | ``` 24 | 25 | 2. Call `[Appirater setAppId:@"yourAppId"]` with the app id provided by Apple. A good place to do this is at the beginning of your app delegate's `application:didFinishLaunchingWithOptions:` method. 26 | 3. Call `[Appirater appLaunched:YES]` at the end of your app delegate's `application:didFinishLaunchingWithOptions:` method. 27 | 4. Call `[Appirater appEnteredForeground:YES]` in your app delegate's `applicationWillEnterForeground:` method. 28 | 5. (OPTIONAL) Call `[Appirater userDidSignificantEvent:YES]` when the user does something 'significant' in the app. 29 | 30 | ### Development 31 | Setting `[Appirater setDebug:YES]` will ensure that the rating request is shown each time the app is launched. 32 | 33 | ### Production 34 | Make sure you set `[Appirater setDebug:NO]` to ensure the request is not shown every time the app is launched. Also make sure that each of these components are set in the `application:didFinishLaunchingWithOptions:` method. 35 | 36 | This example states that the rating request is only shown when the app has been launched 5 times **and** after 7 days. 37 | 38 | ```objc 39 | [Appirater setAppId:@"770699556"]; 40 | [Appirater setDaysUntilPrompt:7]; 41 | [Appirater setUsesUntilPrompt:5]; 42 | [Appirater setSignificantEventsUntilPrompt:-1]; 43 | [Appirater setTimeBeforeReminding:2]; 44 | [Appirater setDebug:NO]; 45 | [Appirater appLaunched:YES]; 46 | ``` 47 | 48 | If you wanted to show the request after 5 days only you can set the following: 49 | 50 | ```objc 51 | [Appirater setAppId:@"770699556"]; 52 | [Appirater setDaysUntilPrompt:5]; 53 | [Appirater setUsesUntilPrompt:0]; 54 | [Appirater setSignificantEventsUntilPrompt:-1]; 55 | [Appirater setTimeBeforeReminding:2]; 56 | [Appirater setDebug:NO]; 57 | [Appirater appLaunched:YES]; 58 | ``` 59 | 60 | SKStoreReviewController 61 | ---------------------- 62 | In iOS 10.3, [SKStoreReviewController](https://developer.apple.com/library/content/releasenotes/General/WhatsNewIniOS/Articles/iOS10_3.html) was introduced which allows rating directly within the app without any additional setup. 63 | 64 | Appirater automatically uses `SKStoreReviewController` if available. You'll need to manually link `StoreKit` in your App however. 65 | 66 | If `SKStoreReviewController` is used, Appirater is used only to decide when to show the rating dialog to the user. Keep in mind, that `SKStoreReviewController` automatically limits the number of impressions, so the dialog might be displayed less frequently than your configured conditions might suggest. 67 | 68 | License 69 | ------- 70 | Copyright 2017. [Arash Payan] [arash]. 71 | This library is distributed under the terms of the MIT/X11. 72 | 73 | While not required, I greatly encourage and appreciate any improvements that you make 74 | to this library be contributed back for the benefit of all who use Appirater. 75 | 76 | Ports for other SDKs 77 | -------------- 78 | A few people have ported Appirater to other SDKs. The ports are listed here in hopes that they may assist developers of those SDKs. I don't know how closesly (if at all) they track the Objective-C version of Appirater. If you need support for any of the libraries, please contact the maintainer of the port. 79 | 80 | + MonoTouch Binding (using native Appirater). [Github] [monotouchbinding] 81 | 82 | [stackoverflow]: http://stackoverflow.com/ 83 | [homepage]: https://arashpayan.com/blog/2009/09/07/presenting-appirater/ 84 | [arash]: https://arashpayan.com 85 | [Appirater.h]: https://github.com/arashpayan/appirater/blob/master/Appirater.h 86 | [monotouchbinding]: https://github.com/theonlylawislove/MonoTouch.Appirater 87 | -------------------------------------------------------------------------------- /ar.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "إذا كنت تستمع باستخدام %@، فهل تمانع بأن تأخذ دقيقة من وقتك لتقيمه؟ لن يستغرق الأمر أكثر من دقيقة. شكرا لدعمك!"; 2 | "Rate %@" = "قيم %@"; 3 | "No, Thanks" = "لا شكرا"; 4 | "Remind me later" = "ذكرني لاحقا"; -------------------------------------------------------------------------------- /ca.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Si li agrada utilitzar %@, li importaria prendre’s un moment per a valorar-lo? No trigarà més d’un minut. Gràcies por la seva col·laboració!"; 2 | "Rate %@" = "Valorar %@"; 3 | "No, Thanks" = "No, gràcies"; 4 | "Remind me later" = "Recordar-m’ho més tard"; -------------------------------------------------------------------------------- /cs.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Pokud se Vám aplikace %@ líbí, mohli byste ji prosím ohodnotit v App Store? Zabere to jen chvilku. Díky za Vaši podporu!"; 2 | "Rate %@" = "Ohodnotit %@"; 3 | "No, Thanks" = "Ne, díky"; 4 | "Remind me later" = "Možná později"; -------------------------------------------------------------------------------- /da.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Hvis du synes om at bruge %@, vil du have noget imod at bruge et kort øjeblik på at bedømme det? Det tager kun et minut. Tak for din støtte!"; 2 | "Rate %@" = "Bedøm %@"; 3 | "No, Thanks" = "Nej tak"; 4 | "Remind me later" = "Påmind mig senere"; 5 | -------------------------------------------------------------------------------- /de.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Sie nutzen %@ gerne? Dann nehmen Sie sich bitte für eine Bewertung einen Moment Zeit! Es dauert nicht länger als eine Minute. Vielen Dank!"; 2 | "Rate %@" = "Bewerte %@"; 3 | "No, Thanks" = "Nein, danke"; 4 | "Remind me later" = "Später erinnern"; 5 | -------------------------------------------------------------------------------- /el.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Αν σου αρέσει το %@, θα μπορούσες να αφιερώσεις μια στιγμή για να το βαθμολογήσεις; Η διαδικασία είναι πολύ σύντομη. Ευχαριστούμε για τη στήριξη!"; 2 | "Rate %@" = "Βαθμολόγηση του %@"; 3 | "No, Thanks" = "Όχι, ευχαριστώ"; 4 | "Remind me later" = "Υπενθύμιση αργότερα"; -------------------------------------------------------------------------------- /en.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!"; 2 | "Rate %@" = "Rate %@"; 3 | "No, Thanks" = "No, thanks"; 4 | "Remind me later" = "Remind me later"; -------------------------------------------------------------------------------- /es.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Si te ha gustado %@, ¿te gustaría calificarnos? No te tomará más de un minuto. ¡Gracias por tu colaboración!"; 2 | "Rate %@" = "Calificar %@"; 3 | "No, Thanks" = "No, gracias"; 4 | "Remind me later" = "Recuérdame más tarde"; 5 | -------------------------------------------------------------------------------- /fa.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "اگر از استفاده برنامه %@ لذت می‌برید، زمان دارید بهش امتیاز دهید؟ بیشتر از یک دقیقه زمان نخواهد گرفت. با تشکر از اینکه ما را همایت می‌کنید."; 2 | "Rate %@" = "ارزیابی کردن %@"; 3 | "No, Thanks" = "نه، مرسی"; 4 | "Remind me later" = "بعدا"; 5 | 6 | -------------------------------------------------------------------------------- /fi.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Jos käytät mielelläsi %@, voisitko käyttää hetken ja arvostella sen? Se ei kestä minuuttia kauempaa. Kiitos tuestasi!"; 2 | "Rate %@" = "Arvioi %@"; 3 | "No, Thanks" = "Ei kiitos"; 4 | "Remind me later" = "Muistuta minua myöhemmin"; -------------------------------------------------------------------------------- /fr.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Si vous aimez %@, voulez-vous prendre un moment pour l'évaluer ? Cela ne vous prendra pas plus d'une minute. Merci de votre soutien !"; 2 | "Rate %@" = "Évaluer %@"; 3 | "No, Thanks" = "Non, merci"; 4 | "Remind me later" = "Me rappeler plus tard"; 5 | -------------------------------------------------------------------------------- /he.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "אם נהנת להשתמש ב %@, האם תסכים לדרג אותה? זה לא יקח יותר מדקה. תודה על התמיכה!"; 2 | "Rate %@" = "דרג את %@"; 3 | "No, Thanks" = "לא תודה"; 4 | "Remind me later" = "מאוחר יותר"; -------------------------------------------------------------------------------- /hu.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Ha tetszik a %@, ne felejtsd el értékelni az App Store-ban! Csak egy perc az egész. Köszönet a támogatásért!"; 2 | "Rate %@" = "%@ értékelése"; 3 | "No, Thanks" = "Most inkább nem"; 4 | "Remind me later" = "Emlékeztess később"; 5 | -------------------------------------------------------------------------------- /hy.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Եթե Դուք հաճույքով եք օգտագործում %@-ը, դեմ չե՞ք լինի տրամադրել մեկ րոպե այն գնահատելու համար: Այն չի պահանջի ձեզանից ավելի քան մեկ րոպե: Շնորհակալություն աջակցության համար:"; 2 | "Rate %@" = "Գնահատել %@-ը"; 3 | "No, Thanks" = "Ոչ, շնորհակալություն"; 4 | "Remind me later" = "Հիշեցնել ավելի ուշ"; 5 | -------------------------------------------------------------------------------- /id.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Jika anda menyukai %@, maukah anda memberikan rating kepada aplikasi ini? Rating hanya memakan waktu kurang dari 1 menit. Terimakasih untuk dukungan anda!"; 2 | "Rate %@" = "Rating %@"; 3 | "No, Thanks" = "Tidak, terimakasih"; 4 | "Remind me later" = "Silakan ingatkan saya lagi"; -------------------------------------------------------------------------------- /it.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Se ti piace %@, perché non dedichi qualche istante a darne una valutazione sull'App Store? Non richiederà più di un minuto. Grazie per il supporto!"; 2 | "Rate %@" = "Valuta %@"; 3 | "No, Thanks" = "No, grazie"; 4 | "Remind me later" = "Ricordamelo più tardi"; -------------------------------------------------------------------------------- /ja.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "%@をお使いいただきありがとうございます。もしよろしければ、ほんの少しだけお時間をいただき評価をお願いできませんか?ご協力感謝いたします!"; 2 | "Rate %@" = "%@を評価する"; 3 | "No, Thanks" ="結構です"; 4 | "Remind me later" = "あとで"; 5 | -------------------------------------------------------------------------------- /ko.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "%@ 사용이 맘에 드셨나요? 잠시만 시간을 내서 평가를 부탁드리겠습니다. 감사합니다!"; 2 | "Rate %@" = "%@ 평가하기"; 3 | "No, Thanks" = "평가하지 않겠습니다"; 4 | "Remind me later" = "다음에 평가하겠습니다"; -------------------------------------------------------------------------------- /ms.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Jika anda suka %@, bolehkah luangkan sedikit masa untuk beri penarafan? Tak sampai seminit pun. Terima kasih atas sokongan anda!"; 2 | "Rate %@" = "Tarafkan %@"; 3 | "No, Thanks" = "Terima kasih saja"; 4 | "Remind me later" = "Ingatkan saya lain kali"; -------------------------------------------------------------------------------- /nb.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Hvis du liker å bruke %@, kan du ta deg et øyeblikk for å vurdere den? Det vil ikke ta mer enn ett minutt. Takk for din støtte!"; 2 | "Rate %@" = "Vurder %@"; 3 | "No, Thanks" = "Nei, takk"; 4 | "Remind me later" = "Påminn meg senere"; -------------------------------------------------------------------------------- /nl.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Als het gebruik van %@ je bevalt, zou je dan een momentje de tijd willen nemen om het te beoordelen? Het duurt nog geen minuut. Bedankt voor je steun!"; 2 | "Rate %@" = "%@ beoordelen"; 3 | "No, Thanks" = "Nee, bedankt"; 4 | "Remind me later" = "Herinner me er later aan"; 5 | -------------------------------------------------------------------------------- /pl.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Jeżeli podoba Ci się korzystanie z %@, może zechciałbyś poświęcić chwilę czasu, aby ocenić aplikację? Nie zajmie Ci to więcej niż minutę. Dziękujemy za pomoc!"; 2 | "Rate %@" = "Oceń %@"; 3 | "No, Thanks" = "Nie, dziękuję"; 4 | "Remind me later" = "Przypomnij później"; -------------------------------------------------------------------------------- /pt-BR.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Se você gosta de usar o %@, que tal avaliá-lo? Não levará mais de um minuto. Agradecemos o seu apoio!"; 2 | "Rate %@" = "Avaliar o %@"; 3 | "No, Thanks" = "Não, obrigado"; 4 | "Remind me later" = "Mais tarde"; 5 | -------------------------------------------------------------------------------- /pt.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Se você gosta de usar o %@, que tal avaliá-lo? Não levará mais de um minuto. Agradecemos o seu apoio!"; 2 | "Rate %@" = "Avaliar o %@"; 3 | "No, Thanks" = "Não, obrigado"; 4 | "Remind me later" = "Mais tarde"; 5 | -------------------------------------------------------------------------------- /ro.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Dacă îți place %@, acordă-i o notă te rog, nu durează mult. Mulțumim pentru susținere!"; 2 | "Rate %@" = "Acordă notă pentru %@"; 3 | "No, Thanks" = "Nu, mulțumesc"; 4 | "Remind me later" = "Adu-mi aminte mai târziu"; -------------------------------------------------------------------------------- /ru.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Если Вам нравится %@, пожалуйста, поставьте свою оценку. Это займет у Вас не больше одной минуты.\n Спасибо за поддержку!"; 2 | "Rate %@" = "Оценить %@"; 3 | "No, Thanks" = "Нет, спасибо"; 4 | "Remind me later" = "Напомнить позже"; 5 | -------------------------------------------------------------------------------- /sk.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Pokiaľ sa Vám páči aplikácia %@, mohli by ste ju prosím ohodnotiť v App Store? Zaberie to len chvíľu. Vďaka za Vašu podporu!"; 2 | "Rate %@" = "Ohodnotiť %@"; 3 | "No, Thanks" = "Nie, ďakujem"; 4 | "Remind me later" = "Pripomenúť neskôr"; -------------------------------------------------------------------------------- /sv.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Om du gillar att använda %@, kan du tänka dig att betygsätta det åt oss? Det tar bara en minut. Tack för hjälpen!"; 2 | "Rate %@" = "Betygsätt %@"; 3 | "No, Thanks" = "Nej tack"; 4 | "Remind me later" = "Påminn mig senare"; 5 | -------------------------------------------------------------------------------- /th.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "ถ้าคุณกำลังใช้ %@ โปรดสละเวลาสักครู่ในการให้อันดับแก่เรา คุณจะเสียเวลาไม่เกินหนึ่งนาที ขอบคุณสำหรับการสนับสนุน!"; 2 | "Rate %@" = "ให้อันดับ %@"; 3 | "No, Thanks" = "ไม่ ขอบคุณ"; 4 | "Remind me later" = "เตือนฉันภายหลัง"; 5 | -------------------------------------------------------------------------------- /tr.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Eğer %@ uygulamasını kullanmaktan keyif alıyorsanız, onu değerlendirmek için zaman ayırabilir misiniz? Desteğiniz için teşekkür ederiz!"; 2 | "Rate %@" = "%@ uygulamasını değerlendir"; 3 | "No, Thanks" = "Hayır, teşekkürler"; 4 | "Remind me later" = "Daha sonra hatırlat"; -------------------------------------------------------------------------------- /uk.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Якщо вам сподобалося %@, будь ласка, поставте свою оцінку. Це займає не більше однієї хвилини.\n Дякуємо за підтримку!"; 2 | "Rate %@" = "Оцінити %@"; 3 | "No, Thanks" = "Ні, дякую"; 4 | "Remind me later" = "Нагадати пізніше"; 5 | -------------------------------------------------------------------------------- /vi.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "Cảm ơn bạn đã sử dụng ứng dụng %@ trong thời gian qua, bạn có thể dành chút thời gian để đánh giá ứng dụng trong AppStore không?"; 2 | "Rate %@" = "Đánh giá %@"; 3 | "No, Thanks" = "Không, xin cảm ơn"; 4 | "Remind me later" = "Hãy nhắc nhở tôi sau"; -------------------------------------------------------------------------------- /zh-Hans.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "如果你喜欢使用%@,你介意花一点时间给它评分吗?不会超过一分钟。感谢您的支持!"; 2 | "Rate %@" = "给%@评分"; 3 | "No, Thanks" = "不,谢谢"; 4 | "Remind me later" = "稍后提醒我"; 5 | -------------------------------------------------------------------------------- /zh-Hant.lproj/AppiraterLocalizable.strings: -------------------------------------------------------------------------------- 1 | "If you enjoy using %@, would you mind taking a moment to rate it? It won't take more than a minute. Thanks for your support!" = "如果你喜歡使用%@,你介意花一點時間給它評分嗎?不會超過一分鐘。感謝您的支持!"; 2 | "Rate %@" = "給%@評分"; 3 | "No, Thanks" = "不,謝謝"; 4 | "Remind me later" = "稍後提醒我"; 5 | --------------------------------------------------------------------------------