From ce4d4a42ff62dcffec56fd7980e8d61ad1814f6e Mon Sep 17 00:00:00 2001 From: You shall not pass Date: Fri, 11 Dec 2015 12:11:21 +0100 Subject: [PATCH 01/31] Fix localization by getting the framework bundle --- Source/Armchair.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index eafb212..d79024f 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -1567,7 +1567,7 @@ public class Manager : ArmchairManager { if let url = armchairBundleURL { bundle = NSBundle(URL: url) } else { - bundle = NSBundle.mainBundle() + bundle = NSBundle(forClass: self.dynamicType) } } From 7a76f714c4b96d31eb7b9997ef063e569768601d Mon Sep 17 00:00:00 2001 From: phimage Date: Tue, 15 Dec 2015 05:49:41 +0100 Subject: [PATCH 02/31] Allow to capture log using closure --- Source/Armchair.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index eafb212..13ddc23 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -1715,13 +1715,19 @@ public class Manager : ArmchairManager { // MARK: - // MARK: Debug + public typealias ArmchairLogger = (Manager, log: String, file: StaticString, function: StaticString, line: UInt) -> Void + let lockQueue = dispatch_queue_create("com.armchair.lockqueue", nil) - private func debugLog(log: String) { - if debugEnabled { - dispatch_sync(lockQueue, { + public var logger: ArmchairLogger = { manager, log, file, function, line in + if manager.debugEnabled { + dispatch_sync(manager.lockQueue, { print("[Armchair] \(log)") }) } } + private func debugLog(log: String, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__) { + logger(self, log: log, file: file, function: function, line: line) + } + } From 7b5477469a4ba12302ce03d809216ec3eebb304c Mon Sep 17 00:00:00 2001 From: phimage Date: Tue, 15 Dec 2015 06:19:21 +0100 Subject: [PATCH 03/31] Replace `showPrompt(ifNecessary: Bool)` by `showPromptIfNecessary()` in README.md Remove Extra/Unwanted Spaces --- README.md | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index a6cc15e..224eda9 100644 --- a/README.md +++ b/README.md @@ -121,24 +121,24 @@ Run `carthage update` to build the framework and drag the built `Armchair.framew Armchair includes sensible defaults as well as reads data from your localized, or unlocalized `info.plist` to set itself up. While everything is configurable, the only **required** item to configure is your App Store ID. This call is the same for iOS and Mac apps, and should be made as part of your App Delegate's `initialize()` function Armchair.appID("12345678") - + That's it to get started. Setting Armchair up with this line uses some sensible default criterion (detailed below) and will present a rating prompt whenever they are met. ### Custom Configuration Optionally, if you are using significant events in your app to track when the user does something of significance, add this line to any place where this event happens, such as a `levelDidFinish` function, or `userDidUploadPhoto` function. - + Armchair.userDidSignificantEvent(true) In order for this to mean anything to Armchair, you also have to set the threshold for significant events. Typically, this, and all other logic configuration settings, should be made as part of your App Delegate's `initialize()` function so it can get the notifications on app launch. Armchair.significantEventsUntilPrompt(5) - + As mentioned above, the `appID` is the only required item to configure. It is used to generate the URL that will link to the page. Most often, this is configured to the App that is currently running, but there may be an instance where you want to set it to another app, such as in an App that reviews other Apps. GETTER: Armchair.appID() -> String SETTER: Armchair.appID(appID: String) - + ##### Display Strings The `appName` is used in several places on the review prompt popup. It can be configured here to customize your message without losing any of the default localizations. By default, Armchair will read the value from your localized, or unlocalized `info.plist`, but you can set it specifically if you want. @@ -183,10 +183,10 @@ The `usesUntilPrompt` configuration determines how many times the user will need GETTER: Armchair.usesUntilPrompt() -> UInt SETTER: Armchair.usesUntilPrompt(usesUntilPrompt: UInt) -An example of a 'use' would be if the user launched the app, or brings it to the foreground. Armchair keeps track of these internally by listening to UIApplication/NSApplication lifecycle notifications. +An example of a 'use' would be if the user launched the app, or brings it to the foreground. Armchair keeps track of these internally by listening to UIApplication/NSApplication lifecycle notifications. As discussed briefly above, the `significantEventsUntilPrompt` configuration determines how many "significant events" the user will need to have before they will be prompted to rate the App. It defaults to 0 significant events. - + GETTER: Armchair.significantEventsUntilPrompt() -> UInt SETTER: Armchair.significantEventsUntilPrompt(significantEventsUntilPrompt: UInt) @@ -195,7 +195,7 @@ A significant event can be anything you want to be in your app. In a telephone a Armchair.userDidSignificantEvent(canPromptForRating: Bool) The `daysBeforeReminding` configuration determines how many days Armchair will wait before reminding the user to rate again, should they select the "Remind Me Later" option on the first alert. It defaults to 1 day. A value of 0 will remove the "Remind Me Later" button and disable this feature. - + GETTER: Armchair.daysBeforeReminding() -> UInt SETTER: Armchair.daysBeforeReminding(daysBeforeReminding: UInt) @@ -205,15 +205,15 @@ The `tracksNewVersions` configuration determines whether or not Armchair should SETTER: Armchair.tracksNewVersions(tracksNewVersions: Bool) The `shouldPromptIfRated` configuration determines whether or not to show the review prompt to users who have rated the app once before. This setting is a little like the `tracksNewVersions` setting, but a little less nuclear. Setting this to `false` will cause new users of the app to get the popup, but won't ask users who have already been asked for a popup in the past. This is useful if you release small bug-fix versions and don't want to pester your existing users with popups for every minor version, but want to ensure new users get prompted for a review. For example, you might set this to false for every minor build, then when you push a major version upgrade, leave it as true to ask for a rating again. Its default value is `true`. - + GETTER: Armchair.shouldPromptIfRated() -> Bool SETTER: Armchair.shouldPromptIfRated(shouldPromptIfRated: Bool) The `useMainAppBundleForLocalizations` configuration is a way to tell Armchair that you are providing your own translations for the review prompt popup strings. This may be because you are just customizing them, or that you have set your own text for the popup. If set to `true`, the main bundle will always be used to load localized strings. If set to `false` Armchair will look in its own translation bundle for the translating strings. It's default value is `false`. - + GETTER: Armchair.useMainAppBundleForLocalizations() -> Bool SETTER: Armchair.useMainAppBundleForLocalizations(useMainAppBundleForLocalizations: Bool) - + ##### Affiliate Codes The `affiliateCode` configuration is optional and is used to configure with the review URL. If you are an Apple Affiliate, enter your code here. If none is set, the author's code will be used as it is better to be set as something rather than nothing. If you want to thank me for making Armchair, feel free to leave this value at it's default. @@ -223,14 +223,14 @@ The `affiliateCode` configuration is optional and is used to configure with the SETTER: Armchair.affiliateCode(affiliateCode: String) The `affiliateCampaignCode` configuration is optional and is used to configure the review URL. It provides context to the affiliate code and defaults to "Armchair-\". - + GETTER: Armchair.affiliateCampaignCode() -> String SETTER: Armchair.affiliateCampaignCode(affiliateCampaignCode: String) ##### Debug Mode The `debugEnabled` configuration is useful for testing how your review prompt popup looks and for testing. Setting it to `true` will show the Armchair alert every time by tricking the app into thinking the conditions for a prompt have been met. Calling this function in a production build (determined when `Debug` swift compiler flag is *not* defined) has no effect. In App Store builds, you don't have to worry about accidentally leaving debug on. The default value of `debugEnabled` is `false`. - + GETTER: Armchair.debugEnabled() -> Bool SETTER: Armchair.debugEnabled(debugEnabled: Bool) @@ -244,15 +244,15 @@ The `usesAnimation` configuration determines whether or not Armchair uses animat SETTER: Armchair.usesAnimation(usesAnimation: Bool) The `usesAlertController` configuration determines whether or not Armchair uses a UIAlertController when presenting an alert on iOS 8. By default, we do not use it because the reordering of buttons is not possible in the alert controller as of iOS 8.0. It's default value is `false`. Changing this value does not affect iOS 7 at all. - + GETTER: Armchair.usesAlertController() -> Bool SETTER: Armchair.usesAlertController(usesAnimation: Bool) - + The `opensInStoreKit` configuration determines if Armchair will open the App Store link inside the App using a `SKStoreProductViewController`. By default, this is `false` on iOS 7, and `true` on iOS 8. - + GETTER: Armchair.opensInStoreKit() -> Bool SETTER: Armchair.opensInStoreKit(opensInStoreKit: Bool) - + There are 2 reasons why the default is `false` on iOS 7. - The SKStoreProductViewController __does not allow the user to write a review__ (as of iOS 7)! @@ -273,11 +273,11 @@ Read more about these functions below in the [Should-Prompt Closure](#should-pro `showPrompt()` tells Armchair to show the review prompt alert. The prompt will be showed if there is an internet connection available, the user hasn't already declined to rate, hasn't rated the current version and you are tracking new versions. You could call to show the prompt regardless of Armchair settings, for instance, in the case of some special event in your app like positive feedback given. - Armchair.showPrompt() + Armchair.showPrompt() -`showPrompt(ifNecessary: Bool)` tells Armchair to show the review prompt alert if all restrictions have been met. The prompt will be shown if all restrictions are met, there is an internet connection available, the user hasn't declined to rate, hasn't rated current version, and you are tracking new versions. You could call to show the prompt, for instance, in the case of some special event in your app like a user login. +`showPromptIfNecessary()` tells Armchair to show the review prompt alert if all restrictions have been met. The prompt will be shown if all restrictions are met, there is an internet connection available, the user hasn't declined to rate, hasn't rated current version, and you are tracking new versions. You could call to show the prompt, for instance, in the case of some special event in your app like a user login. - Armchair.showPrompt(ifNecessary: Bool) + Armchair.showPromptIfNecessary() The `reviewURLString()` function is the review URL string, generated by substituting the `appID`, `affiliateCode` and `affiliateCampaignCode` into the template URL for the current platform. @@ -296,7 +296,7 @@ The `reviewURLString()` function is the review URL string, generated by substitu ### Closures Armchair uses optional closures instead of delegate functions for callbacks. Default is nil for all of them. - + Armchair.onDidDisplayAlert(didDisplayAlertClosure: ArmchairClosure?) Armchair.onDidDeclineToRate(didDeclineToRateClosure: ArmchairClosure?) Armchair.onDidOptToRate(didOptToRateClosure: ArmchairClosure?) @@ -316,19 +316,19 @@ By default Armchair increments the use count every time the app enters the foreg ##### Should-Prompt Closure Armchair allows you to set a closure that is called immediately preceding the display of the popup. - + public typealias ArmchairShouldPromptClosure = (ArmchairTrackingInfo) -> Bool - + The `ArmchairShouldPromptClosure` passes you the keys and values Armchair used to determine that the prompt should be called (found in the ArmchairTrackingInfo's `info` dictionary), and expects a `Bool` return value on whether or not the prompt should still be displayed. This allows you to have one last chance to do any of your own custom logic to determine whether or not this is an appropriate time to display the prompt. Armchair.shouldPromptClosure(shouldPromptClosure: ArmchairShouldPromptClosure?) - + In addition to the global `shouldPromptClosure`, the Armchair functions that trigger the presentation of the prompt (`showPrompt(ifNecessary: Bool)` and `userDidSignificantEvent()`) have their own closure based variant that allows you to customize whether or not this is an appropriate time to display the prompt. Armchair.showPrompt(shouldPrompt: ArmchairShouldPromptClosure) Armchair.userDidSignificantEvent(shouldPrompt: ArmchairShouldPromptClosure) - + When using these functions instead of their `Bool` sister-functions, none of the internal Armchair logic is used to determine whether or not to display the prompt. **Only** your closure is used to decide whether or not it should be presented, based solely on the return value you pass back in the closure. This also means that even the global `shouldPromptClosure()` (if set) will not be called when using these functions. **Note:** The `shouldPromptClosure()` is run synchronous and on the main queue, so be sure to handle it appropriately. @@ -360,16 +360,16 @@ The `userDefaultsObject` can be any object that responds to the `ArmchairDefault func setInteger(value: Int, forKey defaultName: String) func setDouble(value: Double, forKey defaultName: String) func setBool(value: Bool, forKey defaultName: String) - + func synchronize() -> Bool } So, to use it with iCloud and the `NSUbiquitousKeyValueStore`, set up like so: Armchair.userDefaultsObject(NSUbiquitousKeyValueStoreSubclass.defaultStore()) - + ...where you have a subclass that conforms to the protocol. - + You can get/set the `keyPrefix` to the keys above that store the usage data for Armchair. The default value is the `appID`, and it is prepended to the keys for key type. Setting a `keyPrefix` prevents different apps using a shared Key/Value store from overwriting each other. GETTER Armchair.keyPrefix() -> String From 2f7b70cb0dc9579f1a10abb6a18528c0d2649400 Mon Sep 17 00:00:00 2001 From: Matt Coneybeare Date: Tue, 15 Dec 2015 09:20:55 -0500 Subject: [PATCH 04/31] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 224eda9..e55f7c9 100644 --- a/README.md +++ b/README.md @@ -324,7 +324,7 @@ The `ArmchairShouldPromptClosure` passes you the keys and values Armchair used t Armchair.shouldPromptClosure(shouldPromptClosure: ArmchairShouldPromptClosure?) -In addition to the global `shouldPromptClosure`, the Armchair functions that trigger the presentation of the prompt (`showPrompt(ifNecessary: Bool)` and `userDidSignificantEvent()`) have their own closure based variant that allows you to customize whether or not this is an appropriate time to display the prompt. +In addition to the global `shouldPromptClosure`, the Armchair functions that trigger the presentation of the prompt (`showPromptIfNecessary` and `userDidSignificantEvent()`) have their own closure based variant that allows you to customize whether or not this is an appropriate time to display the prompt. Armchair.showPrompt(shouldPrompt: ArmchairShouldPromptClosure) Armchair.userDidSignificantEvent(shouldPrompt: ArmchairShouldPromptClosure) From 304380a9acb16555c03ef1952a24d62c46c50ed9 Mon Sep 17 00:00:00 2001 From: phimage Date: Tue, 15 Dec 2015 16:05:24 +0100 Subject: [PATCH 05/31] Add global method to set logger closure Add information into README.md --- README.md | 5 +++++ Source/Armchair.swift | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a6cc15e..681658b 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,11 @@ The `debugEnabled` configuration is useful for testing how your review prompt po GETTER: Armchair.debugEnabled() -> Bool SETTER: Armchair.debugEnabled(debugEnabled: Bool) +##### Logging + Armchair allows you to set a closure to capture debug log and to plug in the desired logging framework. + + Armchair.logger(logger: ArmchairLogger) + ##### iOS Only Configuration These configuration functions only make sense for iOS builds due to their dependency on iOS-only frameworks and functions. diff --git a/Source/Armchair.swift b/Source/Armchair.swift index 13ddc23..6ce660b 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -563,6 +563,18 @@ public func shouldIncrementUseCountClosure(shouldIncrementUseCountClosure: Armch Manager.defaultManager.shouldIncrementUseCountClosure = shouldIncrementUseCountClosure } + + +// MARK: Armchair Logger Protocol +public typealias ArmchairLogger = (Manager, log: String, file: StaticString, function: StaticString, line: UInt) -> Void + +/* +* Set a closure to capture debug log and to plug in the desired logging framework. +*/ +public func logger(logger: ArmchairLogger) { + Manager.defaultManager.logger = logger +} + // MARK: - // MARK: Armchair Defaults Protocol @@ -1715,8 +1727,6 @@ public class Manager : ArmchairManager { // MARK: - // MARK: Debug - public typealias ArmchairLogger = (Manager, log: String, file: StaticString, function: StaticString, line: UInt) -> Void - let lockQueue = dispatch_queue_create("com.armchair.lockqueue", nil) public var logger: ArmchairLogger = { manager, log, file, function, line in From 6ef0ad481101989f9add4d77c11995c46b57b816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarte=20Skj=C3=B8restad?= Date: Mon, 21 Dec 2015 11:13:39 +0100 Subject: [PATCH 06/31] Made ratingConditionsHaveBeenMet() public. --- Source/Armchair.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index 93631f5..e23046b 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -472,6 +472,13 @@ public func showPrompt(shouldPrompt: ArmchairShouldPromptClosure) { Manager.defaultManager.showPrompt(shouldPrompt) } +/** + Returns true if rating conditions have been met already and rating prompt is likely to be shown. + */ +public func ratingConditionsHaveBeenMet() -> Bool { + return Manager.defaultManager.ratingConditionsHaveBeenMet() +} + // MARK: Misc /* * This is the review URL string, generated by substituting the appID, affiliate code From 8e747575a59bc4ff7fafbdd206e300b5d9edec43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarte=20Skj=C3=B8restad?= Date: Tue, 22 Dec 2015 08:34:28 +0100 Subject: [PATCH 07/31] Updated readme for ratingConditionsHaveBeenMet. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7a26b16..48948dc 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,10 @@ The `reviewURLString()` function is the review URL string, generated by substitu Armchair.rateApp() +`ratingConditionsHaveBeenMet()` function is returning true if conditions to show rating has been met. + + Armchair.ratingConditionsHaveBeenMet() -> Bool + ##### iOS Only Functions `closeModalPanel()` tells Armchair to immediately close any open rating modal panels for instance, a `SKStoreProductViewController`. From be83189c676c1ebcbd06b7a37054d6b4d983abb6 Mon Sep 17 00:00:00 2001 From: Matt Coneybeare Date: Tue, 22 Dec 2015 08:22:32 -0500 Subject: [PATCH 08/31] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 48948dc..a85590a 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ The `reviewURLString()` function is the review URL string, generated by substitu Armchair.rateApp() -`ratingConditionsHaveBeenMet()` function is returning true if conditions to show rating has been met. +`ratingConditionsHaveBeenMet()` returns true when all of the setup conditions to display a prompt have been met. Armchair.ratingConditionsHaveBeenMet() -> Bool From 7e8b62a07e1c9bac9dc9150f462ecc259f478e58 Mon Sep 17 00:00:00 2001 From: Daniel Bolella Date: Mon, 29 Feb 2016 21:03:45 -0500 Subject: [PATCH 09/31] Set the default value of useAlertController based off ios version and only use alert controllers if ios version is >= 9 --- Source/Armchair.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index e23046b..6087cec 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -329,7 +329,7 @@ public func resetDefaults() { #if os(iOS) Manager.defaultManager.usesAnimation = true - Manager.defaultManager.usesAlertController = false + Manager.defaultManager.usesAlertController = Manager.defaultManager.defaultUsesAlertController() Manager.defaultManager.opensInStoreKit = Manager.defaultManager.defaultOpensInStoreKit() Manager.defaultManager.willPresentModalViewClosure = nil Manager.defaultManager.didDismissModalViewClosure = nil @@ -807,12 +807,15 @@ public class Manager : ArmchairManager { #if os(iOS) private var usesAnimation: Bool = true - private var usesAlertController: Bool = false + private lazy var usesAlertController: Bool = self.defaultUsesAlertController() private lazy var opensInStoreKit: Bool = self.defaultOpensInStoreKit() private func defaultOpensInStoreKit() -> Bool { return operatingSystemVersion >= 8 } + private func defaultUsesAlertController() -> Bool { + return operatingSystemVersion >= 9 + } #endif // MARK: Tracking Keys with sensible defaults @@ -1136,7 +1139,7 @@ public class Manager : ArmchairManager { private func showRatingAlert() { #if os(iOS) - if operatingSystemVersion >= 8 && usesAlertController { + if (operatingSystemVersion >= 8 && usesAlertController) || operatingSystemVersion >= 9 { /* iOS 8 uses new UIAlertController API*/ let alertView : UIAlertController = UIAlertController(title: reviewTitle, message: reviewMessage, preferredStyle: UIAlertControllerStyle.Alert) alertView.addAction(UIAlertAction(title: cancelButtonTitle, style:UIAlertActionStyle.Cancel, handler: { From 6b8e8915ea1e07ff434dfe5338c538b03336d67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Pinto=20Castillo?= Date: Fri, 22 Apr 2016 11:02:16 +0200 Subject: [PATCH 10/31] Fixes bug related to remind button The remind button was appearing even though "days before reminding" was set to 0 if the review button title was set. --- Source/Armchair.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index 6087cec..da0ba43 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -1134,7 +1134,7 @@ public class Manager : ArmchairManager { } private func showsRemindButton() -> Bool { - return (remindButtonTitle != nil) + return (daysBeforeReminding > 0 && remindButtonTitle != nil) } private func showRatingAlert() { From 7feeafba9d977da79f98f3c12c1df4bfc1174ffc Mon Sep 17 00:00:00 2001 From: Fotis Dimanidis Date: Wed, 4 May 2016 10:19:37 +0200 Subject: [PATCH 11/31] Created public method to reset counters --- Source/Armchair.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index da0ba43..67b89e3 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -302,6 +302,16 @@ public func debugEnabled(debugEnabled: Bool) { #endif } +/** + Reset all counters manually. This resets UseCount, SignificantEventCount and FirstUseDate (daysUntilPrompt) + */ +public func resetCounters() { + StandardUserDefaults().setObject(NSNumber(double: NSDate().timeIntervalSince1970), forKey: keyForArmchairKeyType(ArmchairKey.FirstUseDate)) + StandardUserDefaults().setObject(NSNumber(integer: 1), forKey: keyForArmchairKeyType(ArmchairKey.UseCount)) + StandardUserDefaults().setObject(NSNumber(integer: 0), forKey: keyForArmchairKeyType(ArmchairKey.SignificantEventCount)) + StandardUserDefaults().synchronize() +} + /* * * @@ -981,9 +991,7 @@ public class Manager : ArmchairManager { userDefaultsObject?.setObject(userDefaultsObject?.objectForKey(keyForArmchairKeyType(ArmchairKey.DeclinedToRate)), forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersionDeclinedToRate)) userDefaultsObject?.setObject(currentVersion, forKey: currentVersionKey) - userDefaultsObject?.setObject(NSNumber(double: NSDate().timeIntervalSince1970), forKey: keyForArmchairKeyType(ArmchairKey.FirstUseDate)) - userDefaultsObject?.setObject(NSNumber(integer: 1), forKey: keyForArmchairKeyType(ArmchairKey.UseCount)) - userDefaultsObject?.setObject(NSNumber(integer: 0), forKey: keyForArmchairKeyType(ArmchairKey.SignificantEventCount)) + resetCounters() userDefaultsObject?.setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)) userDefaultsObject?.setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.DeclinedToRate)) userDefaultsObject?.setObject(NSNumber(double: 0), forKey: keyForArmchairKeyType(ArmchairKey.ReminderRequestDate)) From e1de2361bf6cdbcd3d917f95e5462b954f431db1 Mon Sep 17 00:00:00 2001 From: Fotis Dimanidis Date: Wed, 4 May 2016 11:12:32 +0200 Subject: [PATCH 12/31] Updated Readme to include resetCounters() --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a85590a..081fff7 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,10 @@ The `reviewURLString()` function is the review URL string, generated by substitu `ratingConditionsHaveBeenMet()` returns true when all of the setup conditions to display a prompt have been met. Armchair.ratingConditionsHaveBeenMet() -> Bool + +`resetCounters()` resets all counters manually. This resets UseCount, SignificantEventCount and FirstUseDate (daysUntilPrompt). + + Armchair.resetCounters() ##### iOS Only Functions From 813de6e889a716b96a74169bc45c97ff78774d7a Mon Sep 17 00:00:00 2001 From: Fotis Dimanidis Date: Wed, 4 May 2016 14:37:30 +0200 Subject: [PATCH 13/31] Added resetAll function --- Source/Armchair.swift | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index 67b89e3..58cb36c 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -312,6 +312,26 @@ public func resetCounters() { StandardUserDefaults().synchronize() } +/** + Reset all values tracked by Armchair to initial state. + */ +public func resetAll() { + let currentVersionKey = keyForArmchairKeyType(ArmchairKey.CurrentVersion) + let trackingVersion: String? = StandardUserDefaults().stringForKey(currentVersionKey) + let bundleVersionKey = kCFBundleVersionKey as String + let currentVersion = NSBundle.mainBundle().objectForInfoDictionaryKey(bundleVersionKey) as? String + + StandardUserDefaults().setObject(trackingVersion, forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersion)) + StandardUserDefaults().setObject(StandardUserDefaults().objectForKey(keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)), forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersionRated)) + StandardUserDefaults().setObject(StandardUserDefaults().objectForKey(keyForArmchairKeyType(ArmchairKey.DeclinedToRate)), forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersionDeclinedToRate)) + StandardUserDefaults().setObject(currentVersion, forKey: currentVersionKey) + resetCounters() + StandardUserDefaults().setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)) + StandardUserDefaults().setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.DeclinedToRate)) + StandardUserDefaults().setObject(NSNumber(double: 0), forKey: keyForArmchairKeyType(ArmchairKey.ReminderRequestDate)) + StandardUserDefaults().synchronize() +} + /* * * @@ -986,16 +1006,7 @@ public class Manager : ArmchairManager { } else if tracksNewVersions { // it's a new version of the app, so restart tracking - userDefaultsObject?.setObject(trackingVersion, forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersion)) - userDefaultsObject?.setObject(userDefaultsObject?.objectForKey(keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)), forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersionRated)) - userDefaultsObject?.setObject(userDefaultsObject?.objectForKey(keyForArmchairKeyType(ArmchairKey.DeclinedToRate)), forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersionDeclinedToRate)) - - userDefaultsObject?.setObject(currentVersion, forKey: currentVersionKey) - resetCounters() - userDefaultsObject?.setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)) - userDefaultsObject?.setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.DeclinedToRate)) - userDefaultsObject?.setObject(NSNumber(double: 0), forKey: keyForArmchairKeyType(ArmchairKey.ReminderRequestDate)) - + resetAll() debugLog("Reset Tracking Version to: \(trackingVersion!)") } From 93ae5bf93ac775e6070b2d41b4302285e9135347 Mon Sep 17 00:00:00 2001 From: Fotis Dimanidis Date: Wed, 4 May 2016 14:42:25 +0200 Subject: [PATCH 14/31] Renamed reset methods --- Source/Armchair.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index 58cb36c..254e5e9 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -305,7 +305,7 @@ public func debugEnabled(debugEnabled: Bool) { /** Reset all counters manually. This resets UseCount, SignificantEventCount and FirstUseDate (daysUntilPrompt) */ -public func resetCounters() { +public func resetUsageCounters() { StandardUserDefaults().setObject(NSNumber(double: NSDate().timeIntervalSince1970), forKey: keyForArmchairKeyType(ArmchairKey.FirstUseDate)) StandardUserDefaults().setObject(NSNumber(integer: 1), forKey: keyForArmchairKeyType(ArmchairKey.UseCount)) StandardUserDefaults().setObject(NSNumber(integer: 0), forKey: keyForArmchairKeyType(ArmchairKey.SignificantEventCount)) @@ -315,7 +315,7 @@ public func resetCounters() { /** Reset all values tracked by Armchair to initial state. */ -public func resetAll() { +public func resetAllCounters() { let currentVersionKey = keyForArmchairKeyType(ArmchairKey.CurrentVersion) let trackingVersion: String? = StandardUserDefaults().stringForKey(currentVersionKey) let bundleVersionKey = kCFBundleVersionKey as String @@ -325,7 +325,7 @@ public func resetAll() { StandardUserDefaults().setObject(StandardUserDefaults().objectForKey(keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)), forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersionRated)) StandardUserDefaults().setObject(StandardUserDefaults().objectForKey(keyForArmchairKeyType(ArmchairKey.DeclinedToRate)), forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersionDeclinedToRate)) StandardUserDefaults().setObject(currentVersion, forKey: currentVersionKey) - resetCounters() + resetUsageCounters() StandardUserDefaults().setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)) StandardUserDefaults().setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.DeclinedToRate)) StandardUserDefaults().setObject(NSNumber(double: 0), forKey: keyForArmchairKeyType(ArmchairKey.ReminderRequestDate)) @@ -1006,7 +1006,7 @@ public class Manager : ArmchairManager { } else if tracksNewVersions { // it's a new version of the app, so restart tracking - resetAll() + resetAllCounters() debugLog("Reset Tracking Version to: \(trackingVersion!)") } From 4e03a1d725c33d171dd8cbc962b87fc9c2186f0c Mon Sep 17 00:00:00 2001 From: Fotis Dimanidis Date: Wed, 4 May 2016 14:46:32 +0200 Subject: [PATCH 15/31] Updated Readme to include resetCounters() & resetUsageCounters() --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 081fff7..1cbef07 100644 --- a/README.md +++ b/README.md @@ -296,9 +296,13 @@ The `reviewURLString()` function is the review URL string, generated by substitu Armchair.ratingConditionsHaveBeenMet() -> Bool -`resetCounters()` resets all counters manually. This resets UseCount, SignificantEventCount and FirstUseDate (daysUntilPrompt). +`resetUsageCounters()` resets app usage counters. Calling this method resets UseCount, SignificantEventCount and FirstUseDate (daysUntilPrompt). - Armchair.resetCounters() + Armchair.resetUsageCounters() + +`resetAllCounters()` resets all counters. Calling thid method resets every value tracked by Armachair. + + Armchair.resetAllCounters() ##### iOS Only Functions From 051e0d2f1012b548c38aa510921e0fac97dc7039 Mon Sep 17 00:00:00 2001 From: Mahdi Bchetnia Date: Fri, 6 May 2016 12:00:17 +0900 Subject: [PATCH 16/31] Added syntax highlighting to README --- README.md | 319 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 227 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 1cbef07..d0043db 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,9 @@ Run `carthage update` to build the framework and drag the built `Armchair.framew Armchair includes sensible defaults as well as reads data from your localized, or unlocalized `info.plist` to set itself up. While everything is configurable, the only **required** item to configure is your App Store ID. This call is the same for iOS and Mac apps, and should be made as part of your App Delegate's `initialize()` function - Armchair.appID("12345678") +```swift +Armchair.appID("12345678") +``` That's it to get started. Setting Armchair up with this line uses some sensible default criterion (detailed below) and will present a rating prompt whenever they are met. @@ -128,116 +130,191 @@ That's it to get started. Setting Armchair up with this line uses some sensible Optionally, if you are using significant events in your app to track when the user does something of significance, add this line to any place where this event happens, such as a `levelDidFinish` function, or `userDidUploadPhoto` function. - Armchair.userDidSignificantEvent(true) +```swift +Armchair.userDidSignificantEvent(true) +``` In order for this to mean anything to Armchair, you also have to set the threshold for significant events. Typically, this, and all other logic configuration settings, should be made as part of your App Delegate's `initialize()` function so it can get the notifications on app launch. - Armchair.significantEventsUntilPrompt(5) +```swift +Armchair.significantEventsUntilPrompt(5) +``` As mentioned above, the `appID` is the only required item to configure. It is used to generate the URL that will link to the page. Most often, this is configured to the App that is currently running, but there may be an instance where you want to set it to another app, such as in an App that reviews other Apps. - GETTER: Armchair.appID() -> String - SETTER: Armchair.appID(appID: String) +```swift +// GETTER +Armchair.appID() -> String +// SETTER +Armchair.appID(appID: String) +``` ##### Display Strings The `appName` is used in several places on the review prompt popup. It can be configured here to customize your message without losing any of the default localizations. By default, Armchair will read the value from your localized, or unlocalized `info.plist`, but you can set it specifically if you want. - GETTER: Armchair.appName() -> String - SETTER: Armchair.appName(appName: String) +```swift +// GETTER +Armchair.appName() -> String +// SETTER +Armchair.appName(appName: String) +``` The `reviewTitle` is the title to use on the review prompt popup. It's default value is a localized "Rate \", but you can set it to anything you want. - GETTER: Armchair.reviewTitle() -> String - SETTER: Armchair.reviewTitle(reviewTitle: String) +```swift +// GETTER +Armchair.reviewTitle() -> String +// SETTER +Armchair.reviewTitle(reviewTitle: String) +``` The `reviewMessage` is the message to use on the review prompt popup. It's default value is a localized "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!", but you can change it specifically if you want. However, if you do change it, you will need to provide your own localization strings as shown farther down below. - GETTER: Armchair.reviewMessage() -> String - SETTER: Armchair.reviewMessage(reviewMessage: String) +```swift +// GETTER +Armchair.reviewMessage() -> String +// SETTER +Armchair.reviewMessage(reviewMessage: String) +``` The `cancelButtonTitle` is the button title to use on the review prompt popup for the "Cancel" action. Its default value is a localized "No, Thanks" - GETTER: Armchair.cancelButtonTitle() -> String - SETTER: Armchair.cancelButtonTitle(cancelButtonTitle: String) +```swift +// GETTER +Armchair.cancelButtonTitle() -> String +// SETTER +Armchair.cancelButtonTitle(cancelButtonTitle: String) +``` The `rateButtonTitle` is the button title to use on the review prompt popup for the "Rate" action. Its default value is a localized "Rate \" - GETTER: Armchair.rateButtonTitle() -> String - SETTER: Armchair.rateButtonTitle(rateButtonTitle: String) +```swift +// GETTER +Armchair.rateButtonTitle() -> String +// SETTER +Armchair.rateButtonTitle(rateButtonTitle: String) +``` The `remindButtonTitle` is the button title to use on the review prompt popup for the "Remind" action. Its default value is a localized "Remind me later" - GETTER: Armchair.remindButtonTitle() -> String - SETTER: Armchair.remindButtonTitle(remindButtonTitle: String) +```swift +// GETTER +Armchair.remindButtonTitle() -> String +// SETTER +Armchair.remindButtonTitle(remindButtonTitle: String) +``` ##### Logic The `daysUntilPrompt` configuration determines how many days the users will need to have the same version of your App installed before they will be prompted to rate it. It's default is 30 days. - GETTER: Armchair.daysUntilPrompt() -> UInt - SETTER: Armchair.daysUntilPrompt(daysUntilPrompt: UInt) +```swift +// GETTER +Armchair.daysUntilPrompt() -> UInt +// SETTER +Armchair.daysUntilPrompt(daysUntilPrompt: UInt) +``` The `usesUntilPrompt` configuration determines how many times the user will need to have 'used' the same version of you App before they will be prompted to rate it. Its default is 20 uses. - GETTER: Armchair.usesUntilPrompt() -> UInt - SETTER: Armchair.usesUntilPrompt(usesUntilPrompt: UInt) +```swift +// GETTER +Armchair.usesUntilPrompt() -> UInt +// SETTER +Armchair.usesUntilPrompt(usesUntilPrompt: UInt) +``` An example of a 'use' would be if the user launched the app, or brings it to the foreground. Armchair keeps track of these internally by listening to UIApplication/NSApplication lifecycle notifications. As discussed briefly above, the `significantEventsUntilPrompt` configuration determines how many "significant events" the user will need to have before they will be prompted to rate the App. It defaults to 0 significant events. - GETTER: Armchair.significantEventsUntilPrompt() -> UInt - SETTER: Armchair.significantEventsUntilPrompt(significantEventsUntilPrompt: UInt) +```swift +// GETTER +Armchair.significantEventsUntilPrompt() -> UInt +// SETTER +Armchair.significantEventsUntilPrompt(significantEventsUntilPrompt: UInt) +``` A significant event can be anything you want to be in your app. In a telephone app, a significant event might be placing or receiving a call. In a game, it might be beating a level or a boss. This is just another layer of filtering that can be used to make sure that only the most loyal of your users are being prompted to rate you on the app store. If you leave this at a value of 0 (default), then this won't be a criterion used for rating. To tell Armchair that the user has performed a significant event, call the function: - Armchair.userDidSignificantEvent(canPromptForRating: Bool) +```swift +Armchair.userDidSignificantEvent(canPromptForRating: Bool) +``` The `daysBeforeReminding` configuration determines how many days Armchair will wait before reminding the user to rate again, should they select the "Remind Me Later" option on the first alert. It defaults to 1 day. A value of 0 will remove the "Remind Me Later" button and disable this feature. - GETTER: Armchair.daysBeforeReminding() -> UInt - SETTER: Armchair.daysBeforeReminding(daysBeforeReminding: UInt) +```swift +// GETTER +Armchair.daysBeforeReminding() -> UInt +// SETTER +Armchair.daysBeforeReminding(daysBeforeReminding: UInt) +``` The `tracksNewVersions` configuration determines whether or not Armchair should track a new app version if detected. By default, Armchair tracks **all** new bundle versions. When it detects a new version, it resets the values saved for usage, significant events, popup shown, user action etc... By setting this to `NO` Armchair will **only** track the version it was initialized with, or the one it last knew about. If this setting is set to `true`, Armchair will reset itself after each new version detection. Its default value is `true`. - GETTER: Armchair.tracksNewVersions() -> Bool - SETTER: Armchair.tracksNewVersions(tracksNewVersions: Bool) +```swift +// GETTER +Armchair.tracksNewVersions() -> Bool +// SETTER +Armchair.tracksNewVersions(tracksNewVersions: Bool) +``` The `shouldPromptIfRated` configuration determines whether or not to show the review prompt to users who have rated the app once before. This setting is a little like the `tracksNewVersions` setting, but a little less nuclear. Setting this to `false` will cause new users of the app to get the popup, but won't ask users who have already been asked for a popup in the past. This is useful if you release small bug-fix versions and don't want to pester your existing users with popups for every minor version, but want to ensure new users get prompted for a review. For example, you might set this to false for every minor build, then when you push a major version upgrade, leave it as true to ask for a rating again. Its default value is `true`. - GETTER: Armchair.shouldPromptIfRated() -> Bool - SETTER: Armchair.shouldPromptIfRated(shouldPromptIfRated: Bool) +```swift +// GETTER +Armchair.shouldPromptIfRated() -> Bool +// SETTER +Armchair.shouldPromptIfRated(shouldPromptIfRated: Bool) +``` The `useMainAppBundleForLocalizations` configuration is a way to tell Armchair that you are providing your own translations for the review prompt popup strings. This may be because you are just customizing them, or that you have set your own text for the popup. If set to `true`, the main bundle will always be used to load localized strings. If set to `false` Armchair will look in its own translation bundle for the translating strings. It's default value is `false`. - GETTER: Armchair.useMainAppBundleForLocalizations() -> Bool - SETTER: Armchair.useMainAppBundleForLocalizations(useMainAppBundleForLocalizations: Bool) +```swift +// GETTER +Armchair.useMainAppBundleForLocalizations() -> Bool +// SETTER +Armchair.useMainAppBundleForLocalizations(useMainAppBundleForLocalizations: Bool) +``` ##### Affiliate Codes The `affiliateCode` configuration is optional and is used to configure with the review URL. If you are an Apple Affiliate, enter your code here. If none is set, the author's code will be used as it is better to be set as something rather than nothing. If you want to thank me for making Armchair, feel free to leave this value at it's default. - - GETTER: Armchair.affiliateCode() -> String - SETTER: Armchair.affiliateCode(affiliateCode: String) +```swift +// GETTER +Armchair.affiliateCode() -> String +// SETTER +Armchair.affiliateCode(affiliateCode: String) +``` The `affiliateCampaignCode` configuration is optional and is used to configure the review URL. It provides context to the affiliate code and defaults to "Armchair-\". - GETTER: Armchair.affiliateCampaignCode() -> String - SETTER: Armchair.affiliateCampaignCode(affiliateCampaignCode: String) +```swift +// GETTER +Armchair.affiliateCampaignCode() -> String +// SETTER +Armchair.affiliateCampaignCode(affiliateCampaignCode: String) +``` ##### Debug Mode The `debugEnabled` configuration is useful for testing how your review prompt popup looks and for testing. Setting it to `true` will show the Armchair alert every time by tricking the app into thinking the conditions for a prompt have been met. Calling this function in a production build (determined when `Debug` swift compiler flag is *not* defined) has no effect. In App Store builds, you don't have to worry about accidentally leaving debug on. The default value of `debugEnabled` is `false`. - GETTER: Armchair.debugEnabled() -> Bool - SETTER: Armchair.debugEnabled(debugEnabled: Bool) +```swift +// GETTER +Armchair.debugEnabled() -> Bool +// SETTER +Armchair.debugEnabled(debugEnabled: Bool) +``` ##### Logging Armchair allows you to set a closure to capture debug log and to plug in the desired logging framework. - Armchair.logger(logger: ArmchairLogger) +```swift +Armchair.logger(logger: ArmchairLogger) +``` ##### iOS Only Configuration @@ -245,18 +322,30 @@ These configuration functions only make sense for iOS builds due to their depend The `usesAnimation` configuration determines whether or not Armchair uses animation when presenting a modal `SKStoreProductViewController`. Its default value is `true`. - GETTER: Armchair.usesAnimation() -> Bool - SETTER: Armchair.usesAnimation(usesAnimation: Bool) +```swift +// GETTER +Armchair.usesAnimation() -> Bool +// SETTER +Armchair.usesAnimation(usesAnimation: Bool) +``` The `usesAlertController` configuration determines whether or not Armchair uses a UIAlertController when presenting an alert on iOS 8. By default, we do not use it because the reordering of buttons is not possible in the alert controller as of iOS 8.0. It's default value is `false`. Changing this value does not affect iOS 7 at all. - GETTER: Armchair.usesAlertController() -> Bool - SETTER: Armchair.usesAlertController(usesAnimation: Bool) +```swift +// GETTER +Armchair.usesAlertController() -> Bool +// SETTER +Armchair.usesAlertController(usesAnimation: Bool) +``` The `opensInStoreKit` configuration determines if Armchair will open the App Store link inside the App using a `SKStoreProductViewController`. By default, this is `false` on iOS 7, and `true` on iOS 8. - GETTER: Armchair.opensInStoreKit() -> Bool - SETTER: Armchair.opensInStoreKit(opensInStoreKit: Bool) +```swift +// GETTER +Armchair.opensInStoreKit() -> Bool +// SETTER +Armchair.opensInStoreKit(opensInStoreKit: Bool) +``` There are 2 reasons why the default is `false` on iOS 7. @@ -267,66 +356,91 @@ There are 2 reasons why the default is `false` on iOS 7. `userDidSignificantEvent(canPromptForRating: Bool)` tells Armchair that the user performed a significant event. A significant event is whatever you want it to be. If your app is used to make VoIP calls, then you might want to call this function whenever the user places a call. If it's a game, you might want to call this whenever the user beats a level boss. If the user has performed enough significant events and used the app enough, you can suppress the rating alert by passing `NO` for `canPromptForRating`. The rating alert will simply be postponed until it is called again with `true` for `canPromptForRating`. The rating alert can also be triggered by `appLaunched:` and `appEnteredForeground:` notifications. - Armchair.userDidSignificantEvent(canPromptForRating: Bool) +```swift +Armchair.userDidSignificantEvent(canPromptForRating: Bool) +``` In addition to the above functions that can trigger the presentation of the prompt, there is a closure based variant that allows you to customize whether or not this is an appropriate time to display the prompt. - Armchair.userDidSignificantEvent(shouldPrompt: ArmchairShouldPromptClosure) - -Read more about these functions below in the [Should-Prompt Closure](#should-prompt-closure) section. +```swift +Armchair.userDidSignificantEvent(shouldPrompt: ArmchairShouldPromptClosure) +``` +Read more about these functions below in the [Should-Prompt Closure](#should-prompt-closure) section. `showPrompt()` tells Armchair to show the review prompt alert. The prompt will be showed if there is an internet connection available, the user hasn't already declined to rate, hasn't rated the current version and you are tracking new versions. You could call to show the prompt regardless of Armchair settings, for instance, in the case of some special event in your app like positive feedback given. - Armchair.showPrompt() +```swift +Armchair.showPrompt() +``` `showPromptIfNecessary()` tells Armchair to show the review prompt alert if all restrictions have been met. The prompt will be shown if all restrictions are met, there is an internet connection available, the user hasn't declined to rate, hasn't rated current version, and you are tracking new versions. You could call to show the prompt, for instance, in the case of some special event in your app like a user login. - Armchair.showPromptIfNecessary() +```swift +Armchair.showPromptIfNecessary() +``` The `reviewURLString()` function is the review URL string, generated by substituting the `appID`, `affiliateCode` and `affiliateCampaignCode` into the template URL for the current platform. - Armchair.reviewURLString() -> String +```swift +Armchair.reviewURLString() -> String +``` `rateApp()` tells Armchair to open the App Store page where the user can specify a rating for the app. It also records the fact that this has happened, so the user won't be prompted again to rate the app for this version. The only case where you should call this directly is if your app has an explicit "Rate this app" command somewhere. In all other cases, don't worry about calling this — instead, just call the other functions listed above, and let Armchair handle the bookkeeping of deciding when to ask the user whether to rate the app. - Armchair.rateApp() +```swift +Armchair.rateApp() +``` `ratingConditionsHaveBeenMet()` returns true when all of the setup conditions to display a prompt have been met. - Armchair.ratingConditionsHaveBeenMet() -> Bool - +```swift +Armchair.ratingConditionsHaveBeenMet() -> Bool +``` + `resetUsageCounters()` resets app usage counters. Calling this method resets UseCount, SignificantEventCount and FirstUseDate (daysUntilPrompt). - Armchair.resetUsageCounters() - +```swift +Armchair.resetUsageCounters() +``` + `resetAllCounters()` resets all counters. Calling thid method resets every value tracked by Armachair. - Armchair.resetAllCounters() +```swift +Armchair.resetAllCounters() +``` ##### iOS Only Functions `closeModalPanel()` tells Armchair to immediately close any open rating modal panels for instance, a `SKStoreProductViewController`. - Armchair.closeModalPanel() +```swift +Armchair.closeModalPanel() +``` ### Closures Armchair uses optional closures instead of delegate functions for callbacks. Default is nil for all of them. - Armchair.onDidDisplayAlert(didDisplayAlertClosure: ArmchairClosure?) - Armchair.onDidDeclineToRate(didDeclineToRateClosure: ArmchairClosure?) - Armchair.onDidOptToRate(didOptToRateClosure: ArmchairClosure?) - Armchair.onDidOptToRemindLater(didOptToRemindLaterClosure: ArmchairClosure?) +```swift +Armchair.onDidDisplayAlert(didDisplayAlertClosure: ArmchairClosure?) +Armchair.onDidDeclineToRate(didDeclineToRateClosure: ArmchairClosure?) +Armchair.onDidOptToRate(didOptToRateClosure: ArmchairClosure?) +Armchair.onDidOptToRemindLater(didOptToRemindLaterClosure: ArmchairClosure?) +``` ##### iOS Only Closures - Armchair.onWillPresentModalView(willPresentModalViewClosure: ArmchairAnimateClosure?) - Armchair.onDidDismissModalView(didDismissModalViewClosure: ArmchairAnimateClosure?) +```swift +Armchair.onWillPresentModalView(willPresentModalViewClosure: ArmchairAnimateClosure?) +Armchair.onDidDismissModalView(didDismissModalViewClosure: ArmchairAnimateClosure?) +``` ##### Should-Increment Closure - Armchair.shouldIncrementUseCountClosure(shouldIncrementUseCountClosure: ArmchairShouldIncrementClosure?) +```swift +Armchair.shouldIncrementUseCountClosure(shouldIncrementUseCountClosure: ArmchairShouldIncrementClosure?) +``` By default Armchair increments the use count every time the app enters the foreground. If you want to suppress this behavior (i.e. not counting a foreground event caused by switching apps during a Facebook login) you can do so with a ArmchairShouldIncrementClosure that returns `false` to ignore a foreground event or `true` to count it as normal. @@ -334,17 +448,22 @@ By default Armchair increments the use count every time the app enters the foreg Armchair allows you to set a closure that is called immediately preceding the display of the popup. - public typealias ArmchairShouldPromptClosure = (ArmchairTrackingInfo) -> Bool +```swift +public typealias ArmchairShouldPromptClosure = (ArmchairTrackingInfo) -> Bool +``` The `ArmchairShouldPromptClosure` passes you the keys and values Armchair used to determine that the prompt should be called (found in the ArmchairTrackingInfo's `info` dictionary), and expects a `Bool` return value on whether or not the prompt should still be displayed. This allows you to have one last chance to do any of your own custom logic to determine whether or not this is an appropriate time to display the prompt. - Armchair.shouldPromptClosure(shouldPromptClosure: ArmchairShouldPromptClosure?) - +```swift +Armchair.shouldPromptClosure(shouldPromptClosure: ArmchairShouldPromptClosure?) +``` In addition to the global `shouldPromptClosure`, the Armchair functions that trigger the presentation of the prompt (`showPromptIfNecessary` and `userDidSignificantEvent()`) have their own closure based variant that allows you to customize whether or not this is an appropriate time to display the prompt. - Armchair.showPrompt(shouldPrompt: ArmchairShouldPromptClosure) - Armchair.userDidSignificantEvent(shouldPrompt: ArmchairShouldPromptClosure) +```swift +Armchair.showPrompt(shouldPrompt: ArmchairShouldPromptClosure) +Armchair.userDidSignificantEvent(shouldPrompt: ArmchairShouldPromptClosure) +``` When using these functions instead of their `Bool` sister-functions, none of the internal Armchair logic is used to determine whether or not to display the prompt. **Only** your closure is used to decide whether or not it should be presented, based solely on the return value you pass back in the closure. This also means that even the global `shouldPromptClosure()` (if set) will not be called when using these functions. @@ -354,43 +473,59 @@ When using these functions instead of their `Bool` sister-functions, none of the Armchair has sensible defaults for the `NSUserDefaults` keys it uses, but you can customize that here if you want. Get/Set the `NSUserDefaults` keys that store the usage data for Armchair. Default values are all in the form of "\_Armchair\" - GETTER Armchair.keyForArmchairKeyType(keyType: ArmchairKey) -> String - SETTER Armchair.setKey(key: NSString, armchairKeyType: ArmchairKey) +```swift +// GETTER +Armchair.keyForArmchairKeyType(keyType: ArmchairKey) -> String +// SETTER +Armchair.setKey(key: NSString, armchairKeyType: ArmchairKey) +``` You don't have to use NSUserDefaults as your Key/Value store, though Armchair defaults to using it. If you want to sync your ratings and usage stats across all of your User's devices, you may want to use the `NSUbiquitousKeyValueStore` instead. This will ensure that the user won't be prompted to rate the same version of the same app on separate devices. - GETTER Armchair.userDefaultsObject() -> ArmchairDefaultsObject? - SETTER Armchair.userDefaultsObject(userDefaultsObject: ArmchairDefaultsObject?) +```swift +// GETTER +Armchair.userDefaultsObject() -> ArmchairDefaultsObject? +// SETTER +Armchair.userDefaultsObject(userDefaultsObject: ArmchairDefaultsObject?) +``` The `userDefaultsObject` can be any object that responds to the `ArmchairDefaultsObject` protocol — essentially a stripped-down version of the NSUserDefaults api: - @objc public protocol ArmchairDefaultsObject { - func objectForKey(defaultName: String) -> AnyObject? - func setObject(value: AnyObject?, forKey defaultName: String) - func removeObjectForKey(defaultName: String) +```swift +@objc public protocol ArmchairDefaultsObject { + func objectForKey(defaultName: String) -> AnyObject? + func setObject(value: AnyObject?, forKey defaultName: String) + func removeObjectForKey(defaultName: String) - func stringForKey(defaultName: String) -> String? - func integerForKey(defaultName: String) -> Int - func doubleForKey(defaultName: String) -> Double - func boolForKey(defaultName: String) -> Bool + func stringForKey(defaultName: String) -> String? + func integerForKey(defaultName: String) -> Int + func doubleForKey(defaultName: String) -> Double + func boolForKey(defaultName: String) -> Bool - func setInteger(value: Int, forKey defaultName: String) - func setDouble(value: Double, forKey defaultName: String) - func setBool(value: Bool, forKey defaultName: String) + func setInteger(value: Int, forKey defaultName: String) + func setDouble(value: Double, forKey defaultName: String) + func setBool(value: Bool, forKey defaultName: String) - func synchronize() -> Bool - } + func synchronize() -> Bool +} +``` So, to use it with iCloud and the `NSUbiquitousKeyValueStore`, set up like so: - Armchair.userDefaultsObject(NSUbiquitousKeyValueStoreSubclass.defaultStore()) +```swift +Armchair.userDefaultsObject(NSUbiquitousKeyValueStoreSubclass.defaultStore()) +``` ...where you have a subclass that conforms to the protocol. You can get/set the `keyPrefix` to the keys above that store the usage data for Armchair. The default value is the `appID`, and it is prepended to the keys for key type. Setting a `keyPrefix` prevents different apps using a shared Key/Value store from overwriting each other. - GETTER Armchair.keyPrefix() -> String - SETTER Armchair.keyPrefix(keyPrefix: String) +```swift +// GETTER +Armchair.keyPrefix() -> String +// SETTER +Armchair.keyPrefix(keyPrefix: String) +``` ### Configuration/Usage Examples From fd361253d84561777c83ea468b24a5a4740922d3 Mon Sep 17 00:00:00 2001 From: Mahdi Bchetnia Date: Fri, 6 May 2016 12:03:21 +0900 Subject: [PATCH 17/31] Fixed README typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0043db..b83e988 100644 --- a/README.md +++ b/README.md @@ -404,7 +404,7 @@ Armchair.ratingConditionsHaveBeenMet() -> Bool Armchair.resetUsageCounters() ``` -`resetAllCounters()` resets all counters. Calling thid method resets every value tracked by Armachair. +`resetAllCounters()` resets all counters. Calling this method resets every value tracked by Armchair. ```swift Armchair.resetAllCounters() From 674f044a431327b922259042ab3a1c69e305f74a Mon Sep 17 00:00:00 2001 From: Cesare de Cal Date: Fri, 20 May 2016 21:26:25 +0200 Subject: [PATCH 18/31] Very small grammar fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b83e988..eced31d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ If you are an iTunes Affiliate, you can easily setup Armchair to use your code a ##### Ready For Primetime -Armchair is clean code, well documented and well organized. It is easy to understand the logic flow and the purpose of each function. It doesn't mix logic up randomly between Class functions and Instance functions. It's API is clean and predictable. +Armchair is clean code, well documented and well organized. It is easy to understand the logic flow and the purpose of each function. It doesn't mix logic up randomly between Class functions and Instance functions. Its API is clean and predictable. --- From cc3706ca07b5ca78b952c90bf989346fc213017d Mon Sep 17 00:00:00 2001 From: Fotis Dimanidis Date: Mon, 30 May 2016 11:36:48 +0200 Subject: [PATCH 19/31] Falling back to Localizable.strings --- Source/Armchair.swift | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index 254e5e9..9be924d 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -750,7 +750,9 @@ public class Manager : ArmchairManager { var template = "Rate %@" // Check for a localized version of the default title if let bundle = self.bundle() { - template = bundle.localizedStringForKey(template, value:"", table: "ArmchairLocalizable") + template = bundle.localizedStringForKey(template, + value: bundle.localizedStringForKey(template, value:"", table: nil), + table: "ArmchairLocalizable") } return template.stringByReplacingOccurrencesOfString("%@", withString: "\(self.appName)", options: NSStringCompareOptions(rawValue: 0), range: nil) @@ -761,7 +763,9 @@ public class Manager : ArmchairManager { var template = "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!" // Check for a localized version of the default title if let bundle = self.bundle() { - template = bundle.localizedStringForKey(template, value:"", table: "ArmchairLocalizable") + template = bundle.localizedStringForKey(template, + value: bundle.localizedStringForKey(template, value:"", table: nil), + table: "ArmchairLocalizable") } return template.stringByReplacingOccurrencesOfString("%@", withString: "\(self.appName)", options: NSStringCompareOptions(rawValue: 0), range: nil) @@ -772,7 +776,9 @@ public class Manager : ArmchairManager { var title = "No, Thanks" // Check for a localized version of the default title if let bundle = self.bundle() { - title = bundle.localizedStringForKey(title, value:"", table: "ArmchairLocalizable") + title = bundle.localizedStringForKey(title, + value: bundle.localizedStringForKey(title, value:"", table: nil), + table: "ArmchairLocalizable") } return title @@ -783,7 +789,9 @@ public class Manager : ArmchairManager { var template = "Rate %@" // Check for a localized version of the default title if let bundle = self.bundle() { - template = bundle.localizedStringForKey(template, value:"", table: "ArmchairLocalizable") + template = bundle.localizedStringForKey(template, + value: bundle.localizedStringForKey(template, value:"", table: nil), + table: "ArmchairLocalizable") } return template.stringByReplacingOccurrencesOfString("%@", withString: "\(self.appName)", options: NSStringCompareOptions(rawValue: 0), range: nil) @@ -798,7 +806,9 @@ public class Manager : ArmchairManager { var title = "Remind me later" // Check for a localized version of the default title if let bundle = self.bundle() { - title = bundle.localizedStringForKey(title, value:"", table: "ArmchairLocalizable") + title = bundle.localizedStringForKey(title, + value: bundle.localizedStringForKey(title, value:"", table: nil), + table: "ArmchairLocalizable") } return title From 0ea672f4d508caa27718214c7dfae39264747106 Mon Sep 17 00:00:00 2001 From: Fotis Dimanidis Date: Mon, 30 May 2016 11:41:08 +0200 Subject: [PATCH 20/31] Updated README to include localization changes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eced31d..18e0be5 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ Armchair.shouldPromptIfRated() -> Bool Armchair.shouldPromptIfRated(shouldPromptIfRated: Bool) ``` -The `useMainAppBundleForLocalizations` configuration is a way to tell Armchair that you are providing your own translations for the review prompt popup strings. This may be because you are just customizing them, or that you have set your own text for the popup. If set to `true`, the main bundle will always be used to load localized strings. If set to `false` Armchair will look in its own translation bundle for the translating strings. It's default value is `false`. +The `useMainAppBundleForLocalizations` configuration is a way to tell Armchair that you are providing your own translations for the review prompt popup strings. This may be because you are just customizing them, or that you have set your own text for the popup. If set to `true`, the main bundle will always be used to load localized strings. You have to include the translations either in a file called `ArmchairLocalizable.strings` or the standard `Localizable.strings`. If set to `false` Armchair will look in its own translation bundle for the translating strings. It's default value is `false`. ```swift // GETTER From e1d6e23ef7cea2f43a2f7fe2974a9b7ca28b67f0 Mon Sep 17 00:00:00 2001 From: Tarik Date: Sat, 23 Jul 2016 11:37:38 -0700 Subject: [PATCH 21/31] Latest version fix for CocoaPods It wasn't installing the latest version which is 0.1.2. Source: https://guides.cocoapods.org/using/the-podfile.html --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18e0be5..34fb21d 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ platform :ios, '8.0' platform :osx, '10.10' use_frameworks! -pod 'Armchair', '~> 0.1.1' +pod 'Armchair', '~> 0.1' ``` Then, run the following command: From 3f78915843c40482939d9cf6373f0d241daabbf2 Mon Sep 17 00:00:00 2001 From: Tarik Date: Sat, 23 Jul 2016 11:41:02 -0700 Subject: [PATCH 22/31] Latest version fix for CocoaPods It wasn't installing the latest version which is 0.1.2. Source: https://guides.cocoapods.org/using/the-podfile.html --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34fb21d..c4db18e 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ platform :ios, '8.0' platform :osx, '10.10' use_frameworks! -pod 'Armchair', '~> 0.1' +pod 'Armchair', '>= 0.1' ``` Then, run the following command: From 2a1d87665be6972e52ef34c51a7210bc6b0fa790 Mon Sep 17 00:00:00 2001 From: Dawid Drechny Date: Wed, 10 Aug 2016 20:33:28 +0200 Subject: [PATCH 23/31] Add tintColor configuration setting --- Example/ViewController.swift | 3 +++ README.md | 9 +++++++++ Source/Armchair.swift | 17 ++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index fbd0bcb..7d5703f 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -141,6 +141,9 @@ extension ViewController { #if os(iOS) // Explicitly disable the storeKit as the default may be true if on iOS 8 Armchair.opensInStoreKit(false) + + // This sets a custom tint color (applies only to UIAlertController). + Armchair.tintColor(UIColor.brownColor()) #endif // This sets the Affiliate code you want to use, but is not required. diff --git a/README.md b/README.md index c4db18e..fd5259f 100644 --- a/README.md +++ b/README.md @@ -329,6 +329,15 @@ Armchair.usesAnimation() -> Bool Armchair.usesAnimation(usesAnimation: Bool) ``` +The `tintColor` configuration specifies a tint color that is applied to UIAlertController when `usesAlertController` is true. Its default value is `nil`, which means that tint color is not customized. + +```swift +// GETTER +Armchair.tintColor() -> UIColor? +// SETTER +Armchair.tintColor(tintColor: UIColor?) +``` + The `usesAlertController` configuration determines whether or not Armchair uses a UIAlertController when presenting an alert on iOS 8. By default, we do not use it because the reordering of buttons is not possible in the alert controller as of iOS 8.0. It's default value is `false`. Changing this value does not affect iOS 7 at all. ```swift diff --git a/Source/Armchair.swift b/Source/Armchair.swift index 9be924d..fcb5554 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -359,6 +359,7 @@ public func resetDefaults() { #if os(iOS) Manager.defaultManager.usesAnimation = true + Manager.defaultManager.tintColor = nil Manager.defaultManager.usesAlertController = Manager.defaultManager.defaultUsesAlertController() Manager.defaultManager.opensInStoreKit = Manager.defaultManager.defaultOpensInStoreKit() Manager.defaultManager.willPresentModalViewClosure = nil @@ -394,7 +395,18 @@ public func resetDefaults() { public func usesAnimation(usesAnimation: Bool) { Manager.defaultManager.usesAnimation = usesAnimation } - + + /* + * Set a tint color to apply to UIAlertController + * Default => nil (the default tint color is used) + */ + public func tintColor() -> UIColor? { + return Manager.defaultManager.tintColor + } + public func tintColor(tintColor: UIColor?) { + Manager.defaultManager.tintColor = tintColor + } + /* * Set whether or not Armchair uses a UIAlertController when presenting on iOS 8 * We prefer not to use it so that the Rate button can be on the bottom and the cancel button on the top, @@ -847,6 +859,7 @@ public class Manager : ArmchairManager { #if os(iOS) private var usesAnimation: Bool = true + private var tintColor: UIColor? = nil private lazy var usesAlertController: Bool = self.defaultUsesAlertController() private lazy var opensInStoreKit: Bool = self.defaultOpensInStoreKit() @@ -1192,6 +1205,8 @@ public class Manager : ArmchairManager { topController.presentViewController(alertView, animated: usesAnimation) { print("presentViewController() completed") } + // note that tint color has to be set after the controller is presented in order to take effect (last checked in iOS 9.3) + alertView.view.tintColor = tintColor } } From 96f70c8bdff2320e62aaed0d0aa68cdeb7e2a9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Pinto=20Castillo?= Date: Thu, 8 Sep 2016 13:15:59 +0200 Subject: [PATCH 24/31] Updates project so it builds with Swift 2.3 --- Armchair.xcodeproj/project.pbxproj | 24 +++++++++++-- .../xcshareddata/xcschemes/Armchair.xcscheme | 2 +- .../xcschemes/ArmchairMac.xcscheme | 2 +- Source/Armchair.swift | 11 +++--- iOS Example.xcodeproj/project.pbxproj | 35 ++++++++----------- 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/Armchair.xcodeproj/project.pbxproj b/Armchair.xcodeproj/project.pbxproj index 75b4e3c..ec08c4c 100644 --- a/Armchair.xcodeproj/project.pbxproj +++ b/Armchair.xcodeproj/project.pbxproj @@ -318,14 +318,17 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = Armchair; TargetAttributes = { E6A0AF6419C9CFF400C3A7DC = { CreatedOnToolsVersion = 6.0; + ProvisioningStyle = Manual; }; F8111E3219A95C8B0040E7D1 = { CreatedOnToolsVersion = 6.0; + LastSwiftMigration = 0800; + ProvisioningStyle = Manual; }; }; }; @@ -482,6 +485,7 @@ isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -503,6 +507,7 @@ buildSettings = { COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "Source/Info-Localizable.plist"; INSTALL_PATH = ""; MACOSX_DEPLOYMENT_TARGET = 10.10; @@ -527,7 +532,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "com.armchair.$(PRODUCT_NAME:rfc1034identifier)"; @@ -552,7 +557,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "com.armchair.$(PRODUCT_NAME:rfc1034identifier)"; @@ -616,8 +621,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -627,6 +634,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -665,8 +673,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -675,6 +685,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -685,6 +696,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -696,7 +708,9 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -709,6 +723,7 @@ SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -716,7 +731,9 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -728,6 +745,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 2.3; }; name = Release; }; diff --git a/Armchair.xcodeproj/xcshareddata/xcschemes/Armchair.xcscheme b/Armchair.xcodeproj/xcshareddata/xcschemes/Armchair.xcscheme index 74ef91f..53b90e2 100644 --- a/Armchair.xcodeproj/xcshareddata/xcschemes/Armchair.xcscheme +++ b/Armchair.xcodeproj/xcshareddata/xcschemes/Armchair.xcscheme @@ -1,6 +1,6 @@ Date: Mon, 12 Sep 2016 15:35:14 +0200 Subject: [PATCH 25/31] Changed scopes and deprecated elements to compile in Swift 3 --- Armchair.xcodeproj/project.pbxproj | 18 +- .../xcshareddata/xcschemes/Armchair.xcscheme | 2 +- .../xcschemes/ArmchairMac.xcscheme | 2 +- Source/Armchair.swift | 1598 +++++++++-------- 4 files changed, 819 insertions(+), 801 deletions(-) diff --git a/Armchair.xcodeproj/project.pbxproj b/Armchair.xcodeproj/project.pbxproj index 75b4e3c..fb1ae98 100644 --- a/Armchair.xcodeproj/project.pbxproj +++ b/Armchair.xcodeproj/project.pbxproj @@ -318,7 +318,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = Armchair; TargetAttributes = { E6A0AF6419C9CFF400C3A7DC = { @@ -326,6 +326,7 @@ }; F8111E3219A95C8B0040E7D1 = { CreatedOnToolsVersion = 6.0; + LastSwiftMigration = 0800; }; }; }; @@ -527,7 +528,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "com.armchair.$(PRODUCT_NAME:rfc1034identifier)"; @@ -552,7 +553,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "com.armchair.$(PRODUCT_NAME:rfc1034identifier)"; @@ -616,8 +617,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -627,6 +630,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -665,8 +669,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -675,6 +681,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -685,6 +692,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -696,6 +704,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -709,6 +718,7 @@ SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -716,6 +726,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -728,6 +739,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/Armchair.xcodeproj/xcshareddata/xcschemes/Armchair.xcscheme b/Armchair.xcodeproj/xcshareddata/xcschemes/Armchair.xcscheme index 74ef91f..53b90e2 100644 --- a/Armchair.xcodeproj/xcshareddata/xcschemes/Armchair.xcscheme +++ b/Armchair.xcodeproj/xcshareddata/xcschemes/Armchair.xcscheme @@ -1,6 +1,6 @@ String { return Manager.defaultManager.appName } -public func appName(appName: String) { +public func appName(_ appName: String) { Manager.defaultManager.appName = appName } /* -* Get/Set the title to use on the review prompt. -* Default value is a localized "Rate " -*/ + * Get/Set the title to use on the review prompt. + * Default value is a localized "Rate " + */ public func reviewTitle() -> String { return Manager.defaultManager.reviewTitle } -public func reviewTitle(reviewTitle: String) { +public func reviewTitle(_ reviewTitle: String) { Manager.defaultManager.reviewTitle = reviewTitle } /* -* Get/Set the message to use on the review prompt. -* Default value is a localized -* "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!" -*/ + * Get/Set the message to use on the review prompt. + * Default value is a localized + * "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!" + */ public func reviewMessage() -> String { return Manager.defaultManager.reviewMessage } -public func reviewMessage(reviewMessage: String) { +public func reviewMessage(_ reviewMessage: String) { Manager.defaultManager.reviewMessage = reviewMessage } /* -* Get/Set the cancel button title to use on the review prompt. -* Default value is a localized "No, Thanks" -*/ + * Get/Set the cancel button title to use on the review prompt. + * Default value is a localized "No, Thanks" + */ public func cancelButtonTitle() -> String { return Manager.defaultManager.cancelButtonTitle } -public func cancelButtonTitle(cancelButtonTitle: String) { +public func cancelButtonTitle(_ cancelButtonTitle: String) { Manager.defaultManager.cancelButtonTitle = cancelButtonTitle } /* -* Get/Set the rate button title to use on the review prompt. -* Default value is a localized "Rate " -*/ + * Get/Set the rate button title to use on the review prompt. + * Default value is a localized "Rate " + */ public func rateButtonTitle() -> String { return Manager.defaultManager.rateButtonTitle } -public func rateButtonTitle(rateButtonTitle: String) { +public func rateButtonTitle(_ rateButtonTitle: String) { Manager.defaultManager.rateButtonTitle = rateButtonTitle } /* -* Get/Set the remind me later button title to use on the review prompt. -* It is optional, so you can set it to nil to hide the remind button from displaying -* Default value is a localized "Remind me later" -*/ + * Get/Set the remind me later button title to use on the review prompt. + * It is optional, so you can set it to nil to hide the remind button from displaying + * Default value is a localized "Remind me later" + */ public func remindButtonTitle() -> String? { return Manager.defaultManager.remindButtonTitle } -public func remindButtonTitle(remindButtonTitle: String?) { +public func remindButtonTitle(_ remindButtonTitle: String?) { Manager.defaultManager.remindButtonTitle = remindButtonTitle } /* -* Get/Set the NSUserDefault keys that store the usage data for Armchair -* Default values are in the form of "_Armchair" -*/ -public func keyForArmchairKeyType(keyType: ArmchairKey) -> String { + * Get/Set the NSUserDefault keys that store the usage data for Armchair + * Default values are in the form of "_Armchair" + */ +public func keyForArmchairKeyType(_ keyType: ArmchairKey) -> String { return Manager.defaultManager.keyForArmchairKeyType(keyType) } -public func setKey(key: NSString, armchairKeyType: ArmchairKey) { +public func setKey(_ key: NSString, armchairKeyType: ArmchairKey) { Manager.defaultManager.setKey(key, armchairKeyType: armchairKeyType) } /* -* Get/Set the prefix to the NSUserDefault keys that store the usage data for Armchair -* Default value is the App ID, and it is prepended to the keys for key type, above -* This prevents different apps using a shared Key/Value store from overwriting each other. -*/ + * Get/Set the prefix to the NSUserDefault keys that store the usage data for Armchair + * Default value is the App ID, and it is prepended to the keys for key type, above + * This prevents different apps using a shared Key/Value store from overwriting each other. + */ public func keyPrefix() -> String { return Manager.defaultManager.keyPrefix } -public func keyPrefix(keyPrefix: String) { +public func keyPrefix(_ keyPrefix: String) { Manager.defaultManager.keyPrefix = keyPrefix } /* -* Get/Set the object that stores the usage data for Armchair -* it is optional but if you pass nil, Armchair can not run. -* Default value is NSUserDefaults.standardUserDefaults() -*/ + * Get/Set the object that stores the usage data for Armchair + * it is optional but if you pass nil, Armchair can not run. + * Default value is NSUserDefaults.standardUserDefaults() + */ public func userDefaultsObject() -> ArmchairDefaultsObject? { return Manager.defaultManager.userDefaultsObject } -public func userDefaultsObject(userDefaultsObject: ArmchairDefaultsObject?) { +public func userDefaultsObject(_ userDefaultsObject: ArmchairDefaultsObject?) { Manager.defaultManager.userDefaultsObject = userDefaultsObject } /* -* Users will need to have the same version of your app installed for this many -* days before they will be prompted to rate it. -* Default => 30 -*/ + * Users will need to have the same version of your app installed for this many + * days before they will be prompted to rate it. + * Default => 30 + */ public func daysUntilPrompt() -> UInt { return Manager.defaultManager.daysUntilPrompt } -public func daysUntilPrompt(daysUntilPrompt: UInt) { +public func daysUntilPrompt(_ daysUntilPrompt: UInt) { Manager.defaultManager.daysUntilPrompt = daysUntilPrompt } /* -* An example of a 'use' would be if the user launched the app. Bringing the app -* into the foreground (on devices that support it) would also be considered -* a 'use'. -* -* Users need to 'use' the same version of the app this many times before -* before they will be prompted to rate it. -* Default => 20 -*/ + * An example of a 'use' would be if the user launched the app. Bringing the app + * into the foreground (on devices that support it) would also be considered + * a 'use'. + * + * Users need to 'use' the same version of the app this many times before + * before they will be prompted to rate it. + * Default => 20 + */ public func usesUntilPrompt() -> UInt { return Manager.defaultManager.usesUntilPrompt } -public func usesUntilPrompt(usesUntilPrompt: UInt) { +public func usesUntilPrompt(_ usesUntilPrompt: UInt) { Manager.defaultManager.usesUntilPrompt = usesUntilPrompt } /* -* A significant event can be anything you want to be in your app. In a -* telephone app, a significant event might be placing or receiving a call. -* In a game, it might be beating a level or a boss. This is just another -* layer of filtering that can be used to make sure that only the most -* loyal of your users are being prompted to rate you on the app store. -* If you leave this at a value of 0 (default), then this won't be a criterion -* used for rating. -* -* To tell Armchair that the user has performed -* a significant event, call the method Armchair.userDidSignificantEvent() -* Default => 0 -*/ + * A significant event can be anything you want to be in your app. In a + * telephone app, a significant event might be placing or receiving a call. + * In a game, it might be beating a level or a boss. This is just another + * layer of filtering that can be used to make sure that only the most + * loyal of your users are being prompted to rate you on the app store. + * If you leave this at a value of 0 (default), then this won't be a criterion + * used for rating. + * + * To tell Armchair that the user has performed + * a significant event, call the method Armchair.userDidSignificantEvent() + * Default => 0 + */ public func significantEventsUntilPrompt() -> UInt { return Manager.defaultManager.significantEventsUntilPrompt } -public func significantEventsUntilPrompt(significantEventsUntilPrompt: UInt) { +public func significantEventsUntilPrompt(_ significantEventsUntilPrompt: UInt) { Manager.defaultManager.significantEventsUntilPrompt = significantEventsUntilPrompt } /* -* Once the rating alert is presented to the user, they might select -* 'Remind me later'. This value specifies how many days Armchair -* will wait before reminding them. A value of 0 disables reminders and -* removes the 'Remind me later' button. -* Default => 1 -*/ + * Once the rating alert is presented to the user, they might select + * 'Remind me later'. This value specifies how many days Armchair + * will wait before reminding them. A value of 0 disables reminders and + * removes the 'Remind me later' button. + * Default => 1 + */ public func daysBeforeReminding() -> UInt { return Manager.defaultManager.daysBeforeReminding } -public func daysBeforeReminding(daysBeforeReminding: UInt) { +public func daysBeforeReminding(_ daysBeforeReminding: UInt) { Manager.defaultManager.daysBeforeReminding = daysBeforeReminding } /* -* By default, Armchair tracks all new bundle versions. -* When it detects a new version, it resets the values saved for usage, -* significant events, popup shown, user action etc... -* By setting this to false, Armchair will ONLY track the version it -* was initialized with. If this setting is set to true, Armchair -* will reset after each new version detection. -* Default => true -*/ + * By default, Armchair tracks all new bundle versions. + * When it detects a new version, it resets the values saved for usage, + * significant events, popup shown, user action etc... + * By setting this to false, Armchair will ONLY track the version it + * was initialized with. If this setting is set to true, Armchair + * will reset after each new version detection. + * Default => true + */ public func tracksNewVersions() -> Bool { return Manager.defaultManager.tracksNewVersions } -public func tracksNewVersions(tracksNewVersions: Bool) { +public func tracksNewVersions(_ tracksNewVersions: Bool) { Manager.defaultManager.tracksNewVersions = tracksNewVersions } /* -* If the user has rated the app once before, and you don't want it to show on -* a new version, set this to false. This is useful if you release small bugfix -* versions and don't want to pester your users with popups for every minor -* version. For example, you might set this to false for every minor build, then -* when you push a major version upgrade, leave it as true to ask for a rating again. -* Default => true -*/ + * If the user has rated the app once before, and you don't want it to show on + * a new version, set this to false. This is useful if you release small bugfix + * versions and don't want to pester your users with popups for every minor + * version. For example, you might set this to false for every minor build, then + * when you push a major version upgrade, leave it as true to ask for a rating again. + * Default => true + */ public func shouldPromptIfRated() -> Bool { return Manager.defaultManager.shouldPromptIfRated } -public func shouldPromptIfRated(shouldPromptIfRated: Bool) { +public func shouldPromptIfRated(_ shouldPromptIfRated: Bool) { Manager.defaultManager.shouldPromptIfRated = shouldPromptIfRated } /* -* If set to true, the main bundle will always be used to load localized strings. -* Set this to true if you have provided your own custom localizations in -* ArmchairLocalizable.strings in your main bundle -* Default => false. -*/ + * If set to true, the main bundle will always be used to load localized strings. + * Set this to true if you have provided your own custom localizations in + * ArmchairLocalizable.strings in your main bundle + * Default => false. + */ public func useMainAppBundleForLocalizations() -> Bool { return Manager.defaultManager.useMainAppBundleForLocalizations } -public func useMainAppBundleForLocalizations(useMainAppBundleForLocalizations: Bool) { +public func useMainAppBundleForLocalizations(_ useMainAppBundleForLocalizations: Bool) { Manager.defaultManager.useMainAppBundleForLocalizations = useMainAppBundleForLocalizations } /* -* If you are an Apple Affiliate, enter your code here. -* If none is set, the author's code will be used as it is better to be set as something -* rather than nothing. If you want to thank me for making Armchair, feel free -* to leave this value at it's default. -*/ + * If you are an Apple Affiliate, enter your code here. + * If none is set, the author's code will be used as it is better to be set as something + * rather than nothing. If you want to thank me for making Armchair, feel free + * to leave this value at it's default. + */ public func affiliateCode() -> String { return Manager.defaultManager.affiliateCode } -public func affiliateCode(affiliateCode: String) { +public func affiliateCode(_ affiliateCode: String) { Manager.defaultManager.affiliateCode = affiliateCode } /* -* If you are an Apple Affiliate, enter your campaign code here. -* Default => "Armchair-" -*/ + * If you are an Apple Affiliate, enter your campaign code here. + * Default => "Armchair-" + */ public func affiliateCampaignCode() -> String { return Manager.defaultManager.affiliateCampaignCode } -public func affiliateCampaignCode(affiliateCampaignCode: String) { +public func affiliateCampaignCode(_ affiliateCampaignCode: String) { Manager.defaultManager.affiliateCampaignCode = affiliateCampaignCode } /* -* 'true' will show the Armchair alert everytime. Useful for testing -* how your message looks and making sure the link to your app's review page works. -* Calling this method in a production build (DEBUG preprocessor macro is not defined) -* has no effect. In app store builds, you don't have to worry about accidentally -* leaving debugEnabled to true -* Default => false -*/ + * 'true' will show the Armchair alert everytime. Useful for testing + * how your message looks and making sure the link to your app's review page works. + * Calling this method in a production build (DEBUG preprocessor macro is not defined) + * has no effect. In app store builds, you don't have to worry about accidentally + * leaving debugEnabled to true + * Default => false + */ public func debugEnabled() -> Bool { return Manager.defaultManager.debugEnabled } -public func debugEnabled(debugEnabled: Bool) { -#if Debug - Manager.defaultManager.debugEnabled = debugEnabled -#else - print("[Armchair] Debug is disabled on release builds.") - print("[Armchair] If you really want to enable debug mode,") - print("[Armchair] add \"-DDebug\" to your Swift Compiler - Custom Flags") - print("[Armchair] section in the target's build settings for release") -#endif +public func debugEnabled(_ debugEnabled: Bool) { + #if Debug + Manager.defaultManager.debugEnabled = debugEnabled + #else + print("[Armchair] Debug is disabled on release builds.") + print("[Armchair] If you really want to enable debug mode,") + print("[Armchair] add \"-DDebug\" to your Swift Compiler - Custom Flags") + print("[Armchair] section in the target's build settings for release") + #endif } /** Reset all counters manually. This resets UseCount, SignificantEventCount and FirstUseDate (daysUntilPrompt) */ public func resetUsageCounters() { - StandardUserDefaults().setObject(NSNumber(double: NSDate().timeIntervalSince1970), forKey: keyForArmchairKeyType(ArmchairKey.FirstUseDate)) - StandardUserDefaults().setObject(NSNumber(integer: 1), forKey: keyForArmchairKeyType(ArmchairKey.UseCount)) - StandardUserDefaults().setObject(NSNumber(integer: 0), forKey: keyForArmchairKeyType(ArmchairKey.SignificantEventCount)) + StandardUserDefaults().setObject(NSNumber(value: Date().timeIntervalSince1970), forKey: keyForArmchairKeyType(ArmchairKey.FirstUseDate)) + StandardUserDefaults().setObject(NSNumber(value: 1), forKey: keyForArmchairKeyType(ArmchairKey.UseCount)) + StandardUserDefaults().setObject(NSNumber(value: 0), forKey: keyForArmchairKeyType(ArmchairKey.SignificantEventCount)) StandardUserDefaults().synchronize() } @@ -319,23 +319,23 @@ public func resetAllCounters() { let currentVersionKey = keyForArmchairKeyType(ArmchairKey.CurrentVersion) let trackingVersion: String? = StandardUserDefaults().stringForKey(currentVersionKey) let bundleVersionKey = kCFBundleVersionKey as String - let currentVersion = NSBundle.mainBundle().objectForInfoDictionaryKey(bundleVersionKey) as? String + let currentVersion = Bundle.main.object(forInfoDictionaryKey: bundleVersionKey) as? String - StandardUserDefaults().setObject(trackingVersion, forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersion)) + StandardUserDefaults().setObject(trackingVersion as AnyObject?, forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersion)) StandardUserDefaults().setObject(StandardUserDefaults().objectForKey(keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)), forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersionRated)) StandardUserDefaults().setObject(StandardUserDefaults().objectForKey(keyForArmchairKeyType(ArmchairKey.DeclinedToRate)), forKey: keyForArmchairKeyType(ArmchairKey.PreviousVersionDeclinedToRate)) - StandardUserDefaults().setObject(currentVersion, forKey: currentVersionKey) + StandardUserDefaults().setObject(currentVersion as AnyObject?, forKey: currentVersionKey) resetUsageCounters() - StandardUserDefaults().setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)) - StandardUserDefaults().setObject(NSNumber(bool: false), forKey: keyForArmchairKeyType(ArmchairKey.DeclinedToRate)) - StandardUserDefaults().setObject(NSNumber(double: 0), forKey: keyForArmchairKeyType(ArmchairKey.ReminderRequestDate)) + StandardUserDefaults().setObject(NSNumber(value: false), forKey: keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)) + StandardUserDefaults().setObject(NSNumber(value: false), forKey: keyForArmchairKeyType(ArmchairKey.DeclinedToRate)) + StandardUserDefaults().setObject(NSNumber(value: 0), forKey: keyForArmchairKeyType(ArmchairKey.ReminderRequestDate)) StandardUserDefaults().synchronize() } /* -* -* -*/ + * + * + */ public func resetDefaults() { Manager.defaultManager.debugEnabled = false Manager.defaultManager.appName = Manager.defaultManager.defaultAppName() @@ -356,15 +356,15 @@ public func resetDefaults() { Manager.defaultManager.didDisplayAlertClosure = nil Manager.defaultManager.didOptToRateClosure = nil Manager.defaultManager.didOptToRemindLaterClosure = nil - -#if os(iOS) - Manager.defaultManager.usesAnimation = true - Manager.defaultManager.usesAlertController = Manager.defaultManager.defaultUsesAlertController() - Manager.defaultManager.opensInStoreKit = Manager.defaultManager.defaultOpensInStoreKit() - Manager.defaultManager.willPresentModalViewClosure = nil - Manager.defaultManager.didDismissModalViewClosure = nil -#endif - + + #if os(iOS) + Manager.defaultManager.usesAnimation = true + Manager.defaultManager.usesAlertController = Manager.defaultManager.defaultUsesAlertController() + Manager.defaultManager.opensInStoreKit = Manager.defaultManager.defaultOpensInStoreKit() + Manager.defaultManager.willPresentModalViewClosure = nil + Manager.defaultManager.didDismissModalViewClosure = nil + #endif + Manager.defaultManager.armchairKeyFirstUseDate = Manager.defaultManager.defaultArmchairKeyFirstUseDate() Manager.defaultManager.armchairKeyUseCount = Manager.defaultManager.defaultArmchairKeyUseCount() Manager.defaultManager.armchairKeySignificantEventCount = Manager.defaultManager.defaultArmchairKeySignificantEventCount() @@ -378,47 +378,47 @@ public func resetDefaults() { Manager.defaultManager.armchairKeyRatedAnyVersion = Manager.defaultManager.defaultArmchairKeyRatedAnyVersion() Manager.defaultManager.armchairKeyAppiraterMigrationCompleted = Manager.defaultManager.defaultArmchairKeyAppiraterMigrationCompleted() Manager.defaultManager.armchairKeyUAAppReviewManagerMigrationCompleted = Manager.defaultManager.defaultArmchairKeyUAAppReviewManagerMigrationCompleted() - + Manager.defaultManager.keyPrefix = Manager.defaultManager.defaultKeyPrefix() } #if os(iOS) /* - * Set whether or not Armchair uses animation when pushing modal StoreKit - * view controllers for the app. - * Default => true - */ + * Set whether or not Armchair uses animation when pushing modal StoreKit + * view controllers for the app. + * Default => true + */ public func usesAnimation() -> Bool { return Manager.defaultManager.usesAnimation } - public func usesAnimation(usesAnimation: Bool) { + public func usesAnimation(_ usesAnimation: Bool) { Manager.defaultManager.usesAnimation = usesAnimation } - + /* - * Set whether or not Armchair uses a UIAlertController when presenting on iOS 8 - * We prefer not to use it so that the Rate button can be on the bottom and the cancel button on the top, - * Something not possible as of iOS 8.0 - * Default => false - */ + * Set whether or not Armchair uses a UIAlertController when presenting on iOS 8 + * We prefer not to use it so that the Rate button can be on the bottom and the cancel button on the top, + * Something not possible as of iOS 8.0 + * Default => false + */ public func usesAlertController() -> Bool { return Manager.defaultManager.usesAlertController } - public func usesAlertController(usesAlertController: Bool) { + public func usesAlertController(_ usesAlertController: Bool) { Manager.defaultManager.usesAlertController = usesAlertController } - + /* - * If set to true, Armchair will open App Store link inside the app using - * SKStoreProductViewController. - * - itunes affiliate codes DO NOT work on iOS 7 inside StoreKit, - * - itunes affiliate codes DO work on iOS 8 inside StoreKit, - * Default => false on iOS 7, true on iOS 8+ - */ + * If set to true, Armchair will open App Store link inside the app using + * SKStoreProductViewController. + * - itunes affiliate codes DO NOT work on iOS 7 inside StoreKit, + * - itunes affiliate codes DO work on iOS 8 inside StoreKit, + * Default => false on iOS 7, true on iOS 8+ + */ public func opensInStoreKit() -> Bool { return Manager.defaultManager.opensInStoreKit } - public func opensInStoreKit(opensInStoreKit: Bool) { + public func opensInStoreKit(_ opensInStoreKit: Bool) { Manager.defaultManager.opensInStoreKit = opensInStoreKit } #endif @@ -426,79 +426,79 @@ public func resetDefaults() { // MARK: Events /* -* Tells Armchair that the user performed a significant event. -* A significant event is whatever you want it to be. If you're app is used -* to make VoIP calls, then you might want to call this method whenever the -* user places a call. If it's a game, you might want to call this whenever -* the user beats a level boss. -* -* If the user has performed enough significant events and used the app enough, -* you can suppress the rating alert by passing false for canPromptForRating. The -* rating alert will simply be postponed until it is called again with true for -* canPromptForRating. -*/ -public func userDidSignificantEvent(canPromptForRating: Bool) { + * Tells Armchair that the user performed a significant event. + * A significant event is whatever you want it to be. If you're app is used + * to make VoIP calls, then you might want to call this method whenever the + * user places a call. If it's a game, you might want to call this whenever + * the user beats a level boss. + * + * If the user has performed enough significant events and used the app enough, + * you can suppress the rating alert by passing false for canPromptForRating. The + * rating alert will simply be postponed until it is called again with true for + * canPromptForRating. + */ +public func userDidSignificantEvent(_ canPromptForRating: Bool) { Manager.defaultManager.userDidSignificantEvent(canPromptForRating) } /* -* Tells Armchair that the user performed a significant event. -* A significant event is whatever you want it to be. If you're app is used -* to make VoIP calls, then you might want to call this method whenever the -* user places a call. If it's a game, you might want to call this whenever -* the user beats a level boss. -* -* This is similar to the userDidSignificantEvent method, but allows the passing of a -* ArmchairShouldPromptClosure that will be executed before prompting. -* The closure passes all the keys and values that Armchair uses to -* determine if it the prompt conditions have been met, and it is up to you -* to use this info and return a Bool on whether or not the prompt should be shown. -* The closure is run synchronous and on the main queue, so be sure to handle it appropriately. -* Return true to proceed and show the prompt, return false to kill the pending presentation. -*/ -public func userDidSignificantEvent(shouldPrompt: ArmchairShouldPromptClosure) { + * Tells Armchair that the user performed a significant event. + * A significant event is whatever you want it to be. If you're app is used + * to make VoIP calls, then you might want to call this method whenever the + * user places a call. If it's a game, you might want to call this whenever + * the user beats a level boss. + * + * This is similar to the userDidSignificantEvent method, but allows the passing of a + * ArmchairShouldPromptClosure that will be executed before prompting. + * The closure passes all the keys and values that Armchair uses to + * determine if it the prompt conditions have been met, and it is up to you + * to use this info and return a Bool on whether or not the prompt should be shown. + * The closure is run synchronous and on the main queue, so be sure to handle it appropriately. + * Return true to proceed and show the prompt, return false to kill the pending presentation. + */ +public func userDidSignificantEvent(_ shouldPrompt: @escaping ArmchairShouldPromptClosure) { Manager.defaultManager.userDidSignificantEvent(shouldPrompt) } // MARK: Prompts /* -* Tells Armchair to show the prompt (a rating alert). The prompt -* will be showed if there is an internet connection available, the user hasn't -* declined to rate, hasn't rated current version and you are tracking new versions. -* -* You could call to show the prompt regardless of Armchair settings, -* for instance, in the case of some special event in your app. -*/ + * Tells Armchair to show the prompt (a rating alert). The prompt + * will be showed if there is an internet connection available, the user hasn't + * declined to rate, hasn't rated current version and you are tracking new versions. + * + * You could call to show the prompt regardless of Armchair settings, + * for instance, in the case of some special event in your app. + */ public func showPrompt() { Manager.defaultManager.showPrompt() } /* -* Tells Armchair to show the review prompt alert if all restrictions have been met. -* The prompt will be shown if all restrictions are met, there is an internet connection available, -* the user hasn't declined to rate, hasn't rated current version, and you are tracking new versions. -* -* You could call to show the prompt, for instance, in the case of some special event in your app, -* like a user login. -*/ + * Tells Armchair to show the review prompt alert if all restrictions have been met. + * The prompt will be shown if all restrictions are met, there is an internet connection available, + * the user hasn't declined to rate, hasn't rated current version, and you are tracking new versions. + * + * You could call to show the prompt, for instance, in the case of some special event in your app, + * like a user login. + */ public func showPromptIfNecessary() { Manager.defaultManager.showPrompt(ifNecessary: true) } /* -* Tells Armchair to show the review prompt alert. -* -* This is similar to the showPromptIfNecessary method, but allows the passing of a -* ArmchairShouldPromptClosure that will be executed before prompting. -* The closure passes all the keys and values that Armchair uses to -* determine if it the prompt conditions have been met, and it is up to you -* to use this info and return a Bool on whether or not the prompt should be shown. -* The closure is run synchronous and on the main queue, so be sure to handle it appropriately. -* Return true to proceed and show the prompt, return false to kill the pending presentation. -*/ -public func showPrompt(shouldPrompt: ArmchairShouldPromptClosure) { + * Tells Armchair to show the review prompt alert. + * + * This is similar to the showPromptIfNecessary method, but allows the passing of a + * ArmchairShouldPromptClosure that will be executed before prompting. + * The closure passes all the keys and values that Armchair uses to + * determine if it the prompt conditions have been met, and it is up to you + * to use this info and return a Bool on whether or not the prompt should be shown. + * The closure is run synchronous and on the main queue, so be sure to handle it appropriately. + * Return true to proceed and show the prompt, return false to kill the pending presentation. + */ +public func showPrompt(_ shouldPrompt: ArmchairShouldPromptClosure) { Manager.defaultManager.showPrompt(shouldPrompt) } @@ -511,33 +511,33 @@ public func ratingConditionsHaveBeenMet() -> Bool { // MARK: Misc /* -* This is the review URL string, generated by substituting the appID, affiliate code -* and affilitate campaign code into the template URL. -*/ + * This is the review URL string, generated by substituting the appID, affiliate code + * and affilitate campaign code into the template URL. + */ public func reviewURLString() -> String { return Manager.defaultManager.reviewURLString() } /* -* Tells Armchair to open the App Store page where the user can specify a -* rating for the app. Also records the fact that this has happened, so the -* user won't be prompted again to rate the app. -* -* The only case where you should call this directly is if your app has an -* explicit "Rate this app" command somewhere. In all other cases, don't worry -* about calling this -- instead, just call the other functions listed above, -* and let Armchair handle the bookkeeping of deciding when to ask the user -* whether to rate the app. -*/ + * Tells Armchair to open the App Store page where the user can specify a + * rating for the app. Also records the fact that this has happened, so the + * user won't be prompted again to rate the app. + * + * The only case where you should call this directly is if your app has an + * explicit "Rate this app" command somewhere. In all other cases, don't worry + * about calling this -- instead, just call the other functions listed above, + * and let Armchair handle the bookkeeping of deciding when to ask the user + * whether to rate the app. + */ public func rateApp() { Manager.defaultManager.rateApp() } #if os(iOS) /* - * Tells Armchair to immediately close any open rating modals - * for instance, a StoreKit rating View Controller. - */ + * Tells Armchair to immediately close any open rating modals + * for instance, a StoreKit rating View Controller. + */ public func closeModalPanel() { Manager.defaultManager.closeModalPanel() } @@ -545,70 +545,70 @@ public func rateApp() { // MARK: Closures /* -* Armchair uses closures instead of delegate methods for callbacks. -* Default is nil for all of them. -*/ + * Armchair uses closures instead of delegate methods for callbacks. + * Default is nil for all of them. + */ public typealias ArmchairClosure = () -> () public typealias ArmchairAnimateClosure = (Bool) -> () public typealias ArmchairShouldPromptClosure = (ArmchairTrackingInfo) -> Bool public typealias ArmchairShouldIncrementClosure = () -> Bool -public func onDidDisplayAlert(didDisplayAlertClosure: ArmchairClosure?) { +public func onDidDisplayAlert(_ didDisplayAlertClosure: ArmchairClosure?) { Manager.defaultManager.didDisplayAlertClosure = didDisplayAlertClosure } -public func onDidDeclineToRate(didDeclineToRateClosure: ArmchairClosure?) { +public func onDidDeclineToRate(_ didDeclineToRateClosure: ArmchairClosure?) { Manager.defaultManager.didDeclineToRateClosure = didDeclineToRateClosure } -public func onDidOptToRate(didOptToRateClosure: ArmchairClosure?) { +public func onDidOptToRate(_ didOptToRateClosure: ArmchairClosure?) { Manager.defaultManager.didOptToRateClosure = didOptToRateClosure } -public func onDidOptToRemindLater(didOptToRemindLaterClosure: ArmchairClosure?) { +public func onDidOptToRemindLater(_ didOptToRemindLaterClosure: ArmchairClosure?) { Manager.defaultManager.didOptToRemindLaterClosure = didOptToRemindLaterClosure } #if os(iOS) - public func onWillPresentModalView(willPresentModalViewClosure: ArmchairAnimateClosure?) { + public func onWillPresentModalView(_ willPresentModalViewClosure: ArmchairAnimateClosure?) { Manager.defaultManager.willPresentModalViewClosure = willPresentModalViewClosure } - public func onDidDismissModalView(didDismissModalViewClosure: ArmchairAnimateClosure?) { + public func onDidDismissModalView(_ didDismissModalViewClosure: ArmchairAnimateClosure?) { Manager.defaultManager.didDismissModalViewClosure = didDismissModalViewClosure } #endif /* -* The setShouldPromptClosure is called just after all the rating coditions -* have been met and Armchair has decided it should display a prompt, -* but just before the prompt actually displays. -* -* The closure passes all the keys and values that Armchair used to -* determine that the prompt conditions had been met, but it is up to you -* to use this info and return a Bool on whether or not the prompt should be shown. -* Return true to proceed and show the prompt, return false to kill the pending presentation. -*/ -public func shouldPromptClosure(shouldPromptClosure: ArmchairShouldPromptClosure?) { + * The setShouldPromptClosure is called just after all the rating coditions + * have been met and Armchair has decided it should display a prompt, + * but just before the prompt actually displays. + * + * The closure passes all the keys and values that Armchair used to + * determine that the prompt conditions had been met, but it is up to you + * to use this info and return a Bool on whether or not the prompt should be shown. + * Return true to proceed and show the prompt, return false to kill the pending presentation. + */ +public func shouldPromptClosure(_ shouldPromptClosure: ArmchairShouldPromptClosure?) { Manager.defaultManager.shouldPromptClosure = shouldPromptClosure } /* -* The setShouldIncrementUseClosure, if valid, is called before incrementing the use count. -* Returning false allows you to ignore a use. This may be usefull in cases such as Facebook login -* where the app is backgrounded momentarily and the resultant enter foreground event should -* not be considered another use. -*/ -public func shouldIncrementUseCountClosure(shouldIncrementUseCountClosure: ArmchairShouldIncrementClosure?) { + * The setShouldIncrementUseClosure, if valid, is called before incrementing the use count. + * Returning false allows you to ignore a use. This may be usefull in cases such as Facebook login + * where the app is backgrounded momentarily and the resultant enter foreground event should + * not be considered another use. + */ +public func shouldIncrementUseCountClosure(_ shouldIncrementUseCountClosure: ArmchairShouldIncrementClosure?) { Manager.defaultManager.shouldIncrementUseCountClosure = shouldIncrementUseCountClosure } // MARK: Armchair Logger Protocol -public typealias ArmchairLogger = (Manager, log: String, file: StaticString, function: StaticString, line: UInt) -> Void +public typealias ArmchairLogger = (Manager, _ log: String, _ file: StaticString, _ function: StaticString, _ line: UInt) -> Void /* -* Set a closure to capture debug log and to plug in the desired logging framework. -*/ -public func logger(logger: ArmchairLogger) { + * Set a closure to capture debug log and to plug in the desired logging framework. + */ +public func logger(_ logger: @escaping ArmchairLogger) { Manager.defaultManager.logger = logger } @@ -616,39 +616,41 @@ public func logger(logger: ArmchairLogger) { // MARK: Armchair Defaults Protocol @objc public protocol ArmchairDefaultsObject { - func objectForKey(defaultName: String) -> AnyObject? - func setObject(value: AnyObject?, forKey defaultName: String) - func removeObjectForKey(defaultName: String) - - func stringForKey(defaultName: String) -> String? - func integerForKey(defaultName: String) -> Int - func doubleForKey(defaultName: String) -> Double - func boolForKey(defaultName: String) -> Bool - - func setInteger(value: Int, forKey defaultName: String) - func setDouble(value: Double, forKey defaultName: String) - func setBool(value: Bool, forKey defaultName: String) + func objectForKey(_ defaultName: String) -> AnyObject? + func setObject(_ value: AnyObject?, forKey defaultName: String) + func removeObjectForKey(_ defaultName: String) + func stringForKey(_ defaultName: String) -> String? + func integerForKey(_ defaultName: String) -> Int + func doubleForKey(_ defaultName: String) -> Double + func boolForKey(_ defaultName: String) -> Bool + + func setInteger(_ value: Int, forKey defaultName: String) + func setDouble(_ value: Double, forKey defaultName: String) + func setBool(_ value: Bool, forKey defaultName: String) + + @discardableResult func synchronize() -> Bool } -public class StandardUserDefaults: ArmchairDefaultsObject { - let defaults = NSUserDefaults.standardUserDefaults() - - @objc public func objectForKey(defaultName: String) -> AnyObject? { return defaults.objectForKey(defaultName) } - @objc public func setObject(value: AnyObject?, forKey defaultName: String) { defaults.setObject(value, forKey: defaultName) } - @objc public func removeObjectForKey(defaultName: String) { defaults.removeObjectForKey(defaultName) } - - @objc public func stringForKey(defaultName: String) -> String? { return defaults.stringForKey(defaultName) } - @objc public func integerForKey(defaultName: String) -> Int { return defaults.integerForKey(defaultName) } - @objc public func doubleForKey(defaultName: String) -> Double { return defaults.doubleForKey(defaultName) } - @objc public func boolForKey(defaultName: String) -> Bool { return defaults.boolForKey(defaultName) } - - @objc public func setInteger(value: Int, forKey defaultName: String) { defaults.setInteger(value, forKey: defaultName) } - @objc public func setDouble(value: Double, forKey defaultName: String) { defaults.setDouble(value, forKey: defaultName) } - @objc public func setBool(value: Bool, forKey defaultName: String) { defaults.setBool(value, forKey: defaultName) } - - @objc public func synchronize() -> Bool { return defaults.synchronize() } +open class StandardUserDefaults: ArmchairDefaultsObject { + let defaults = UserDefaults.standard + + @objc open func objectForKey(_ defaultName: String) -> AnyObject? { return defaults.object(forKey: defaultName) as AnyObject? } + @objc open func setObject(_ value: AnyObject?, forKey defaultName: String) { defaults.set(value, forKey: defaultName) } + @objc open func removeObjectForKey(_ defaultName: String) { defaults.removeObject(forKey: defaultName) } + + @objc open func stringForKey(_ defaultName: String) -> String? { return defaults.string(forKey: defaultName) } + @objc open func integerForKey(_ defaultName: String) -> Int { return defaults.integer(forKey: defaultName) } + @objc open func doubleForKey(_ defaultName: String) -> Double { return defaults.double(forKey: defaultName) } + @objc open func boolForKey(_ defaultName: String) -> Bool { return defaults.bool(forKey: defaultName) } + + @objc open func setInteger(_ value: Int, forKey defaultName: String) { defaults.set(value, forKey: defaultName) } + @objc open func setDouble(_ value: Double, forKey defaultName: String) { defaults.set(value, forKey: defaultName) } + @objc open func setBool(_ value: Bool, forKey defaultName: String) { defaults.set(value, forKey: defaultName) } + + @discardableResult + @objc open func synchronize() -> Bool { return defaults.synchronize() } } public enum ArmchairKey: String, CustomStringConvertible { @@ -665,9 +667,9 @@ public enum ArmchairKey: String, CustomStringConvertible { case RatedAnyVersion = "Rated Any Version" case AppiraterMigrationCompleted = "Appirater Migration Completed" case UAAppReviewManagerMigrationCompleted = "UAAppReviewManager Migration Completed" - + static let allValues = [FirstUseDate, UseCount, SignificantEventCount, CurrentVersion, RatedCurrentVersion, DeclinedToRate, ReminderRequestDate, PreviousVersion, PreviousVersionRated, PreviousVersionDeclinedToRate, RatedAnyVersion, AppiraterMigrationCompleted, UAAppReviewManagerMigrationCompleted] - + public var description : String { get { return self.rawValue @@ -675,14 +677,14 @@ public enum ArmchairKey: String, CustomStringConvertible { } } -public class ArmchairTrackingInfo: CustomStringConvertible { - public let info: Dictionary - +open class ArmchairTrackingInfo: CustomStringConvertible { + open let info: Dictionary + init(info: Dictionary) { self.info = info } - - public var description: String { + + open var description: String { get { var description = "ArmchairTrackingInfo\r" for (key, val) in info { @@ -708,97 +710,97 @@ public struct AppiraterKey { // MARK: PRIVATE Interface #if os(iOS) -public class ArmchairManager : NSObject, UIAlertViewDelegate, SKStoreProductViewControllerDelegate { } + open class ArmchairManager : NSObject, UIAlertViewDelegate, SKStoreProductViewControllerDelegate { } #elseif os(OSX) -public class ArmchairManager : NSObject, NSAlertDelegate { } + public class ArmchairManager : NSObject, NSAlertDelegate { } #else -// Untested, and currently unsupported + // Untested, and currently unsupported #endif -public class Manager : ArmchairManager { - -#if os(iOS) - private var operatingSystemVersion = NSString(string: UIDevice.currentDevice().systemVersion).doubleValue -#elseif os(OSX) +open class Manager : ArmchairManager { + + #if os(iOS) + fileprivate var operatingSystemVersion = NSString(string: UIDevice.current.systemVersion).doubleValue + #elseif os(OSX) private var operatingSystemVersion = Double(NSProcessInfo.processInfo().operatingSystemVersion.majorVersion) -#else -#endif + #else + #endif // MARK: - // MARK: Review Alert & Properties - -#if os(iOS) - private var ratingAlert: UIAlertView? = nil - private let reviewURLTemplate = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&id=APP_ID&at=AFFILIATE_CODE&ct=AFFILIATE_CAMPAIGN_CODE" -#elseif os(OSX) + + #if os(iOS) + fileprivate var ratingAlert: UIAlertView? = nil + fileprivate let reviewURLTemplate = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&id=APP_ID&at=AFFILIATE_CODE&ct=AFFILIATE_CAMPAIGN_CODE" + #elseif os(OSX) private var ratingAlert: NSAlert? = nil private let reviewURLTemplate = "macappstore://itunes.apple.com/us/app/idAPP_ID?ls=1&mt=12&at=AFFILIATE_CODE&ct=AFFILIATE_CAMPAIGN_CODE" -#else -#endif - - private lazy var appName: String = self.defaultAppName() - private func defaultAppName() -> String { - let mainBundle = NSBundle.mainBundle() - let displayName = mainBundle.objectForInfoDictionaryKey("CFBundleDisplayName") as? String + #else + #endif + + fileprivate lazy var appName: String = self.defaultAppName() + fileprivate func defaultAppName() -> String { + let mainBundle = Bundle.main + let displayName = mainBundle.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String let bundleNameKey = kCFBundleNameKey as String - let name = mainBundle.objectForInfoDictionaryKey(bundleNameKey) as? String + let name = mainBundle.object(forInfoDictionaryKey: bundleNameKey) as? String return displayName ?? name ?? "This App" } - private lazy var reviewTitle: String = self.defaultReviewTitle() - private func defaultReviewTitle() -> String { + fileprivate lazy var reviewTitle: String = self.defaultReviewTitle() + fileprivate func defaultReviewTitle() -> String { var template = "Rate %@" // Check for a localized version of the default title if let bundle = self.bundle() { - template = bundle.localizedStringForKey(template, - value: bundle.localizedStringForKey(template, value:"", table: nil), - table: "ArmchairLocalizable") + template = bundle.localizedString(forKey: template, + value: bundle.localizedString(forKey: template, value:"", table: nil), + table: "ArmchairLocalizable") } - - return template.stringByReplacingOccurrencesOfString("%@", withString: "\(self.appName)", options: NSStringCompareOptions(rawValue: 0), range: nil) + + return template.replacingOccurrences(of: "%@", with: "\(self.appName)", options: NSString.CompareOptions(rawValue: 0), range: nil) } - - private lazy var reviewMessage: String = self.defaultReviewMessage() - private func defaultReviewMessage() -> String { + + fileprivate lazy var reviewMessage: String = self.defaultReviewMessage() + fileprivate func defaultReviewMessage() -> String { var template = "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!" // Check for a localized version of the default title if let bundle = self.bundle() { - template = bundle.localizedStringForKey(template, - value: bundle.localizedStringForKey(template, value:"", table: nil), - table: "ArmchairLocalizable") + template = bundle.localizedString(forKey: template, + value: bundle.localizedString(forKey: template, value:"", table: nil), + table: "ArmchairLocalizable") } - - return template.stringByReplacingOccurrencesOfString("%@", withString: "\(self.appName)", options: NSStringCompareOptions(rawValue: 0), range: nil) + + return template.replacingOccurrences(of: "%@", with: "\(self.appName)", options: NSString.CompareOptions(rawValue: 0), range: nil) } - - private lazy var cancelButtonTitle: String = self.defaultCancelButtonTitle() - private func defaultCancelButtonTitle() -> String { + + fileprivate lazy var cancelButtonTitle: String = self.defaultCancelButtonTitle() + fileprivate func defaultCancelButtonTitle() -> String { var title = "No, Thanks" // Check for a localized version of the default title if let bundle = self.bundle() { - title = bundle.localizedStringForKey(title, - value: bundle.localizedStringForKey(title, value:"", table: nil), - table: "ArmchairLocalizable") + title = bundle.localizedString(forKey: title, + value: bundle.localizedString(forKey: title, value:"", table: nil), + table: "ArmchairLocalizable") } - + return title } - - private lazy var rateButtonTitle: String = self.defaultRateButtonTitle() - private func defaultRateButtonTitle() -> String { + + fileprivate lazy var rateButtonTitle: String = self.defaultRateButtonTitle() + fileprivate func defaultRateButtonTitle() -> String { var template = "Rate %@" // Check for a localized version of the default title if let bundle = self.bundle() { - template = bundle.localizedStringForKey(template, - value: bundle.localizedStringForKey(template, value:"", table: nil), - table: "ArmchairLocalizable") + template = bundle.localizedString(forKey: template, + value: bundle.localizedString(forKey: template, value:"", table: nil), + table: "ArmchairLocalizable") } - - return template.stringByReplacingOccurrencesOfString("%@", withString: "\(self.appName)", options: NSStringCompareOptions(rawValue: 0), range: nil) + + return template.replacingOccurrences(of: "%@", with: "\(self.appName)", options: NSString.CompareOptions(rawValue: 0), range: nil) } - - private lazy var remindButtonTitle: String? = self.defaultRemindButtonTitle() - private func defaultRemindButtonTitle() -> String? { + + fileprivate lazy var remindButtonTitle: String? = self.defaultRemindButtonTitle() + fileprivate func defaultRemindButtonTitle() -> String? { //if reminders are disabled, return a nil title to supress the button if self.daysBeforeReminding == 0 { return nil @@ -806,16 +808,16 @@ public class Manager : ArmchairManager { var title = "Remind me later" // Check for a localized version of the default title if let bundle = self.bundle() { - title = bundle.localizedStringForKey(title, - value: bundle.localizedStringForKey(title, value:"", table: nil), - table: "ArmchairLocalizable") + title = bundle.localizedString(forKey: title, + value: bundle.localizedString(forKey: title, value:"", table: nil), + table: "ArmchairLocalizable") } - + return title } - + // Tracking Logic / Configuration - private var appID: String = "" { + fileprivate var appID: String = "" { didSet { keyPrefix = defaultKeyPrefix() if affiliateCampaignCode == defaultAffiliateCampaignCode() { @@ -823,275 +825,277 @@ public class Manager : ArmchairManager { } } } - + // MARK: Properties with sensible defaults - private var daysUntilPrompt: UInt = 30 - private var usesUntilPrompt: UInt = 20 - private var significantEventsUntilPrompt: UInt = 0 - private var daysBeforeReminding: UInt = 1 - private var tracksNewVersions: Bool = true - private var shouldPromptIfRated: Bool = true - private var useMainAppBundleForLocalizations: Bool = false - private var debugEnabled: Bool = false { + fileprivate var daysUntilPrompt: UInt = 30 + fileprivate var usesUntilPrompt: UInt = 20 + fileprivate var significantEventsUntilPrompt: UInt = 0 + fileprivate var daysBeforeReminding: UInt = 1 + fileprivate var tracksNewVersions: Bool = true + fileprivate var shouldPromptIfRated: Bool = true + fileprivate var useMainAppBundleForLocalizations: Bool = false + fileprivate var debugEnabled: Bool = false { didSet { if self.debugEnabled { debugLog("Debug enabled for app: \(appID)") } } } - + // If you aren't going to set an affiliate code yourself, please leave this as is. // It is my affiliate code. It is better that somebody's code is used rather than nobody's. - private var affiliateCode: String = "11l7j9" - private var affiliateCampaignCode: String = "Armchair" - -#if os(iOS) - private var usesAnimation: Bool = true - private lazy var usesAlertController: Bool = self.defaultUsesAlertController() - private lazy var opensInStoreKit: Bool = self.defaultOpensInStoreKit() - - private func defaultOpensInStoreKit() -> Bool { + fileprivate var affiliateCode: String = "11l7j9" + fileprivate var affiliateCampaignCode: String = "Armchair" + + #if os(iOS) + fileprivate var usesAnimation: Bool = true + fileprivate lazy var usesAlertController: Bool = self.defaultUsesAlertController() + fileprivate lazy var opensInStoreKit: Bool = self.defaultOpensInStoreKit() + + fileprivate func defaultOpensInStoreKit() -> Bool { return operatingSystemVersion >= 8 } - private func defaultUsesAlertController() -> Bool { + fileprivate func defaultUsesAlertController() -> Bool { return operatingSystemVersion >= 9 } -#endif - + #endif + // MARK: Tracking Keys with sensible defaults - private lazy var armchairKeyFirstUseDate: String = self.defaultArmchairKeyFirstUseDate() - private lazy var armchairKeyUseCount: String = self.defaultArmchairKeyUseCount() - private lazy var armchairKeySignificantEventCount: String = self.defaultArmchairKeySignificantEventCount() - private lazy var armchairKeyCurrentVersion: String = self.defaultArmchairKeyCurrentVersion() - private lazy var armchairKeyRatedCurrentVersion: String = self.defaultArmchairKeyRatedCurrentVersion() - private lazy var armchairKeyDeclinedToRate: String = self.defaultArmchairKeyDeclinedToRate() - private lazy var armchairKeyReminderRequestDate: String = self.defaultArmchairKeyReminderRequestDate() - private lazy var armchairKeyPreviousVersion: String = self.defaultArmchairKeyPreviousVersion() - private lazy var armchairKeyPreviousVersionRated: String = self.defaultArmchairKeyPreviousVersionRated() - private lazy var armchairKeyPreviousVersionDeclinedToRate: String = self.defaultArmchairKeyPreviousVersionDeclinedToRate() - private lazy var armchairKeyRatedAnyVersion: String = self.defaultArmchairKeyRatedAnyVersion() - private lazy var armchairKeyAppiraterMigrationCompleted: String = self.defaultArmchairKeyAppiraterMigrationCompleted() - private lazy var armchairKeyUAAppReviewManagerMigrationCompleted: String = self.defaultArmchairKeyUAAppReviewManagerMigrationCompleted() - - private func defaultArmchairKeyFirstUseDate() -> String { return "ArmchairFirstUseDate" } - private func defaultArmchairKeyUseCount() -> String { return "ArmchairUseCount" } - private func defaultArmchairKeySignificantEventCount() -> String { return "ArmchairSignificantEventCount" } - private func defaultArmchairKeyCurrentVersion() -> String { return "ArmchairKeyCurrentVersion" } - private func defaultArmchairKeyRatedCurrentVersion() -> String { return "ArmchairRatedCurrentVersion" } - private func defaultArmchairKeyDeclinedToRate() -> String { return "ArmchairKeyDeclinedToRate" } - private func defaultArmchairKeyReminderRequestDate() -> String { return "ArmchairReminderRequestDate" } - private func defaultArmchairKeyPreviousVersion() -> String { return "ArmchairPreviousVersion" } - private func defaultArmchairKeyPreviousVersionRated() -> String { return "ArmchairPreviousVersionRated" } - private func defaultArmchairKeyPreviousVersionDeclinedToRate() -> String { return "ArmchairPreviousVersionDeclinedToRate" } - private func defaultArmchairKeyRatedAnyVersion() -> String { return "ArmchairKeyRatedAnyVersion" } - private func defaultArmchairKeyAppiraterMigrationCompleted() -> String { return "ArmchairAppiraterMigrationCompleted" } - private func defaultArmchairKeyUAAppReviewManagerMigrationCompleted() -> String { return "ArmchairUAAppReviewManagerMigrationCompleted" } - - - private lazy var keyPrefix: String = self.defaultKeyPrefix() - private func defaultKeyPrefix() -> String { + fileprivate lazy var armchairKeyFirstUseDate: String = self.defaultArmchairKeyFirstUseDate() + fileprivate lazy var armchairKeyUseCount: String = self.defaultArmchairKeyUseCount() + fileprivate lazy var armchairKeySignificantEventCount: String = self.defaultArmchairKeySignificantEventCount() + fileprivate lazy var armchairKeyCurrentVersion: String = self.defaultArmchairKeyCurrentVersion() + fileprivate lazy var armchairKeyRatedCurrentVersion: String = self.defaultArmchairKeyRatedCurrentVersion() + fileprivate lazy var armchairKeyDeclinedToRate: String = self.defaultArmchairKeyDeclinedToRate() + fileprivate lazy var armchairKeyReminderRequestDate: String = self.defaultArmchairKeyReminderRequestDate() + fileprivate lazy var armchairKeyPreviousVersion: String = self.defaultArmchairKeyPreviousVersion() + fileprivate lazy var armchairKeyPreviousVersionRated: String = self.defaultArmchairKeyPreviousVersionRated() + fileprivate lazy var armchairKeyPreviousVersionDeclinedToRate: String = self.defaultArmchairKeyPreviousVersionDeclinedToRate() + fileprivate lazy var armchairKeyRatedAnyVersion: String = self.defaultArmchairKeyRatedAnyVersion() + fileprivate lazy var armchairKeyAppiraterMigrationCompleted: String = self.defaultArmchairKeyAppiraterMigrationCompleted() + fileprivate lazy var armchairKeyUAAppReviewManagerMigrationCompleted: String = self.defaultArmchairKeyUAAppReviewManagerMigrationCompleted() + + fileprivate func defaultArmchairKeyFirstUseDate() -> String { return "ArmchairFirstUseDate" } + fileprivate func defaultArmchairKeyUseCount() -> String { return "ArmchairUseCount" } + fileprivate func defaultArmchairKeySignificantEventCount() -> String { return "ArmchairSignificantEventCount" } + fileprivate func defaultArmchairKeyCurrentVersion() -> String { return "ArmchairKeyCurrentVersion" } + fileprivate func defaultArmchairKeyRatedCurrentVersion() -> String { return "ArmchairRatedCurrentVersion" } + fileprivate func defaultArmchairKeyDeclinedToRate() -> String { return "ArmchairKeyDeclinedToRate" } + fileprivate func defaultArmchairKeyReminderRequestDate() -> String { return "ArmchairReminderRequestDate" } + fileprivate func defaultArmchairKeyPreviousVersion() -> String { return "ArmchairPreviousVersion" } + fileprivate func defaultArmchairKeyPreviousVersionRated() -> String { return "ArmchairPreviousVersionRated" } + fileprivate func defaultArmchairKeyPreviousVersionDeclinedToRate() -> String { return "ArmchairPreviousVersionDeclinedToRate" } + fileprivate func defaultArmchairKeyRatedAnyVersion() -> String { return "ArmchairKeyRatedAnyVersion" } + fileprivate func defaultArmchairKeyAppiraterMigrationCompleted() -> String { return "ArmchairAppiraterMigrationCompleted" } + fileprivate func defaultArmchairKeyUAAppReviewManagerMigrationCompleted() -> String { return "ArmchairUAAppReviewManagerMigrationCompleted" } + + + fileprivate lazy var keyPrefix: String = self.defaultKeyPrefix() + fileprivate func defaultKeyPrefix() -> String { if !self.appID.isEmpty { return self.appID + "_" } else { return "_" } } - - private var userDefaultsObject:ArmchairDefaultsObject? = StandardUserDefaults() - + + fileprivate var userDefaultsObject:ArmchairDefaultsObject? = StandardUserDefaults() + // MARK: Optional Closures var didDisplayAlertClosure: ArmchairClosure? var didDeclineToRateClosure: ArmchairClosure? var didOptToRateClosure: ArmchairClosure? var didOptToRemindLaterClosure: ArmchairClosure? - -#if os(iOS) + + #if os(iOS) var willPresentModalViewClosure: ArmchairAnimateClosure? var didDismissModalViewClosure: ArmchairAnimateClosure? -#endif + #endif var shouldPromptClosure: ArmchairShouldPromptClosure? var shouldIncrementUseCountClosure: ArmchairShouldIncrementClosure? - + // MARK: State Vars - private var modalPanelOpen: Bool = false -#if os(iOS) - private lazy var currentStatusBarStyle: UIStatusBarStyle = { - return UIApplication.sharedApplication().statusBarStyle + fileprivate var modalPanelOpen: Bool = false + #if os(iOS) + fileprivate lazy var currentStatusBarStyle: UIStatusBarStyle = { + return UIApplication.shared.statusBarStyle }() -#endif - + #endif + // MARK: - // MARK: PRIVATE Methods - - private func userDidSignificantEvent(canPromptForRating: Bool) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)) { + + fileprivate func userDidSignificantEvent(_ canPromptForRating: Bool) { + DispatchQueue.global(qos: .background).async { self.incrementSignificantEventAndRate(canPromptForRating) } } - - private func userDidSignificantEvent(shouldPrompt: ArmchairShouldPromptClosure) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)) { + + fileprivate func userDidSignificantEvent(_ shouldPrompt: @escaping ArmchairShouldPromptClosure) { + DispatchQueue.global(qos: .background).async { self.incrementSignificantEventAndRate(shouldPrompt) } } - + // MARK: - // MARK: PRIVATE Rating Helpers - - private func incrementAndRate(canPromptForRating: Bool) { + + fileprivate func incrementAndRate(_ canPromptForRating: Bool) { migrateKeysIfNecessary() incrementUseCount() showPrompt(ifNecessary: canPromptForRating) } - - private func incrementAndRate(shouldPrompt: ArmchairShouldPromptClosure) { + + fileprivate func incrementAndRate(_ shouldPrompt: ArmchairShouldPromptClosure) { migrateKeysIfNecessary() incrementUseCount() showPrompt(shouldPrompt) } - - private func incrementSignificantEventAndRate(canPromptForRating: Bool) { + + fileprivate func incrementSignificantEventAndRate(_ canPromptForRating: Bool) { migrateKeysIfNecessary() incrementSignificantEventCount() showPrompt(ifNecessary: canPromptForRating) } - - private func incrementSignificantEventAndRate(shouldPrompt: ArmchairShouldPromptClosure) { + + fileprivate func incrementSignificantEventAndRate(_ shouldPrompt: ArmchairShouldPromptClosure) { migrateKeysIfNecessary() incrementSignificantEventCount() showPrompt(shouldPrompt) } - - private func incrementUseCount() { + + fileprivate func incrementUseCount() { var shouldIncrement = true if let closure = shouldIncrementUseCountClosure { shouldIncrement = closure() } - + if shouldIncrement { _incrementCountForKeyType(ArmchairKey.UseCount) } } - - private func incrementSignificantEventCount() { + + fileprivate func incrementSignificantEventCount() { _incrementCountForKeyType(ArmchairKey.SignificantEventCount) } - - private func _incrementCountForKeyType(incrementKeyType: ArmchairKey) { + + fileprivate func _incrementCountForKeyType(_ incrementKeyType: ArmchairKey) { let incrementKey = keyForArmchairKeyType(incrementKeyType) - + let bundleVersionKey = kCFBundleVersionKey as String // App's version. Not settable as the other ivars because that would be crazy. - let currentVersion = NSBundle.mainBundle().objectForInfoDictionaryKey(bundleVersionKey) as? String + let currentVersion = Bundle.main.object(forInfoDictionaryKey: bundleVersionKey) as? String if currentVersion == nil { assertionFailure("Could not read kCFBundleVersionKey from InfoDictionary") return } - + // Get the version number that we've been tracking thus far let currentVersionKey = keyForArmchairKeyType(ArmchairKey.CurrentVersion) var trackingVersion: String? = userDefaultsObject?.stringForKey(currentVersionKey) // New install, or changed keys if trackingVersion == nil { trackingVersion = currentVersion - userDefaultsObject?.setObject(currentVersion, forKey: currentVersionKey) + userDefaultsObject?.setObject(currentVersion as AnyObject?, forKey: currentVersionKey) } - + debugLog("Tracking version: \(trackingVersion!)") - + if trackingVersion == currentVersion { // Check if the first use date has been set. if not, set it. let firstUseDateKey = keyForArmchairKeyType(ArmchairKey.FirstUseDate) var timeInterval: Double? = userDefaultsObject?.doubleForKey(firstUseDateKey) if 0 == timeInterval { - timeInterval = NSDate().timeIntervalSince1970 - userDefaultsObject?.setObject(NSNumber(double: timeInterval!), forKey: firstUseDateKey) + timeInterval = Date().timeIntervalSince1970 + userDefaultsObject?.setObject(NSNumber(value: timeInterval!), forKey: firstUseDateKey) } - + // Increment the key's count - var incrementKeyCount = userDefaultsObject?.integerForKey(incrementKey) - userDefaultsObject?.setInteger(++incrementKeyCount!, forKey:incrementKey) - - debugLog("Incremented \(incrementKeyType): \(incrementKeyCount!)") - + var incrementKeyCount = userDefaultsObject!.integerForKey(incrementKey) + incrementKeyCount += 1 + + userDefaultsObject?.setInteger(incrementKeyCount, forKey:incrementKey) + + debugLog("Incremented \(incrementKeyType): \(incrementKeyCount)") + } else if tracksNewVersions { // it's a new version of the app, so restart tracking resetAllCounters() debugLog("Reset Tracking Version to: \(trackingVersion!)") } - + userDefaultsObject?.synchronize() } - - private func showPrompt(ifNecessary canPromptForRating: Bool) { + + fileprivate func showPrompt(ifNecessary canPromptForRating: Bool) { if canPromptForRating && connectedToNetwork() && ratingConditionsHaveBeenMet() { var shouldPrompt: Bool = true if let closure = shouldPromptClosure { - if NSThread.isMainThread() { + if Thread.isMainThread { shouldPrompt = closure(trackingInfo()) } else { - dispatch_sync(dispatch_get_main_queue()) { + DispatchQueue.main.sync { shouldPrompt = closure(self.trackingInfo()) } } } - + if shouldPrompt { - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { self.showRatingAlert() } } } } - - private func showPrompt(shouldPrompt: ArmchairShouldPromptClosure) { + + fileprivate func showPrompt(_ shouldPrompt: ArmchairShouldPromptClosure) { var shouldPromptVal = false - - if NSThread.isMainThread() { + + if Thread.isMainThread { shouldPromptVal = shouldPrompt(trackingInfo()) } else { - dispatch_sync(dispatch_get_main_queue()) { + DispatchQueue.main.sync { shouldPromptVal = shouldPrompt(self.trackingInfo()) } } - - + + if (shouldPromptVal) { - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { self.showRatingAlert() } } } - - private func showPrompt() { + + fileprivate func showPrompt() { if !appID.isEmpty && connectedToNetwork() && !userHasDeclinedToRate() && !userHasRatedCurrentVersion() { showRatingAlert() } } - - private func ratingConditionsHaveBeenMet() -> Bool { + + fileprivate func ratingConditionsHaveBeenMet() -> Bool { if debugEnabled { return true } - + if appID.isEmpty { return false } - + // check if the app has been used long enough let timeIntervalOfFirstLaunch = userDefaultsObject?.doubleForKey(keyForArmchairKeyType(ArmchairKey.FirstUseDate)) if let timeInterval = timeIntervalOfFirstLaunch { - let dateOfFirstLaunch = NSDate(timeIntervalSince1970: timeInterval) - let timeSinceFirstLaunch = NSDate().timeIntervalSinceDate(dateOfFirstLaunch) - let timeUntilRate: NSTimeInterval = 60 * 60 * 24 * Double(daysUntilPrompt) + let dateOfFirstLaunch = Date(timeIntervalSince1970: timeInterval) + let timeSinceFirstLaunch = Date().timeIntervalSince(dateOfFirstLaunch) + let timeUntilRate: TimeInterval = 60 * 60 * 24 * Double(daysUntilPrompt) if timeSinceFirstLaunch < timeUntilRate { return false } } else { return false } - + // check if the app has been used enough times let useCount = userDefaultsObject?.integerForKey(keyForArmchairKeyType(ArmchairKey.UseCount)) if let count = useCount { @@ -1101,7 +1105,7 @@ public class Manager : ArmchairManager { } else { return false } - + // check if the user has done enough significant events let significantEventCount = userDefaultsObject?.integerForKey(keyForArmchairKeyType(ArmchairKey.SignificantEventCount)) if let count = significantEventCount { @@ -1111,30 +1115,30 @@ public class Manager : ArmchairManager { } else { return false } - + // Check if the user previously has declined to rate this version of the app if userHasDeclinedToRate() { return false } - + // Check if the user has already rated the app? if userHasRatedCurrentVersion() { return false } - + // If the user wanted to be reminded later, has enough time passed? let timeIntervalOfReminder = userDefaultsObject?.doubleForKey(keyForArmchairKeyType(ArmchairKey.ReminderRequestDate)) if let timeInterval = timeIntervalOfReminder { - let reminderRequestDate = NSDate(timeIntervalSince1970: timeInterval) - let timeSinceReminderRequest = NSDate().timeIntervalSinceDate(reminderRequestDate) - let timeUntilReminder: NSTimeInterval = 60 * 60 * 24 * Double(daysBeforeReminding) + let reminderRequestDate = Date(timeIntervalSince1970: timeInterval) + let timeSinceReminderRequest = Date().timeIntervalSince(reminderRequestDate) + let timeUntilReminder: TimeInterval = 60 * 60 * 24 * Double(daysBeforeReminding) if timeSinceReminderRequest < timeUntilReminder { return false } } else { return false } - + // if we have a global set to not show if the end-user has already rated once, and the developer has not opted out of displaying on minor updates let ratedAnyVersion = userDefaultsObject?.boolForKey(keyForArmchairKeyType(ArmchairKey.RatedAnyVersion)) if let ratedAlready = ratedAnyVersion { @@ -1142,113 +1146,113 @@ public class Manager : ArmchairManager { return false } } - + return true } - - private func userHasDeclinedToRate() -> Bool { + + fileprivate func userHasDeclinedToRate() -> Bool { if let declined = userDefaultsObject?.boolForKey(keyForArmchairKeyType(ArmchairKey.DeclinedToRate)) { return declined } else { return false } } - - private func userHasRatedCurrentVersion() -> Bool { + + fileprivate func userHasRatedCurrentVersion() -> Bool { if let ratedCurrentVersion = userDefaultsObject?.boolForKey(keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)) { return ratedCurrentVersion } else { return false } } - - private func showsRemindButton() -> Bool { + + fileprivate func showsRemindButton() -> Bool { return (daysBeforeReminding > 0 && remindButtonTitle != nil) } - - private func showRatingAlert() { -#if os(iOS) - if (operatingSystemVersion >= 8 && usesAlertController) || operatingSystemVersion >= 9 { - /* iOS 8 uses new UIAlertController API*/ - let alertView : UIAlertController = UIAlertController(title: reviewTitle, message: reviewMessage, preferredStyle: UIAlertControllerStyle.Alert) - alertView.addAction(UIAlertAction(title: cancelButtonTitle, style:UIAlertActionStyle.Cancel, handler: { - (alert: UIAlertAction!) in - self.dontRate() - })) - if (showsRemindButton()) { - alertView.addAction(UIAlertAction(title: remindButtonTitle!, style:UIAlertActionStyle.Default, handler: { + + fileprivate func showRatingAlert() { + #if os(iOS) + if (operatingSystemVersion >= 8 && usesAlertController) || operatingSystemVersion >= 9 { + /* iOS 8 uses new UIAlertController API*/ + let alertView : UIAlertController = UIAlertController(title: reviewTitle, message: reviewMessage, preferredStyle: UIAlertControllerStyle.alert) + alertView.addAction(UIAlertAction(title: cancelButtonTitle, style:UIAlertActionStyle.cancel, handler: { (alert: UIAlertAction!) in - self.remindMeLater() + self.dontRate() })) - } - alertView.addAction(UIAlertAction(title: rateButtonTitle, style:UIAlertActionStyle.Default, handler: { - (alert: UIAlertAction!) in - self._rateApp() - })) - - // get the top most controller (= the StoreKit Controller) and dismiss it - if let presentingController = UIApplication.sharedApplication().keyWindow?.rootViewController { - if let topController = topMostViewController(presentingController) { - topController.presentViewController(alertView, animated: usesAnimation) { - print("presentViewController() completed") + if (showsRemindButton()) { + alertView.addAction(UIAlertAction(title: remindButtonTitle!, style:UIAlertActionStyle.default, handler: { + (alert: UIAlertAction!) in + self.remindMeLater() + })) + } + alertView.addAction(UIAlertAction(title: rateButtonTitle, style:UIAlertActionStyle.default, handler: { + (alert: UIAlertAction!) in + self._rateApp() + })) + + // get the top most controller (= the StoreKit Controller) and dismiss it + if let presentingController = UIApplication.shared.keyWindow?.rootViewController { + if let topController = topMostViewController(presentingController) { + topController.present(alertView, animated: usesAnimation) { + print("presentViewController() completed") + } } } - } - - } else { - /* Otherwise we use UIAlertView still */ - var alertView: UIAlertView - if (showsRemindButton()) { - alertView = UIAlertView(title: reviewTitle, message: reviewMessage, delegate: self, cancelButtonTitle: cancelButtonTitle, otherButtonTitles: remindButtonTitle!, rateButtonTitle) + } else { - alertView = UIAlertView(title: reviewTitle, message: reviewMessage, delegate: self, cancelButtonTitle: cancelButtonTitle, otherButtonTitles: rateButtonTitle) + /* Otherwise we use UIAlertView still */ + var alertView: UIAlertView + if (showsRemindButton()) { + alertView = UIAlertView(title: reviewTitle, message: reviewMessage, delegate: self, cancelButtonTitle: cancelButtonTitle, otherButtonTitles: remindButtonTitle!, rateButtonTitle) + } else { + alertView = UIAlertView(title: reviewTitle, message: reviewMessage, delegate: self, cancelButtonTitle: cancelButtonTitle, otherButtonTitles: rateButtonTitle) + } + // If we have a remind button, show it first. Otherwise show the rate button + // If we have a remind button, show the rate button next. Otherwise stop adding buttons. + + alertView.cancelButtonIndex = -1 + ratingAlert = alertView + alertView.show() + + if let closure = didDisplayAlertClosure { + closure() + } } - // If we have a remind button, show it first. Otherwise show the rate button - // If we have a remind button, show the rate button next. Otherwise stop adding buttons. - - alertView.cancelButtonIndex = -1 - ratingAlert = alertView - alertView.show() - - if let closure = didDisplayAlertClosure { + + #elseif os(OSX) + + let alert: NSAlert = NSAlert() + alert.messageText = reviewTitle + alert.informativeText = reviewMessage + alert.addButtonWithTitle(rateButtonTitle) + if showsRemindButton() { + alert.addButtonWithTitle(remindButtonTitle!) + } + alert.addButtonWithTitle(cancelButtonTitle) + ratingAlert = alert + + if let window = NSApplication.sharedApplication().keyWindow { + alert.beginSheetModalForWindow(window) { + (response: NSModalResponse) in + self.handleNSAlertReturnCode(response) + } + } else { + let returnCode = alert.runModal() + handleNSAlertReturnCode(returnCode) + } + + if let closure = self.didDisplayAlertClosure { closure() } - } - -#elseif os(OSX) - - let alert: NSAlert = NSAlert() - alert.messageText = reviewTitle - alert.informativeText = reviewMessage - alert.addButtonWithTitle(rateButtonTitle) - if showsRemindButton() { - alert.addButtonWithTitle(remindButtonTitle!) - } - alert.addButtonWithTitle(cancelButtonTitle) - ratingAlert = alert - - if let window = NSApplication.sharedApplication().keyWindow { - alert.beginSheetModalForWindow(window) { - (response: NSModalResponse) in - self.handleNSAlertReturnCode(response) - } - } else { - let returnCode = alert.runModal() - handleNSAlertReturnCode(returnCode) - } - - if let closure = self.didDisplayAlertClosure { - closure() - } -#else -#endif + #else + #endif } - + // MARK: - // MARK: PRIVATE Alert View / StoreKit Delegate Methods - -#if os(iOS) - public func alertView(alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) { + + #if os(iOS) + open func alertView(_ alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) { // cancelButtonIndex is set to -1 to show the cancel button up top, but a tap on it ends up here with index 0 if (alertView.cancelButtonIndex == buttonIndex || 0 == buttonIndex) { // they don't want to rate it @@ -1261,58 +1265,58 @@ public class Manager : ArmchairManager { _rateApp() } } - + //Delegate call from the StoreKit view. - public func productViewControllerDidFinish(viewController: SKStoreProductViewController!) { + open func productViewControllerDidFinish(_ viewController: SKStoreProductViewController!) { closeModalPanel() } - + //Close the in-app rating (StoreKit) view and restore the previous status bar style. - private func closeModalPanel() { + fileprivate func closeModalPanel() { if modalPanelOpen { - UIApplication.sharedApplication().setStatusBarStyle(currentStatusBarStyle, animated:usesAnimation) + UIApplication.shared.setStatusBarStyle(currentStatusBarStyle, animated:usesAnimation) let usedAnimation = usesAnimation modalPanelOpen = false - + // get the top most controller (= the StoreKit Controller) and dismiss it - if let presentingController = UIApplication.sharedApplication().keyWindow?.rootViewController { + if let presentingController = UIApplication.shared.keyWindow?.rootViewController { if let topController = topMostViewController(presentingController) { - topController.dismissViewControllerAnimated(usesAnimation) { + topController.dismiss(animated: usesAnimation) { if let closure = self.didDismissModalViewClosure { closure(usedAnimation) } } - currentStatusBarStyle = UIStatusBarStyle.Default + currentStatusBarStyle = UIStatusBarStyle.default } } } } - -#elseif os(OSX) - + + #elseif os(OSX) + private func handleNSAlertReturnCode(returnCode: NSInteger) { - switch (returnCode) { - case NSAlertFirstButtonReturn: - // they want to rate it - _rateApp() - case NSAlertSecondButtonReturn: - // remind them later or cancel - if showsRemindButton() { - remindMeLater() - } else { - dontRate() - } - case NSAlertThirdButtonReturn: - // they don't want to rate it - dontRate() - default: - return - } + switch (returnCode) { + case NSAlertFirstButtonReturn: + // they want to rate it + _rateApp() + case NSAlertSecondButtonReturn: + // remind them later or cancel + if showsRemindButton() { + remindMeLater() + } else { + dontRate() } - -#else -#endif - + case NSAlertThirdButtonReturn: + // they don't want to rate it + dontRate() + default: + return + } + } + + #else + #endif + private func dontRate() { userDefaultsObject?.setBool(true, forKey: keyForArmchairKeyType(ArmchairKey.DeclinedToRate)) userDefaultsObject?.synchronize() @@ -1320,98 +1324,98 @@ public class Manager : ArmchairManager { closure() } } - + private func remindMeLater() { - userDefaultsObject?.setDouble(NSDate().timeIntervalSince1970, forKey: keyForArmchairKeyType(ArmchairKey.ReminderRequestDate)) + userDefaultsObject?.setDouble(Date().timeIntervalSince1970, forKey: keyForArmchairKeyType(ArmchairKey.ReminderRequestDate)) userDefaultsObject?.synchronize() if let closure = didOptToRemindLaterClosure { closure() } } - + private func _rateApp() { rateApp() if let closure = didOptToRateClosure { closure() } } - - private func rateApp() { - + + fileprivate func rateApp() { + userDefaultsObject?.setBool(true, forKey: keyForArmchairKeyType(ArmchairKey.RatedCurrentVersion)) userDefaultsObject?.setBool(true, forKey: keyForArmchairKeyType(ArmchairKey.RatedAnyVersion)) userDefaultsObject?.synchronize() - -#if os(iOS) - // Use the in-app StoreKit view if set, available (iOS 7+) and imported. This works in the simulator. - if opensInStoreKit { - - let storeViewController = SKStoreProductViewController() - - var productParameters: [String:AnyObject]! = [SKStoreProductParameterITunesItemIdentifier : appID] - - if (operatingSystemVersion >= 8) { - productParameters[SKStoreProductParameterAffiliateToken] = affiliateCode - productParameters[SKStoreProductParameterCampaignToken] = affiliateCampaignCode - } - - storeViewController.loadProductWithParameters(productParameters, completionBlock: nil) - storeViewController.delegate = self - - if let closure = willPresentModalViewClosure { - closure(usesAnimation) - } - - - if let rootController = getRootViewController() { - rootController.presentViewController(storeViewController, animated: usesAnimation) { - self.modalPanelOpen = true - - //Temporarily use a status bar to match the StoreKit view. - self.currentStatusBarStyle = UIApplication.sharedApplication().statusBarStyle - UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.Default, animated: self.usesAnimation) + + #if os(iOS) + // Use the in-app StoreKit view if set, available (iOS 7+) and imported. This works in the simulator. + if opensInStoreKit { + + let storeViewController = SKStoreProductViewController() + + var productParameters: [String:AnyObject]! = [SKStoreProductParameterITunesItemIdentifier : appID as AnyObject] + + if (operatingSystemVersion >= 8) { + productParameters[SKStoreProductParameterAffiliateToken] = affiliateCode as AnyObject? + productParameters[SKStoreProductParameterCampaignToken] = affiliateCampaignCode as AnyObject? + } + + storeViewController.loadProduct(withParameters: productParameters, completionBlock: nil) + storeViewController.delegate = self + + if let closure = willPresentModalViewClosure { + closure(usesAnimation) + } + + + if let rootController = getRootViewController() { + rootController.present(storeViewController, animated: usesAnimation) { + self.modalPanelOpen = true + + //Temporarily use a status bar to match the StoreKit view. + self.currentStatusBarStyle = UIApplication.shared.statusBarStyle + UIApplication.shared.setStatusBarStyle(UIStatusBarStyle.default, animated: self.usesAnimation) + } + } + + //Use the standard openUrl method + } else { + if let url = URL(string: reviewURLString()) { + UIApplication.shared.openURL(url) } } - - //Use the standard openUrl method - } else { + + if UIDevice.current.model.range(of: "Simulator") != nil { + debugLog("iTunes App Store is not supported on the iOS simulator.") + debugLog(" - We would have went to \(reviewURLString()).") + debugLog(" - Try running on a test-device") + let fakeURL = reviewURLString().replacingOccurrences(of: "itms-apps", with:"http") + debugLog(" - Or try copy/pasting \(fakeURL) into a browser on your computer.") + } + #elseif os(OSX) if let url = NSURL(string: reviewURLString()) { - UIApplication.sharedApplication().openURL(url) + let opened = NSWorkspace.sharedWorkspace().openURL(url) + if !opened { + debugLog("Failed to open \(url)") + } } - } - - if UIDevice.currentDevice().model.rangeOfString("Simulator") != nil { - debugLog("iTunes App Store is not supported on the iOS simulator.") - debugLog(" - We would have went to \(reviewURLString()).") - debugLog(" - Try running on a test-device") - let fakeURL = reviewURLString().stringByReplacingOccurrencesOfString("itms-apps", withString:"http") - debugLog(" - Or try copy/pasting \(fakeURL) into a browser on your computer.") - } -#elseif os(OSX) - if let url = NSURL(string: reviewURLString()) { - let opened = NSWorkspace.sharedWorkspace().openURL(url) - if !opened { - debugLog("Failed to open \(url)") - } - } -#else -#endif + #else + #endif } - - private func reviewURLString() -> String { + + fileprivate func reviewURLString() -> String { let template = reviewURLTemplate - var reviewURL = template.stringByReplacingOccurrencesOfString("APP_ID", withString: "\(appID)") - reviewURL = reviewURL.stringByReplacingOccurrencesOfString("AFFILIATE_CODE", withString: "\(affiliateCode)") - reviewURL = reviewURL.stringByReplacingOccurrencesOfString("AFFILIATE_CAMPAIGN_CODE", withString: "\(affiliateCampaignCode)") + var reviewURL = template.replacingOccurrences(of: "APP_ID", with: "\(appID)") + reviewURL = reviewURL.replacingOccurrences(of: "AFFILIATE_CODE", with: "\(affiliateCode)") + reviewURL = reviewURL.replacingOccurrences(of: "AFFILIATE_CAMPAIGN_CODE", with: "\(affiliateCampaignCode)") return reviewURL } - + // Mark: - // Mark: PRIVATE Key Helpers - + private func trackingInfo() -> ArmchairTrackingInfo { var trackingInfo: Dictionary = [:] - + for keyType in ArmchairKey.allValues { let obj: AnyObject? = userDefaultsObject?.objectForKey(keyForArmchairKeyType(keyType)) if let val = obj as? NSObject { @@ -1420,11 +1424,11 @@ public class Manager : ArmchairManager { trackingInfo[keyType] = NSNull() } } - + return ArmchairTrackingInfo(info: trackingInfo) } - - private func keyForArmchairKeyType(keyType: ArmchairKey) -> String { + + fileprivate func keyForArmchairKeyType(_ keyType: ArmchairKey) -> String { switch (keyType) { case .FirstUseDate: return keyPrefix + armchairKeyFirstUseDate @@ -1454,8 +1458,8 @@ public class Manager : ArmchairManager { return keyPrefix + armchairKeyUAAppReviewManagerMigrationCompleted } } - - private func setKey(key: NSString, armchairKeyType: ArmchairKey) { + + fileprivate func setKey(_ key: NSString, armchairKeyType: ArmchairKey) { switch armchairKeyType { case .FirstUseDate: armchairKeyFirstUseDate = key as String @@ -1485,8 +1489,8 @@ public class Manager : ArmchairManager { armchairKeyUAAppReviewManagerMigrationCompleted = key as String } } - - private func armchairKeyForAppiraterKey(appiraterKey: String) -> String { + + private func armchairKeyForAppiraterKey(_ appiraterKey: String) -> String { switch appiraterKey { case AppiraterKey.FirstUseDate: return keyForArmchairKeyType(ArmchairKey.FirstUseDate) @@ -1508,18 +1512,18 @@ public class Manager : ArmchairManager { return "" } } - + private func migrateAppiraterKeysIfNecessary() { - - let appiraterAlreadyCompletedKey: NSString = keyForArmchairKeyType(.AppiraterMigrationCompleted) + + let appiraterAlreadyCompletedKey: NSString = keyForArmchairKeyType(.AppiraterMigrationCompleted) as NSString let appiraterMigrationAlreadyCompleted = userDefaultsObject?.boolForKey(appiraterAlreadyCompletedKey as String) - + if let completed = appiraterMigrationAlreadyCompleted { if completed { return } } - + let oldKeys: [String] = [AppiraterKey.FirstUseDate, AppiraterKey.UseCount, AppiraterKey.SignificantEventCount, @@ -1536,34 +1540,34 @@ public class Manager : ArmchairManager { userDefaultsObject?.removeObjectForKey(oldKey) } } - - userDefaultsObject?.setObject(NSNumber(bool: true), forKey: appiraterAlreadyCompletedKey as String) + + userDefaultsObject?.setObject(NSNumber(value: true), forKey: appiraterAlreadyCompletedKey as String) userDefaultsObject?.synchronize() } - + // This only supports the default UAAppReviewManager keys. If you customized them, you will have to manually migrate your values over. private func migrateUAAppReviewManagerKeysIfNecessary() { - let appReviewManagerAlreadyCompletedKey: NSString = keyForArmchairKeyType(.UAAppReviewManagerMigrationCompleted) + let appReviewManagerAlreadyCompletedKey: NSString = keyForArmchairKeyType(.UAAppReviewManagerMigrationCompleted) as NSString let appReviewManagerMigrationAlreadyCompleted = userDefaultsObject?.boolForKey(appReviewManagerAlreadyCompletedKey as String) - + if let completed = appReviewManagerMigrationAlreadyCompleted { if completed { return } } - + // By default, UAAppReviewManager keys are in the format _UAAppReviewManagerKey let oldKeys: [String:ArmchairKey] = ["\(appID)_UAAppReviewManagerKeyFirstUseDate" : ArmchairKey.FirstUseDate, - "\(appID)_UAAppReviewManagerKeyUseCount" : ArmchairKey.UseCount, - "\(appID)_UAAppReviewManagerKeySignificantEventCount" : ArmchairKey.SignificantEventCount, - "\(appID)_UAAppReviewManagerKeyCurrentVersion" : ArmchairKey.CurrentVersion, - "\(appID)_UAAppReviewManagerKeyRatedCurrentVersion" : ArmchairKey.RatedCurrentVersion, - "\(appID)_UAAppReviewManagerKeyDeclinedToRate" : ArmchairKey.DeclinedToRate, - "\(appID)_UAAppReviewManagerKeyReminderRequestDate" : ArmchairKey.ReminderRequestDate, - "\(appID)_UAAppReviewManagerKeyPreviousVersion" : ArmchairKey.PreviousVersion, - "\(appID)_UAAppReviewManagerKeyPreviousVersionRated" : ArmchairKey.PreviousVersionRated, - "\(appID)_UAAppReviewManagerKeyPreviousVersionDeclinedToRate" : ArmchairKey.PreviousVersionDeclinedToRate, - "\(appID)_UAAppReviewManagerKeyRatedAnyVersion" : ArmchairKey.RatedAnyVersion] + "\(appID)_UAAppReviewManagerKeyUseCount" : ArmchairKey.UseCount, + "\(appID)_UAAppReviewManagerKeySignificantEventCount" : ArmchairKey.SignificantEventCount, + "\(appID)_UAAppReviewManagerKeyCurrentVersion" : ArmchairKey.CurrentVersion, + "\(appID)_UAAppReviewManagerKeyRatedCurrentVersion" : ArmchairKey.RatedCurrentVersion, + "\(appID)_UAAppReviewManagerKeyDeclinedToRate" : ArmchairKey.DeclinedToRate, + "\(appID)_UAAppReviewManagerKeyReminderRequestDate" : ArmchairKey.ReminderRequestDate, + "\(appID)_UAAppReviewManagerKeyPreviousVersion" : ArmchairKey.PreviousVersion, + "\(appID)_UAAppReviewManagerKeyPreviousVersionRated" : ArmchairKey.PreviousVersionRated, + "\(appID)_UAAppReviewManagerKeyPreviousVersionDeclinedToRate" : ArmchairKey.PreviousVersionDeclinedToRate, + "\(appID)_UAAppReviewManagerKeyRatedAnyVersion" : ArmchairKey.RatedAnyVersion] for (oldKey, newKeyType) in oldKeys { let oldValue: NSObject? = userDefaultsObject?.objectForKey(oldKey) as? NSObject if let val = oldValue { @@ -1571,62 +1575,64 @@ public class Manager : ArmchairManager { userDefaultsObject?.removeObjectForKey(oldKey) } } - - userDefaultsObject?.setObject(NSNumber(bool: true), forKey: appReviewManagerAlreadyCompletedKey as String) + + userDefaultsObject?.setObject(NSNumber(value: true), forKey: appReviewManagerAlreadyCompletedKey as String) userDefaultsObject?.synchronize() } - + private func migrateKeysIfNecessary() { migrateAppiraterKeysIfNecessary() migrateUAAppReviewManagerKeysIfNecessary() } - + // MARK: - // MARK: Internet Connectivity - + private func connectedToNetwork() -> Bool { var zeroAddress = sockaddr_in() - zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress)) + zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) zeroAddress.sin_family = sa_family_t(AF_INET) - - guard let defaultRouteReachability = withUnsafePointer(&zeroAddress, { - SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) + + guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { + SCNetworkReachabilityCreateWithAddress(nil, $0) + } }) else { return false } - + var flags : SCNetworkReachabilityFlags = [] if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) { return false } - - let isReachable = flags.contains(.Reachable) - let needsConnection = flags.contains(.ConnectionRequired) + + let isReachable = flags.contains(.reachable) + let needsConnection = flags.contains(.connectionRequired) return (isReachable && !needsConnection) } - + // MARK: - // MARK: PRIVATE Misc Helpers - - private func bundle() -> NSBundle? { - var bundle: NSBundle? = nil + + private func bundle() -> Bundle? { + var bundle: Bundle? = nil if useMainAppBundleForLocalizations { - bundle = NSBundle.mainBundle() + bundle = Bundle.main } else { - let armchairBundleURL: NSURL? = NSBundle.mainBundle().URLForResource("Armchair", withExtension: "bundle") + let armchairBundleURL: URL? = Bundle.main.url(forResource: "Armchair", withExtension: "bundle") if let url = armchairBundleURL { - bundle = NSBundle(URL: url) + bundle = Bundle(url: url) } else { - bundle = NSBundle(forClass: self.dynamicType) + bundle = Bundle(for: type(of: self)) } } - + return bundle } - -#if os(iOS) - private func topMostViewController(controller: UIViewController?) -> UIViewController? { + + #if os(iOS) + private func topMostViewController(_ controller: UIViewController?) -> UIViewController? { var isPresenting: Bool = false var topController: UIViewController? = controller repeat { @@ -1640,15 +1646,15 @@ public class Manager : ArmchairManager { } } } while isPresenting - + return topController } - + private func getRootViewController() -> UIViewController? { - if var window = UIApplication.sharedApplication().keyWindow { - + if var window = UIApplication.shared.keyWindow { + if window.windowLevel != UIWindowLevelNormal { - let windows: NSArray = UIApplication.sharedApplication().windows + let windows: NSArray = UIApplication.shared.windows as NSArray for candidateWindow in windows { if let candidateWindow = candidateWindow as? UIWindow { if candidateWindow.windowLevel == UIWindowLevelNormal { @@ -1658,73 +1664,73 @@ public class Manager : ArmchairManager { } } } - + for subView in window.subviews { - if let responder = subView.nextResponder() { - if responder.isKindOfClass(UIViewController) { + if let responder = subView.next { + if responder.isKind(of: UIViewController.self) { return topMostViewController(responder as? UIViewController) } - + } } } - + return nil } -#endif - + #endif + private func hideRatingAlert() { if let alert = ratingAlert { debugLog("Hiding Alert") -#if os(iOS) - if alert.visible { - alert.dismissWithClickedButtonIndex(alert.cancelButtonIndex, animated: false) - } -#elseif os(OSX) - if let window = NSApplication.sharedApplication().keyWindow { - if let parent = window.sheetParent { - parent.endSheet(window) + #if os(iOS) + if alert.isVisible { + alert.dismiss(withClickedButtonIndex: alert.cancelButtonIndex, animated: false) } - } - - #else -#endif + #elseif os(OSX) + if let window = NSApplication.sharedApplication().keyWindow { + if let parent = window.sheetParent { + parent.endSheet(window) + } + } + + #else + #endif ratingAlert = nil } } - - private func defaultAffiliateCode() -> String { + + fileprivate func defaultAffiliateCode() -> String { return "11l7j9" } - - private func defaultAffiliateCampaignCode() -> String { + + fileprivate func defaultAffiliateCampaignCode() -> String { return "Armchair" } - + // MARK: - // MARK: Notification Handlers - - public func appWillResignActive(notification: NSNotification) { + + public func appWillResignActive(_ notification: Notification) { debugLog("appWillResignActive:") hideRatingAlert() } - - public func applicationDidFinishLaunching(notification: NSNotification) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)) { + + public func applicationDidFinishLaunching(_ notification: Notification) { + DispatchQueue.global(qos: .background).async { self.debugLog("applicationDidFinishLaunching:") self.migrateKeysIfNecessary() self.incrementUseCount() } } - - public func applicationWillEnterForeground(notification: NSNotification) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)) { + + public func applicationWillEnterForeground(_ notification: Notification) { + DispatchQueue.global(qos: .background).async { self.debugLog("applicationWillEnterForeground:") self.migrateKeysIfNecessary() self.incrementUseCount() } } - + // MARK: - // MARK: Singleton public class var defaultManager: Manager { @@ -1734,49 +1740,49 @@ public class Manager : ArmchairManager { } return Singleton.instance } - + init(appID: String) { super.init() setupNotifications() } - + // MARK: Singleton Instance Setup - - private func setupNotifications() { -#if os(iOS) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "appWillResignActive:", name: UIApplicationWillResignActiveNotification, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationDidFinishLaunching:", name: UIApplicationDidFinishLaunchingNotification, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillEnterForeground:", name: UIApplicationWillEnterForegroundNotification, object: nil) -#elseif os(OSX) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "appWillResignActive:", name: NSApplicationWillResignActiveNotification, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationDidFinishLaunching:", name: NSApplicationDidFinishLaunchingNotification, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillEnterForeground:", name: NSApplicationWillBecomeActiveNotification, object: nil) -#else -#endif + + fileprivate func setupNotifications() { + #if os(iOS) + NotificationCenter.default.addObserver(self, selector: #selector(Manager.appWillResignActive(_:)), name: NSNotification.Name.UIApplicationWillResignActive, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(UIApplicationDelegate.applicationDidFinishLaunching(_:)), name: NSNotification.Name.UIApplicationDidFinishLaunching, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(UIApplicationDelegate.applicationWillEnterForeground(_:)), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) + #elseif os(OSX) + NSNotificationCenter.defaultCenter().addObserver(self, selector: "appWillResignActive:", name: NSApplicationWillResignActiveNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationDidFinishLaunching:", name: NSApplicationDidFinishLaunchingNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillEnterForeground:", name: NSApplicationWillBecomeActiveNotification, object: nil) + #else + #endif } - + // MARK: - // MARK: Printable - override public var debugDescription: String { + override open var debugDescription: String { get { return "Armchair: appID=\(Armchair.appID)" } } - + // MARK: - // MARK: Debug - - let lockQueue = dispatch_queue_create("com.armchair.lockqueue", nil) - + + let lockQueue = DispatchQueue(label: "com.armchair.lockqueue") + public var logger: ArmchairLogger = { manager, log, file, function, line in if manager.debugEnabled { - dispatch_sync(manager.lockQueue, { + manager.lockQueue.sync(execute: { print("[Armchair] \(log)") }) } } - private func debugLog(log: String, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__) { - logger(self, log: log, file: file, function: function, line: line) + fileprivate func debugLog(_ log: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) { + logger(self, log, file, function, line) } - + } From b5270a4892de87e89d444f37df47d0366b8fb55e Mon Sep 17 00:00:00 2001 From: phimage Date: Thu, 22 Sep 2016 15:18:24 +0200 Subject: [PATCH 26/31] fix swift 3 compile issue for mac os --- .swift-version | 1 + Armchair.xcodeproj/project.pbxproj | 2 ++ Source/Armchair.swift | 32 +++++++++++++++--------------- 3 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 .swift-version diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..9f55b2c --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +3.0 diff --git a/Armchair.xcodeproj/project.pbxproj b/Armchair.xcodeproj/project.pbxproj index fb1ae98..eb339f1 100644 --- a/Armchair.xcodeproj/project.pbxproj +++ b/Armchair.xcodeproj/project.pbxproj @@ -538,6 +538,7 @@ SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -562,6 +563,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/Source/Armchair.swift b/Source/Armchair.swift index 4b6d68c..a24ed92 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -724,7 +724,7 @@ public struct AppiraterKey { #if os(iOS) open class ArmchairManager : NSObject, UIAlertViewDelegate, SKStoreProductViewControllerDelegate { } #elseif os(OSX) - public class ArmchairManager : NSObject, NSAlertDelegate { } + open class ArmchairManager : NSObject, NSAlertDelegate { } #else // Untested, and currently unsupported #endif @@ -734,7 +734,7 @@ open class Manager : ArmchairManager { #if os(iOS) fileprivate var operatingSystemVersion = NSString(string: UIDevice.current.systemVersion).doubleValue #elseif os(OSX) - private var operatingSystemVersion = Double(NSProcessInfo.processInfo().operatingSystemVersion.majorVersion) + private var operatingSystemVersion = Double(ProcessInfo.processInfo.operatingSystemVersion.majorVersion) #else #endif @@ -1239,21 +1239,21 @@ open class Manager : ArmchairManager { let alert: NSAlert = NSAlert() alert.messageText = reviewTitle alert.informativeText = reviewMessage - alert.addButtonWithTitle(rateButtonTitle) + alert.addButton(withTitle: rateButtonTitle) if showsRemindButton() { - alert.addButtonWithTitle(remindButtonTitle!) + alert.addButton(withTitle: remindButtonTitle!) } - alert.addButtonWithTitle(cancelButtonTitle) + alert.addButton(withTitle: cancelButtonTitle) ratingAlert = alert - if let window = NSApplication.sharedApplication().keyWindow { - alert.beginSheetModalForWindow(window) { + if let window = NSApplication.shared().keyWindow { + alert.beginSheetModal(for: window) { (response: NSModalResponse) in - self.handleNSAlertReturnCode(response) + self.handleNSAlert(returnCode: response) } } else { let returnCode = alert.runModal() - handleNSAlertReturnCode(returnCode) + handleNSAlert(returnCode:returnCode) } if let closure = self.didDisplayAlertClosure { @@ -1309,7 +1309,7 @@ open class Manager : ArmchairManager { #elseif os(OSX) - private func handleNSAlertReturnCode(returnCode: NSInteger) { + private func handleNSAlert(returnCode: NSInteger) { switch (returnCode) { case NSAlertFirstButtonReturn: // they want to rate it @@ -1407,8 +1407,8 @@ open class Manager : ArmchairManager { debugLog(" - Or try copy/pasting \(fakeURL) into a browser on your computer.") } #elseif os(OSX) - if let url = NSURL(string: reviewURLString()) { - let opened = NSWorkspace.sharedWorkspace().openURL(url) + if let url = URL(string: reviewURLString()) { + let opened = NSWorkspace.shared().open(url) if !opened { debugLog("Failed to open \(url)") } @@ -1702,7 +1702,7 @@ open class Manager : ArmchairManager { alert.dismiss(withClickedButtonIndex: alert.cancelButtonIndex, animated: false) } #elseif os(OSX) - if let window = NSApplication.sharedApplication().keyWindow { + if let window = NSApplication.shared().keyWindow { if let parent = window.sheetParent { parent.endSheet(window) } @@ -1769,9 +1769,9 @@ open class Manager : ArmchairManager { NotificationCenter.default.addObserver(self, selector: #selector(UIApplicationDelegate.applicationDidFinishLaunching(_:)), name: NSNotification.Name.UIApplicationDidFinishLaunching, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(UIApplicationDelegate.applicationWillEnterForeground(_:)), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) #elseif os(OSX) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "appWillResignActive:", name: NSApplicationWillResignActiveNotification, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationDidFinishLaunching:", name: NSApplicationDidFinishLaunchingNotification, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillEnterForeground:", name: NSApplicationWillBecomeActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(Manager.appWillResignActive(_:)), name: NSNotification.Name.NSApplicationWillResignActive, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(NSApplicationDelegate.applicationDidFinishLaunching(_:)), name: NSNotification.Name.NSApplicationDidFinishLaunching, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(Manager.applicationWillEnterForeground(_:)), name: NSNotification.Name.NSApplicationWillBecomeActive, object: nil) #else #endif } From d57ed6802a061b1a219c310b9d175cc60cfba05b Mon Sep 17 00:00:00 2001 From: Oliver Ziegler Date: Sat, 15 Oct 2016 13:13:54 +0200 Subject: [PATCH 27/31] Update check for iOS simulator --- Source/Armchair.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index bce2f22..153140b 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -1395,14 +1395,16 @@ public class Manager : ArmchairManager { UIApplication.sharedApplication().openURL(url) } } - - if UIDevice.currentDevice().model.rangeOfString("Simulator") != nil { + + // Check for iOS simulator + #if arch(i386) || arch(x86_64) debugLog("iTunes App Store is not supported on the iOS simulator.") debugLog(" - We would have went to \(reviewURLString()).") debugLog(" - Try running on a test-device") let fakeURL = reviewURLString().stringByReplacingOccurrencesOfString("itms-apps", withString:"http") debugLog(" - Or try copy/pasting \(fakeURL) into a browser on your computer.") - } + #endif + #elseif os(OSX) if let url = NSURL(string: reviewURLString()) { let opened = NSWorkspace.sharedWorkspace().openURL(url) From bdcacffcb4e4b7a1e07e758cb05e30f224587a83 Mon Sep 17 00:00:00 2001 From: Oliver Ziegler Date: Sat, 15 Oct 2016 13:15:40 +0200 Subject: [PATCH 28/31] Update check for iOS Simulator --- Source/Armchair.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index a24ed92..f233be8 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -1399,13 +1399,15 @@ open class Manager : ArmchairManager { } } - if UIDevice.current.model.range(of: "Simulator") != nil { + // Check for iOS simulator + #if (arch(i386) || arch(x86_64)) && os(iOS) debugLog("iTunes App Store is not supported on the iOS simulator.") debugLog(" - We would have went to \(reviewURLString()).") debugLog(" - Try running on a test-device") let fakeURL = reviewURLString().replacingOccurrences(of: "itms-apps", with:"http") debugLog(" - Or try copy/pasting \(fakeURL) into a browser on your computer.") - } + #endif + #elseif os(OSX) if let url = URL(string: reviewURLString()) { let opened = NSWorkspace.shared().open(url) From 61f124d82ca15b339994466ca7bb1d48b5522c50 Mon Sep 17 00:00:00 2001 From: whitepixelstudios Date: Thu, 16 Feb 2017 13:03:15 +1000 Subject: [PATCH 29/31] Add custom dialog closure Added an optional closure to allow developers to present their own dialog once all criteria for the dialog are filled. Passes closures for 'Rate', 'Remind', and 'No thanks' --- Source/Armchair.swift | 161 +++++++++++++++++++++++------------------- 1 file changed, 87 insertions(+), 74 deletions(-) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index 7cb46e2..d1d5e91 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -356,6 +356,8 @@ public func resetDefaults() { Manager.defaultManager.didDisplayAlertClosure = nil Manager.defaultManager.didOptToRateClosure = nil Manager.defaultManager.didOptToRemindLaterClosure = nil + + Manager.defaultManager.customAlertClosure = nil #if os(iOS) Manager.defaultManager.usesAnimation = true @@ -562,6 +564,7 @@ public func rateApp() { */ public typealias ArmchairClosure = () -> () +public typealias ArmchairClosureCustomAlert = (_ rateAppClosure: ArmchairClosure, _ remindLaterClosure: ArmchairClosure, _ noThanksClosure: ArmchairClosure) -> () public typealias ArmchairAnimateClosure = (Bool) -> () public typealias ArmchairShouldPromptClosure = (ArmchairTrackingInfo) -> Bool public typealias ArmchairShouldIncrementClosure = () -> Bool @@ -920,6 +923,8 @@ open class Manager : ArmchairManager { var didOptToRateClosure: ArmchairClosure? var didOptToRemindLaterClosure: ArmchairClosure? + var customAlertClosure: ArmchairClosureCustomAlert? + #if os(iOS) var willPresentModalViewClosure: ArmchairAnimateClosure? var didDismissModalViewClosure: ArmchairAnimateClosure? @@ -1184,83 +1189,91 @@ open class Manager : ArmchairManager { } fileprivate func showRatingAlert() { - #if os(iOS) - if (operatingSystemVersion >= 8 && usesAlertController) || operatingSystemVersion >= 9 { - /* iOS 8 uses new UIAlertController API*/ - let alertView : UIAlertController = UIAlertController(title: reviewTitle, message: reviewMessage, preferredStyle: UIAlertControllerStyle.alert) - alertView.addAction(UIAlertAction(title: cancelButtonTitle, style:UIAlertActionStyle.cancel, handler: { - (alert: UIAlertAction!) in - self.dontRate() - })) - if (showsRemindButton()) { - alertView.addAction(UIAlertAction(title: remindButtonTitle!, style:UIAlertActionStyle.default, handler: { - (alert: UIAlertAction!) in - self.remindMeLater() - })) - } - alertView.addAction(UIAlertAction(title: rateButtonTitle, style:UIAlertActionStyle.default, handler: { - (alert: UIAlertAction!) in - self._rateApp() - })) - - // get the top most controller (= the StoreKit Controller) and dismiss it - if let presentingController = UIApplication.shared.keyWindow?.rootViewController { - if let topController = topMostViewController(presentingController) { - topController.present(alertView, animated: usesAnimation) { - print("presentViewController() completed") - } - } - // note that tint color has to be set after the controller is presented in order to take effect (last checked in iOS 9.3) - alertView.view.tintColor = tintColor - } - - } else { - /* Otherwise we use UIAlertView still */ - var alertView: UIAlertView - if (showsRemindButton()) { - alertView = UIAlertView(title: reviewTitle, message: reviewMessage, delegate: self, cancelButtonTitle: cancelButtonTitle, otherButtonTitles: remindButtonTitle!, rateButtonTitle) - } else { - alertView = UIAlertView(title: reviewTitle, message: reviewMessage, delegate: self, cancelButtonTitle: cancelButtonTitle, otherButtonTitles: rateButtonTitle) - } - // If we have a remind button, show it first. Otherwise show the rate button - // If we have a remind button, show the rate button next. Otherwise stop adding buttons. - - alertView.cancelButtonIndex = -1 - ratingAlert = alertView - alertView.show() - - if let closure = didDisplayAlertClosure { - closure() - } - } - - #elseif os(OSX) - - let alert: NSAlert = NSAlert() - alert.messageText = reviewTitle - alert.informativeText = reviewMessage - alert.addButton(withTitle: rateButtonTitle) - if showsRemindButton() { - alert.addButton(withTitle: remindButtonTitle!) - } - alert.addButton(withTitle: cancelButtonTitle) - ratingAlert = alert - - if let window = NSApplication.shared().keyWindow { - alert.beginSheetModal(for: window) { - (response: NSModalResponse) in - self.handleNSAlert(returnCode: response) - } - } else { - let returnCode = alert.runModal() - handleNSAlert(returnCode:returnCode) - } - + if let customClosure = customAlertClosure { + customClosure({[weak self] in self?._rateApp()}, {[weak self] in self?.remindMeLater()}, {[weak self] in self?.dontRate()}) if let closure = self.didDisplayAlertClosure { closure() } - #else - #endif + } else { + #if os(iOS) + if (operatingSystemVersion >= 8 && usesAlertController) || operatingSystemVersion >= 9 { + /* iOS 8 uses new UIAlertController API*/ + let alertView : UIAlertController = UIAlertController(title: reviewTitle, message: reviewMessage, preferredStyle: UIAlertControllerStyle.alert) + alertView.addAction(UIAlertAction(title: cancelButtonTitle, style:UIAlertActionStyle.cancel, handler: { + (alert: UIAlertAction!) in + self.dontRate() + })) + if (showsRemindButton()) { + alertView.addAction(UIAlertAction(title: remindButtonTitle!, style:UIAlertActionStyle.default, handler: { + (alert: UIAlertAction!) in + self.remindMeLater() + })) + } + alertView.addAction(UIAlertAction(title: rateButtonTitle, style:UIAlertActionStyle.default, handler: { + (alert: UIAlertAction!) in + self._rateApp() + })) + + // get the top most controller (= the StoreKit Controller) and dismiss it + if let presentingController = UIApplication.shared.keyWindow?.rootViewController { + if let topController = topMostViewController(presentingController) { + topController.present(alertView, animated: usesAnimation) { + print("presentViewController() completed") + } + } + // note that tint color has to be set after the controller is presented in order to take effect (last checked in iOS 9.3) + alertView.view.tintColor = tintColor + } + + } else { + /* Otherwise we use UIAlertView still */ + var alertView: UIAlertView + if (showsRemindButton()) { + alertView = UIAlertView(title: reviewTitle, message: reviewMessage, delegate: self, cancelButtonTitle: cancelButtonTitle, otherButtonTitles: remindButtonTitle!, rateButtonTitle) + } else { + alertView = UIAlertView(title: reviewTitle, message: reviewMessage, delegate: self, cancelButtonTitle: cancelButtonTitle, otherButtonTitles: rateButtonTitle) + } + // If we have a remind button, show it first. Otherwise show the rate button + // If we have a remind button, show the rate button next. Otherwise stop adding buttons. + + alertView.cancelButtonIndex = -1 + ratingAlert = alertView + alertView.show() + + if let closure = didDisplayAlertClosure { + closure() + } + } + + #elseif os(OSX) + + let alert: NSAlert = NSAlert() + alert.messageText = reviewTitle + alert.informativeText = reviewMessage + alert.addButton(withTitle: rateButtonTitle) + if showsRemindButton() { + alert.addButton(withTitle: remindButtonTitle!) + } + alert.addButton(withTitle: cancelButtonTitle) + ratingAlert = alert + + if let window = NSApplication.shared().keyWindow { + alert.beginSheetModal(for: window) { + (response: NSModalResponse) in + self.handleNSAlert(returnCode: response) + } + } else { + let returnCode = alert.runModal() + handleNSAlert(returnCode:returnCode) + } + + if let closure = self.didDisplayAlertClosure { + closure() + } + #else + #endif + } + } // MARK: - From b9b50559aab20ab1eaa0d9ed378cb3bf9892b5b8 Mon Sep 17 00:00:00 2001 From: whitepixelstudios Date: Thu, 16 Feb 2017 13:13:58 +1000 Subject: [PATCH 30/31] Add public setter for custom alert closure --- Source/Armchair.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Armchair.swift b/Source/Armchair.swift index d1d5e91..a3a22ac 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -572,6 +572,9 @@ public typealias ArmchairShouldIncrementClosure = () -> Bool public func onDidDisplayAlert(_ didDisplayAlertClosure: ArmchairClosure?) { Manager.defaultManager.didDisplayAlertClosure = didDisplayAlertClosure } +public func customAlertClosure(_ customAlertClosure: ArmchairClosureCustomAlert?) { + Manager.defaultManager.customAlertClosure = customAlertClosure +} public func onDidDeclineToRate(_ didDeclineToRateClosure: ArmchairClosure?) { Manager.defaultManager.didDeclineToRateClosure = didDeclineToRateClosure } From bf8c97e92bbe340c70db3375922d5df464cd91d9 Mon Sep 17 00:00:00 2001 From: whitepixelstudios Date: Fri, 17 Feb 2017 12:54:25 +1000 Subject: [PATCH 31/31] Update Readme, Fix custom alerts, update review URL Update version in podspec to 0.3.0 (Swift 3) Update readme to match podfile Make custom alert closure parameters escaping Review URL: auto-open to write review page --- Armchair.podspec | 4 +++- README.md | 17 ++++++++++++++++- Source/Armchair.swift | 4 ++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Armchair.podspec b/Armchair.podspec index d4fe553..19d7d0a 100644 --- a/Armchair.podspec +++ b/Armchair.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "Armchair" - s.version = "0.1.1" + s.version = "0.3.0" s.summary = "A simple yet powerful App Review Manager for iOS and OSX in Swift" s.description = <<-DESC A simple yet powerful App Review Manager for iOS and OSX in Swift. @@ -28,5 +28,7 @@ Pod::Spec.new do |s| s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.10' s.requires_arc = true + + end diff --git a/README.md b/README.md index fd5259f..abbf2a4 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,22 @@ platform :ios, '8.0' platform :osx, '10.10' use_frameworks! -pod 'Armchair', '>= 0.1' +pod 'Armchair', '>= 0.3' + +#Add the following in order to automatically set debug flags for armchair in debug builds +post_install do |installer| + installer.pods_project.targets.each do |target| + if target.name == 'Armchair' + target.build_configurations.each do |config| + if config.name == 'Debug' + config.build_settings['OTHER_SWIFT_FLAGS'] = '-DDebug' + else + config.build_settings['OTHER_SWIFT_FLAGS'] = '' + end + end + end + end + end ``` Then, run the following command: diff --git a/Source/Armchair.swift b/Source/Armchair.swift index a3a22ac..9e963be 100644 --- a/Source/Armchair.swift +++ b/Source/Armchair.swift @@ -564,7 +564,7 @@ public func rateApp() { */ public typealias ArmchairClosure = () -> () -public typealias ArmchairClosureCustomAlert = (_ rateAppClosure: ArmchairClosure, _ remindLaterClosure: ArmchairClosure, _ noThanksClosure: ArmchairClosure) -> () +public typealias ArmchairClosureCustomAlert = (_ rateAppClosure: @escaping ArmchairClosure, _ remindLaterClosure: @escaping ArmchairClosure, _ noThanksClosure: @escaping ArmchairClosure) -> () public typealias ArmchairAnimateClosure = (Bool) -> () public typealias ArmchairShouldPromptClosure = (ArmchairTrackingInfo) -> Bool public typealias ArmchairShouldIncrementClosure = () -> Bool @@ -749,7 +749,7 @@ open class Manager : ArmchairManager { #if os(iOS) fileprivate var ratingAlert: UIAlertView? = nil - fileprivate let reviewURLTemplate = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&id=APP_ID&at=AFFILIATE_CODE&ct=AFFILIATE_CAMPAIGN_CODE" + fileprivate let reviewURLTemplate = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&id=APP_ID&at=AFFILIATE_CODE&ct=AFFILIATE_CAMPAIGN_CODE&action=write-review" #elseif os(OSX) private var ratingAlert: NSAlert? = nil private let reviewURLTemplate = "macappstore://itunes.apple.com/us/app/idAPP_ID?ls=1&mt=12&at=AFFILIATE_CODE&ct=AFFILIATE_CAMPAIGN_CODE"