Last active
August 26, 2021 19:34
-
-
Save yosshi4486/2e7486c80281db1e86280e75ceafb5d0 to your computer and use it in GitHub Desktop.
Implementation example of Review Request in iOS.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // ReviewRequestManager.swift | |
| // | |
| // Created by yosshi4486 on 2021/07/22. | |
| // | |
| import StoreKit | |
| /// The global variable to get current app version. | |
| /// | |
| /// This variable should move under an apporopriate type if it is found. | |
| var currentAppVersion: String? { | |
| // Get the current app version. | |
| let infoDictionaryKey = "CFBundleShortVersionString" | |
| guard | |
| let currentVersion = Bundle.main.object(forInfoDictionaryKey: infoDictionaryKey) as? String | |
| else { | |
| fatalError("Expected to find a app version in the info dictionary") | |
| } | |
| return currentVersion | |
| } | |
| /// A manager of review request. | |
| /// | |
| /// *Review Request Manager* folllows best practice, which apple documentation describes at [Requesting App Store Reviews](https://developer.apple.com/documentation/storekit/requesting_app_store_reviews) | |
| /// and Human Interface Guideline describes at [Ratings and Reviews](https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/ratings-and-reviews/). | |
| class ReviewRequestManager { | |
| enum ReviewConditionStorageKey: String { | |
| case processCompletedCountKey | |
| case lastVersionPromptedForReviewKey | |
| } | |
| /// The local key value storage to store review related information. | |
| var userDefault: UserDefaults = .standard | |
| /// The integer value that counts how many times does user tap done button at navigation bar. | |
| var processCompletedCount: Int { | |
| get { | |
| // If the count has not yet been stored, this will return 0. | |
| return userDefault.integer(forKey: ReviewConditionStorageKey.processCompletedCountKey.rawValue) | |
| } | |
| set { | |
| userDefault.set(newValue, forKey: ReviewConditionStorageKey.processCompletedCountKey.rawValue) | |
| } | |
| } | |
| /// The string value that represents app version, which this app showed prompt at last. | |
| var lastVersionPromptedForReview: String? { | |
| get { | |
| // If the version has not yet been stored, this will return nil. | |
| return userDefault.string(forKey: ReviewConditionStorageKey.lastVersionPromptedForReviewKey.rawValue) | |
| } | |
| set { | |
| userDefault.set(newValue, forKey: ReviewConditionStorageKey.lastVersionPromptedForReviewKey.rawValue) | |
| } | |
| } | |
| /// The current version of this app. | |
| var currentAppVersion: String? | |
| /// The threthold value to determine whether this app can request review to ther user. | |
| var thretholdCountForReviewRequest: Int = 10 | |
| /// The waittime value to determine whether the user is continuing to perform tasks. | |
| var waitTimeForReviewRequest: Double = 3.0 | |
| /// The bool value whether this app can request app review to the user. | |
| var canRequestReview: Bool { | |
| print("Process completed \(processCompletedCount) time(s)") | |
| print("Current app version: \(currentAppVersion ?? "nil")") | |
| return processCompletedCount >= thretholdCountForReviewRequest && currentAppVersion != lastVersionPromptedForReview | |
| } | |
| /// The work item, which stores request of app store review. | |
| /// | |
| /// The work item is used to protect multiple request at same time. | |
| var requestReviewWorkItem: DispatchWorkItem? = nil | |
| init(userDefaults: UserDefaults = .standard, currentAppVersion: String?, thretholdCountForReviewRequest: Int = 10, waitTimeForReviewRequest: Double = 3.0) { | |
| self.userDefault = userDefaults | |
| self.currentAppVersion = currentAppVersion | |
| self.thretholdCountForReviewRequest = thretholdCountForReviewRequest | |
| self.waitTimeForReviewRequest = waitTimeForReviewRequest | |
| } | |
| /// Increments the count of process completed, which means adding 1 to its value. | |
| func incrementProcessCompletedCount() { | |
| processCompletedCount += 1 | |
| } | |
| /// Requests review for this app if the provided condition returns true. | |
| /// | |
| /// You must use this method after checking `canRequestReview` and it returns true like bellow: | |
| /// | |
| /// if requestReviewManager.canRequestReview { | |
| /// requestReviewManager.requestReview(in: view.window!.windowScene!) { | |
| /// // Do something | |
| /// return true | |
| /// } | |
| /// } | |
| /// | |
| /// - Parameters: | |
| /// - windowScene: The window scene to present the review prompt. | |
| /// - conditionAfterWait: The condition which is asked after some wait time. | |
| func requestReview(in windowScene: UIWindowScene, conditionAfterWait condition: @escaping () -> Bool) { | |
| // Cancel previous request. | |
| requestReviewWorkItem?.cancel() | |
| // Make new request work. | |
| requestReviewWorkItem = DispatchWorkItem(block: { [weak self] in | |
| if condition() { | |
| SKStoreReviewController.requestReview(in: windowScene) | |
| self?.lastVersionPromptedForReview = self?.currentAppVersion | |
| } | |
| }) | |
| // Scedule the request. | |
| DispatchQueue.main.asyncAfter(deadline: .now() + waitTimeForReviewRequest, execute: requestReviewWorkItem!) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment