diff --git a/Appirater.h b/Appirater.h index 83fcc2a3..5af0fea8 100644 --- a/Appirater.h +++ b/Appirater.h @@ -46,15 +46,27 @@ extern NSString *const kAppiraterRatedCurrentVersion; extern NSString *const kAppiraterDeclinedToRate; extern NSString *const kAppiraterReminderRequestDate; +/*! + Your localized special app's name. + to override the localized app display name: CFBundleDisplayName + */ +#define APPIRATER_LOCALIZED_SPECIAL_APP_NAME [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"ARAppName"] + /*! Your localized app's name. */ -#define APPIRATER_LOCALIZED_APP_NAME [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"] +#define APPIRATER_LOCALIZED_APP_NAME APPIRATER_LOCALIZED_SPECIAL_APP_NAME ? APPIRATER_LOCALIZED_SPECIAL_APP_NAME : [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"] + +/*! + Your special app's name. + to override the app display name: CFBundleDisplayName + */ +#define APPIRATER_SPECIAL_APP_NAME APPIRATER_LOCALIZED_APP_NAME ? APPIRATER_LOCALIZED_APP_NAME : [[[NSBundle mainBundle] infoDictionary] objectForKey:@"ARAppName"] /*! Your app's name. */ -#define APPIRATER_APP_NAME APPIRATER_LOCALIZED_APP_NAME ? APPIRATER_LOCALIZED_APP_NAME : [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"] +#define APPIRATER_APP_NAME APPIRATER_SPECIAL_APP_NAME ? APPIRATER_SPECIAL_APP_NAME : [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"] /*! This is the message your users will see once they've passed the day+launches @@ -85,12 +97,12 @@ extern NSString *const kAppiraterReminderRequestDate; */ #define APPIRATER_RATE_LATER NSLocalizedStringFromTableInBundle(@"Remind me later", @"AppiraterLocalizable", [Appirater bundle], nil) -@interface Appirater : NSObject { +@interface Appirater : NSObject { - UIAlertView *ratingAlert; + UIAlertController *ratingAlert; } -@property(nonatomic, strong) UIAlertView *ratingAlert; +@property(nonatomic, strong) UIAlertController *ratingAlert; @property(nonatomic) BOOL openInAppStore; #if __has_feature(objc_arc_weak) @property(nonatomic, weak) NSObject *delegate; @@ -261,15 +273,3 @@ extern NSString *const kAppiraterReminderRequestDate; +(NSBundle *)bundle; @end - -@interface Appirater(Deprecated) - -/*! - DEPRECATED: While still functional, it's better to use - appLaunched:(BOOL)canPromptForRating instead. - - Calls [Appirater appLaunched:YES]. See appLaunched: for details of functionality. - */ -+ (void)appLaunched __attribute__((deprecated)); - -@end diff --git a/Appirater.m b/Appirater.m index 19ca8156..521d8f8d 100644 --- a/Appirater.m +++ b/Appirater.m @@ -35,6 +35,7 @@ */ #import "Appirater.h" +#import "AppiraterMetrics.h" #import #include @@ -50,6 +51,11 @@ NSString *const kAppiraterDeclinedToRate = @"kAppiraterDeclinedToRate"; NSString *const kAppiraterReminderRequestDate = @"kAppiraterReminderRequestDate"; +NSString *const ARAppiraterDidDisplayAlertNotification = @"ARAppiraterDidDisplayAlertNotification"; +NSString *const ARAppiraterDidDeclineToRateNotification = @"ARAppiraterDidDeclineToRateNotification"; +NSString *const ARAppiraterDidOptToRateNotification = @"ARAppiraterDidOptToRateNotification"; +NSString *const ARAppiraterDidOptToRemindLaterNotification = @"ARAppiraterDidOptToRemindLaterNotification"; + NSString *templateReviewURL = @"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID"; NSString *templateReviewURLiOS7 = @"itms-apps://itunes.apple.com/app/idAPP_ID"; @@ -78,7 +84,7 @@ - (void)incrementUseCount; - (void)hideRatingAlert; @end -@implementation Appirater +@implementation Appirater @synthesize ratingAlert; @@ -176,16 +182,25 @@ - (BOOL)connectedToNetwork { NSLog(@"Error. Could not recover network reachability flags"); return NO; } + + // needsConnection check is not needed, because testConnection will always contains NSURLConnection address + // to remove needsConnection and testConnection from the checks below BOOL isReachable = flags & kSCNetworkFlagsReachable; - BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired; + //BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired; BOOL nonWiFi = flags & kSCNetworkReachabilityFlagsTransientConnection; - NSURL *testURL = [NSURL URLWithString:@"http://www.apple.com/"]; - NSURLRequest *testRequest = [NSURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20.0]; - NSURLConnection *testConnection = [[NSURLConnection alloc] initWithRequest:testRequest delegate:self]; + //NSURL *testURL = [NSURL URLWithString:@"http://www.apple.com/"]; + //NSURLRequest *testRequest = [NSURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20.0]; + // NSURLConnection *testConnection = [[NSURLConnection alloc] initWithRequest:testRequest delegate:self]; - return ((isReachable && !needsConnection) || nonWiFi) ? (testConnection ? YES : NO) : NO; + //return ((isReachable && !needsConnection) || nonWiFi) ? (testConnection ? YES : NO) : NO; + // after removing needs connection check + BOOL connected = (isReachable || nonWiFi) ? YES : NO; + if (_debug) { + NSLog(@"APPIRATER connected to network: %@", (connected ? @"Yes" : @"No")); + } + return connected; } + (Appirater*)sharedInstance { @@ -205,17 +220,81 @@ + (Appirater*)sharedInstance { } - (void)showRatingAlert { - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:APPIRATER_MESSAGE_TITLE - message:APPIRATER_MESSAGE - delegate:self - cancelButtonTitle:APPIRATER_CANCEL_BUTTON - otherButtonTitles:APPIRATER_RATE_BUTTON, APPIRATER_RATE_LATER, nil]; - self.ratingAlert = alertView; - [alertView show]; - - id delegate = _delegate; - if (delegate && [delegate respondsToSelector:@selector(appiraterDidDisplayAlert:)]) { - [delegate appiraterDidDisplayAlert:self]; + + if (@available(iOS 10.3, *)) { + + // use the new built-in review prompt starting from 10.3 + + [SKStoreReviewController requestReview]; + + // record it as if user rated to avoid prompting again for this same version + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setBool:YES forKey:kAppiraterRatedCurrentVersion]; + [userDefaults synchronize]; + + // send notification as if rating alert is shown + [[NSNotificationCenter defaultCenter] postNotificationName:ARAppiraterDidDisplayAlertNotification object:self]; + + } else { + + UIAlertController *alert = [UIAlertController alertControllerWithTitle:APPIRATER_MESSAGE_TITLE + message:APPIRATER_MESSAGE + preferredStyle:UIAlertControllerStyleAlert]; + + self.ratingAlert = alert; + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + id delegate = _delegate; + + UIAlertAction *cancelAction = [UIAlertAction + actionWithTitle:APPIRATER_CANCEL_BUTTON + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * _Nonnull action) { + // they don't want to rate it + [userDefaults setBool:YES forKey:kAppiraterDeclinedToRate]; + [userDefaults synchronize]; + if(delegate && [delegate respondsToSelector:@selector(appiraterDidDeclineToRate:)]){ + [delegate appiraterDidDeclineToRate:self]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:ARAppiraterDidDeclineToRateNotification object:self]; + }]; + + UIAlertAction *rateAction = [UIAlertAction + actionWithTitle:APPIRATER_RATE_BUTTON + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * _Nonnull action) { + // they want to rate it + [Appirater rateApp]; + if(delegate&& [delegate respondsToSelector:@selector(appiraterDidOptToRate:)]){ + [delegate appiraterDidOptToRate:self]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:ARAppiraterDidOptToRateNotification object:self]; + }]; + + UIAlertAction *remindAction = [UIAlertAction + actionWithTitle:APPIRATER_RATE_LATER + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * _Nonnull action) { + // remind them later + [userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterReminderRequestDate]; + [userDefaults synchronize]; + if(delegate && [delegate respondsToSelector:@selector(appiraterDidOptToRemindLater:)]){ + [delegate appiraterDidOptToRemindLater:self]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:ARAppiraterDidOptToRemindLaterNotification object:self]; + }]; + + [alert addAction:cancelAction]; + [alert addAction:rateAction]; + [alert addAction:remindAction]; + + [[[self class] getRootViewController] presentViewController:alert animated:YES completion:^{ + if (delegate && [delegate respondsToSelector:@selector(appiraterDidDisplayAlert:)]) { + [delegate appiraterDidDisplayAlert:self]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:ARAppiraterDidDisplayAlertNotification object:self]; + }]; + } } @@ -391,10 +470,6 @@ - (BOOL)userHasRatedCurrentVersion { return [[NSUserDefaults standardUserDefaults] boolForKey:kAppiraterRatedCurrentVersion]; } -+ (void)appLaunched { - [Appirater appLaunched:YES]; -} - + (void)appLaunched:(BOOL)canPromptForRating { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ @@ -403,10 +478,10 @@ + (void)appLaunched:(BOOL)canPromptForRating { } - (void)hideRatingAlert { - if (self.ratingAlert.visible) { + if (self.ratingAlert.presentingViewController) { if (_debug) NSLog(@"APPIRATER Hiding Alert"); - [self.ratingAlert dismissWithClickedButtonIndex:-1 animated:NO]; + [self.ratingAlert dismissViewControllerAnimated:NO completion:nil]; } } @@ -498,7 +573,8 @@ + (void)rateApp { //Temporarily use a black status bar to match the StoreKit view. [self setStatusBarStyle:[UIApplication sharedApplication].statusBarStyle]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 - [[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent animated:_usesAnimation]; + // setting status bar style is depcrecated since iOS 9 + //[[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent animated:_usesAnimation]; #endif }]; @@ -520,44 +596,6 @@ + (void)rateApp { } } -- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { - NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; - - id delegate = _delegate; - - switch (buttonIndex) { - case 0: - { - // they don't want to rate it - [userDefaults setBool:YES forKey:kAppiraterDeclinedToRate]; - [userDefaults synchronize]; - if(delegate && [delegate respondsToSelector:@selector(appiraterDidDeclineToRate:)]){ - [delegate appiraterDidDeclineToRate:self]; - } - break; - } - case 1: - { - // they want to rate it - [Appirater rateApp]; - if(delegate&& [delegate respondsToSelector:@selector(appiraterDidOptToRate:)]){ - [delegate appiraterDidOptToRate:self]; - } - break; - } - case 2: - // remind them later - [userDefaults setDouble:[[NSDate date] timeIntervalSince1970] forKey:kAppiraterReminderRequestDate]; - [userDefaults synchronize]; - if(delegate && [delegate respondsToSelector:@selector(appiraterDidOptToRemindLater:)]){ - [delegate appiraterDidOptToRemindLater:self]; - } - break; - default: - break; - } -} - //Delegate call from the StoreKit view. - (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController { [Appirater closeModal]; @@ -566,7 +604,8 @@ - (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewContr //Close the in-app rating (StoreKit) view and restore the previous status bar style. + (void)closeModal { if (_modalOpen) { - [[UIApplication sharedApplication]setStatusBarStyle:_statusBarStyle animated:_usesAnimation]; + // setting status bar style is depcrecated since iOS 9 + //[[UIApplication sharedApplication]setStatusBarStyle:_statusBarStyle animated:_usesAnimation]; BOOL usedAnimation = _usesAnimation; [self setModalOpen:NO]; diff --git a/AppiraterMetrics.h b/AppiraterMetrics.h new file mode 100644 index 00000000..c0b8f1c5 --- /dev/null +++ b/AppiraterMetrics.h @@ -0,0 +1,16 @@ +// +// AppiraterMetrics.h +// UnitConverter +// +// Created by Moustafa on 1/25/14. +// Copyright (c) 2014 XTApps. All rights reserved. +// + +#import + +// notifications for different appirater events + +extern NSString *const ARAppiraterDidDisplayAlertNotification; +extern NSString *const ARAppiraterDidDeclineToRateNotification; +extern NSString *const ARAppiraterDidOptToRateNotification; +extern NSString *const ARAppiraterDidOptToRemindLaterNotification; \ No newline at end of file diff --git a/README.md b/README.md index 95c3eb92..e237ff48 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,30 @@ -Introduction ------------- -Appirater is a class that you can drop into any iPhone app (iOS 4.0 or later) that will help remind your users +This fork of Appirator is modified from the original version from arashpayan in the following ways: + +* It's compatible with iOS 9.0+ and doesn't use any depcrecated APIs +* Makes use of the Apple new SKStoreReviewController for iOS 10.3+ + +The rest of this Readme is the original one from arashpayan + +## Introduction +Appirater is a class that you can drop into any iPhone app 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 check out the [Appirater group] [appiratergroup]. -Getting Started ---------------- +## Getting Started -###Cocoapods +### Cocoapods If you're new to Cocoapods [watch this](http://nsscreencast.com/episodes/5-cocoapods). To add Appirater to your app, add `pod "Appirater"` to your Podfile. Cocoapods support is still experimental, and might not work in all use cases. If you experience problems, open an issue and install via Git submodule -###Git submodule +### Git submodule 1. Add the Appirater code into your project. 2. If your project doesn't use ARC, add the `-fobjc-arc` compiler flag to `Appirater.m` in your target's Build Phases » Compile Sources section. 3. Add the `CFNetwork`, `SystemConfiguration`, and `StoreKit` frameworks to your project. Be sure to **change Required to Optional** for StoreKit in your target's Build Phases » Link Binary with Libraries section. -Configuration -------------- +## Configuration 1. Appirater provides class methods to configure its behavior. See [`Appirater.h`] [Appirater.h] for more information. ```objc @@ -37,10 +41,10 @@ Configuration 4. Call `[Appirater appEnteredForeground:YES]` in your app delegate's `applicationWillEnterForeground:` method. 5. (OPTIONAL) Call `[Appirater userDidSignificantEvent:YES]` when the user does something 'significant' in the app. -###Development +### Development Setting `[Appirater setDebug:YES]` will ensure that the rating request is shown each time the app is launched. -###Production +### Production 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. This example states that the rating request is only shown when the app has been launched 5 times **and** after 7 days. @@ -67,24 +71,20 @@ If you wanted to show the request after 5 days only you can set the following: [Appirater appLaunched:YES]; ``` -Help and Support Group ----------------------- +## Help and Support Group Requests for help, questions about usage, suggestions and other relevant topics should be posted at the [Appirater group] [appiratergroup]. As much as I'd like to help everyone who emails me, I can't respond to private emails, but I'll respond to posts on the group where others can benefit from the Q&As. -License -------- +## License Copyright 2013. [Arash Payan] [arash]. This library is distributed under the terms of the MIT/X11. While not required, I greatly encourage and appreciate any improvements that you make to this library be contributed back for the benefit of all who use Appirater. -Tips ----- +## Tips Bitcoin tips are welcome: 1PxVtrzR4oHEKPojVx41JJnWGuPLggYDQy -Ports for other SDKs --------------- +## Ports for other SDKs 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. + MonoTouch Port (using C#). [Github] [monotouchport]