SKStoreReviewController how to detect that user has turned off Rate This App (RTA) in settings or 3 times limit has reached?
Asked Answered
Y

4

20

Starting iOS 10.3, Apple is limiting the review prompt (Rate This App) to 3 times a year and it can be turned off in the user's settings.

Q: How do we detect that the 3 times limit has reached or if the user has turned off RTA so in the app I won't show a popup saying: "Did you like the app? If yes, can you write a review? [Yes/No]" because then, if the user taps Yes, nothing will show up.

There is really not much information here from the official documentation: https://developer.apple.com/reference/storekit/skstorereviewcontroller

Although you should call this method when it makes sense in the user experience flow of your app, the actual display of a rating/review request view is governed by App Store policy. Because this method may or may not present an alert, it's not appropriate to call it in response to a button tap or other user action.

Yoshieyoshiko answered 1/3, 2017 at 13:49 Comment(0)
C
13

Preamble

Asking users if they like the app might lead to your app being rejected. Here is an example: https://twitter.com/pietbrauer/status/791883047373246464

In case the link dies here is an excerpt of Apples response:

3.2.2 ... your app includes content and features that can manipulate the user reviews or chart rankings on the App Store. Specifically, your app filters user reviews and only directs users who intend to rate your app 4 - 5 stars to complete a rating on the App Store...

I personally believe that this can be a valid tactic if you genuinely try to resolve the users issue, and still give them an opportunity to leave a review afterwards, but the question remains if Apple will see it that way.

Possible solution

  1. Show popup asking the user if they enjoy/like/etc using the app.
  2. Try using [SKStoreReviewController requestReview] to get a review.
  3. Check if the number of windows has changed, indicating that a popup has been shown. The caveat here is that this is not 100% reliable since some other event can cause the number of windows to change.
  4. If the number of windows stays the same use deep linking to forward the user to the app store. The docs for SKStoreReviewController suggest using action=write-review as a query parameter to go directly to the reviews page.

Here is a simple implementation:

// make sure we the current iOS version supports in app reviews
if ([SKStoreReviewController class])
{
    NSUInteger windowCount = [UIApplication sharedApplication].windows.count;
    [SKStoreReviewController requestReview];

    // give the review controller some time to display the popup
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        if (windowCount < [UIApplication sharedApplication].windows.count)
        {
            // assume review popup showed instead of some other system alert
            // for example show "thank you"
        }
        else
        {
            // open app store to leave review
            NSURL *reviewUrl = [NSURL URLWithString:@"{your-app-url}?action=write-review"];
            [[UIApplication sharedApplication] openURL:reviewUrl];
        }
    });
}

Note: I have not submitted this code to the App Store, so this is only theoretical.

Colourable answered 23/5, 2017 at 14:20 Comment(14)
So if I understand your code correctly, you are trying to show the new Apple Review popup, but if it fails (because it has been shown 3 times already, or the user has turned it off), then you still show them the review page using the old method? Hm, I'm not sure Apple will accept this, it is what they are trying to stop.Yoshieyoshiko
@VanDuTran as I said I have not tried to submit this to the App Store, but SKStoreReviewController documentation states For presenting a write review form, a deep link is available to the App Store by appending the query params "action=write-review" to a product URL.Colourable
This says it's a "possible solution", have you actually tried this?Usn
@TimJohnsen yes it works (with some exceptions), but as I said at the bottom of my answer that I have not submitted it to the app store.Colourable
What are the exceptions?Usn
@TimJohnsen from the answer: ... this is not 100% reliable since some other event can cause the number of windows to change.. For example, another system popup.Colourable
action=write-review does not work since iOS 11. Yes that goes against Apple's own docs, but I guess they just haven't updated those docs yet.Marielamariele
@Marielamariele I just tested on an iPad with iOS 11 and it works. This is the link I used to test itunes.apple.com/us/app/netflix/…Colourable
How to check for multiple calls of this alert??Polarimeter
How hard would it be for the SDK to return a canPresentReview? Instead of failing silently.Axil
@Axil it's not failing silently, it will open the App Store review page if the popup is not shown.Colourable
have anyone actually tried this and app got approved by apple?Lemmy
Yes, yesterday. 3 apps updated and all approved.Zendah
I did not implement the else branch that takes the user to the App Store to leave a review.Zendah
M
8

Well, you could try to fire the request and see but as long as there's no callback as well as no other official way how to detect whether the rating alert has been displayed at the time you call the requesting method.

There is a way around however – one of the StoreKit classes can be swizzled so you can observe when the Rating dialog is opened.

UIWindow-inspecting ways mentioned around may be useful as well, but swizzling on a method call is probably more reliable.

You can also use some rating managers like AppRating available as a pod, which manage the stuff for you, but only on a naive level by counting the calls and remembering it.

Middlebrow answered 16/3, 2017 at 14:17 Comment(3)
So what's the way around? That view is definitely beyond UIWindows's subviews. It won't even appear in view hierarchy 3D debug view.Scevo
I've updated the original answer to cover the way we use at this moment. Enjoy!Middlebrow
@Middlebrow link is not working, can you edit with the right one for the swizzling solution?Ruebenrueda
T
4

I am using this solution in production code - so far no rejections from Apple:

NSUInteger windowCount = [UIApplication sharedApplication].windows.count;
// SKStoreReviewController only available for >= 10.3, if needed check for class existence
[SKStoreReviewController requestReview];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    BOOL shown = windowCount < [UIApplication sharedApplication].windows.count;
    if (shown) {
        //popup was shown
    }
};
Tobitobiah answered 29/11, 2018 at 19:10 Comment(4)
A completion block with if the user actually submitted a review or tapped not now would be very helpful indeed.Hospodar
-1: In the example they only track that requestView() was called—not if it will actually be shown.You could say the example is broken/misleading. " …this method may or may not present an alert … When you call this method while your app is still in development mode, a rating/review request view is always displayed so that you can test the user interface and experience. … this method has no effect when you call it in an app that you distribute using TestFlight."Starks
This works in our Apple approved production code, thank you. Note that [SKStoreReviewController requestReview] is deprecated per iOS 14 and [SKStoreReviewController requestReviewInScene:] should be used.Dimercaprol
This still works on iOS 16.7 on device. Note that it doesn't work in the Simulators with Xcode 14.3.1. Also note that [UIApplication sharedApplication].windows.count is deprecated since iOS 15.0, use self.view.window.windowScene.windows.count instead.Dimercaprol
A
4

Building on the previous answers to "sniff" the window count for changes, here's a version that's working in Swift 5.4, on iOS 10.3 through 14.4:

func currentWindowCount() -> Int { UIApplication.shared.windows.count }

let initialWindowCount = currentWindowCount()

if #available(iOS 14.0, *) {
    if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
        SKStoreReviewController.requestReview(in: scene)
    }
} else {
    SKStoreReviewController.requestReview()
}

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    let actuallyShowedPrompt = initialWindowCount < currentWindowCount()
    if actuallyShowedPrompt {
        // do stuff
    }
}
Albers answered 2/3, 2021 at 16:58 Comment(3)
actuallyShowedPrompt will true only first time, than every time actuallyShowedPrompt is false. any one facing same?Cuckoo
@Kaushik Makwana That is the expected behavior in sandbox (test) mode. In production, it may not show the prompt the first time your app attempts it. Details in the requestReview() docs.Albers
I've just stumbled on this 'behaviour' in the simulator myself, and it may be true on a device. It seems that the the windows added when the review request is shown are not subsequently removed so currentWindowCount / windowCount does not return to the value first seen eg 1 ie it might remain at 4. So actuallyShowedPrompt is false after the first time. Restarting the app will resolve this. Note that the delay needs to be long enough for the review alert to appear otherwise it could be missed and 'do stuff' never executed.Zendah

© 2022 - 2024 — McMap. All rights reserved.