Increase Your iOS App Ratings with the askForReview Function

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.

app store review dialog

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:

  1. 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.
  2. 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.
  3. 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.
  4. Don’t prompt the user twice on the same version of the app.
  5. 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.