Skip to content

Instantly share code, notes, and snippets.

@yosshi4486
Last active August 26, 2021 19:34
Show Gist options
  • Select an option

  • Save yosshi4486/2e7486c80281db1e86280e75ceafb5d0 to your computer and use it in GitHub Desktop.

Select an option

Save yosshi4486/2e7486c80281db1e86280e75ceafb5d0 to your computer and use it in GitHub Desktop.
Implementation example of Review Request in iOS.
//
// 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