From 556b57cabeacd19ca13e561f049fcef25868f1b4 Mon Sep 17 00:00:00 2001 From: Miguel Olmedo Date: Mon, 12 Sep 2016 15:35:14 +0200 Subject: [PATCH 1/4] 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 2/4] 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 bdcacffcb4e4b7a1e07e758cb05e30f224587a83 Mon Sep 17 00:00:00 2001 From: Oliver Ziegler Date: Sat, 15 Oct 2016 13:15:40 +0200 Subject: [PATCH 3/4] 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 4/4] 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: -