Article summary
The best way to increase your iOS App Store rating is to get more people to rate your app. It is a fact of human nature that the only people who go out of their way to write a review or a rating are the ones who are not pleased with your app. If you could make it easier to leave a review, you will get a rating that is more indicative of how the majority of your users feel about your app.
Fortunately, there’s a quick way to prompt your users for a review using the SKStoreReviewController
class.
A Real-Life Example
I had an app in the App Store that did not have a great rating. I think it had 1.5 out of 5 stars, and only a handful of people had bothered to rate the app.
My customer was happy with the app. The users that they interviewed loved the app. We added a feature where users could provide feedback directly to our customer to avoid getting bad feedback in the App Store. And finally, we responded to every bad review with the proper thoughtful response to try and solve the problem.
Still, over the years, the rating remained the same. When iOS 10.3 came out, Apple introduced a way for apps to prompt the user for a rating within the app. After some discussion, our customer agreed, so we added the rating dialog to their app.
What a huge difference! The app suddenly received hundreds of ratings and a 4.7 out of 5 stars score. It’s now ranked among the top 100 in its category.
A Simple API
Asking the user for a review takes only one line of code. Just call requestReview()
on the SKStoreReviewController
class. Apple will limit how often the prompt will show to three times in a 365-day period.
SKStoreReviewController.requestReview()
The Right Time To Ask
Apple will do all the work required to show the dialog and interact with the App Store review process. But doing it the right way takes a little bit more thought (and code). For example:
- Ask for a review after the user has completed a task successfully or has interacted with your app in a positive way. You don’t want to prompt the user immediately after the app launches or when they have encountered an error.
- Ask for a review after your user has used your app for some time. A first-time user might not have enough experience to give you a review.
- Figure out how frequently you want to prompt for a rating. You are limited to three times in a 365-day period, but you may want to prompt the user less often as to not annoy them.
- Don’t prompt the user twice on the same version of the app.
- Make sure the user has remained on the current screen for a few seconds. You don’t want to show the prompt when the user is actively navigating around your app.
I can’t help you with the first item in the list above. It’s up to you to find a sensible place in your app to call this API.
But I can help with the other items in the list above. I’ve created a nice helper class that uses items stored in the UserDefaults
preferences to help with the list above. If you want to see the complete class, scroll down to the end of this blog post.
Targeting experienced users
One way to figure out if the user has experience with your app is to increment a counter every time they successfully complete a task. After some threshold of tasks, you know the user has enough experience with your app.
My threshold is stored in the minimumInitialDelayCount
variable. I increment a counter until I hit my threshold. (Otherwise the counter would keep increasing forever.)
In the following code, I have a computed property that returns the current count and a function that increments the count and stores it back into the UserDefaults
.
private var initialDelayCount: Int {
get {
return UserDefaults.standard.integer(forKey: UserDefaultsKeys.storeReviewInitialDelayCountKey.rawValue)
}
}
private func incrementInitialDelayCount() {
var count = initialDelayCount
if count < minimumInitialDelayCount {
count += 1
UserDefaults.standard.set(count, forKey: UserDefaultsKeys.storeReviewInitialDelayCountKey.rawValue)
}
}
Asking at the right frequency
I store the date when I last requested the review prompt to show. The following computed property gets the date from the UserDefaults
. This code makes use of a date extension I wrote called daysAgo
. (You can learn more about it in Swift Tool Belt, Part 2: Extending Date.)
If the last date when I requested the review does not exist, I default to my threshold for the minimum number of days between prompts, which is minimumDaysSinceLastReview
plus one day.
private var lastDatePromptedUser: Date {
get {
return UserDefaults.standard.object(forKey: UserDefaultsKeys.lastDateReviewPromptedKey.rawValue) as? Date ?? Date().daysAgo(minimumDaysSinceLastReview + 1)
}
}
Asking about a new version
I don’t want a user to review the same version of the app twice. To keep track of this, the computed property below gets the stored version and a helper function that will build a string out of the version and build number of the app.
private var lastVersionPromptedForReview: String? {
get {
return UserDefaults.standard.string(forKey: UserDefaultsKeys.lastVersionPromptedForReviewKey.rawValue)
}
}
private func version() -> String {
let dictionary = Bundle.main.infoDictionary!
let version = dictionary["CFBundleShortVersionString"] as! String
let build = dictionary["CFBundleVersion"] as! String
return "\(version).\(build)"
}
Asking when the user is not navigating
I want to make sure that the user has been on the current screen for a few seconds before prompting a review. To do this, I store the top ViewController
from the NavigationController
and check whether the top item has changed after a few seconds.
Putting It All Together
I’ve created a function called askForReview
which I call when a user has completed a task successfully. It is up to the function to figure out if it is appropriate to call SKStoreReviewController.requestReview()
. If you use this code, make sure you set the thresholds to a value that makes sense for your app. (The daysAgo
date extension can be found here.)
import Foundation
import StoreKit
enum UserDefaultsKeys: String {
case userPushNotificationAlreadySeenKey, storeReviewInitialDelayCountKey, lastDateReviewPromptedKey, lastVersionPromptedForReviewKey
}
class StoreReviewManager {
private let minimumDaysSinceLastReview = 122
private let minimumInitialDelayCount = 10
func askForReview(navigationController: UINavigationController?) {
guard let navigationController = navigationController else { return }
if #available(iOS 10.3, *) {
let oldTopViewController = navigationController.topViewController
let currentVersion = version()
let count = initialDelayCount
incrementInitialDelayCount()
// Has the task/process been completed several times and the user has not already been prompted for this version?
if count >= minimumInitialDelayCount && currentVersion != lastVersionPromptedForReview && lastDatePromptedUser <= Date().daysAgo(minimumDaysSinceLastReview) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
if navigationController.topViewController == oldTopViewController {
SKStoreReviewController.requestReview()
UserDefaults.standard.set(currentVersion, forKey: UserDefaultsKeys.lastVersionPromptedForReviewKey.rawValue)
UserDefaults.standard.set(Date(), forKey:UserDefaultsKeys.lastDateReviewPromptedKey.rawValue)
}
}
}
}
}
private var lastDatePromptedUser: Date {
get {
return UserDefaults.standard.object(forKey: UserDefaultsKeys.lastDateReviewPromptedKey.rawValue) as? Date ?? Date().daysAgo(minimumDaysSinceLastReview + 1)
}
}
private var lastVersionPromptedForReview: String? {
get {
return UserDefaults.standard.string(forKey: UserDefaultsKeys.lastVersionPromptedForReviewKey.rawValue)
}
}
private func version() -> String {
let dictionary = Bundle.main.infoDictionary!
let version = dictionary["CFBundleShortVersionString"] as! String
let build = dictionary["CFBundleVersion"] as! String
return "\(version).\(build)"
}
private var initialDelayCount: Int {
get {
return UserDefaults.standard.integer(forKey: UserDefaultsKeys.storeReviewInitialDelayCountKey.rawValue)
}
}
private func incrementInitialDelayCount() {
var count = initialDelayCount
if count < minimumInitialDelayCount {
count += 1
UserDefaults.standard.set(count, forKey: UserDefaultsKeys.storeReviewInitialDelayCountKey.rawValue)
}
}
}
Do you have similar success stories with asking for ratings? If you found this code helpful, let me know in the comments below.