Search code examples
iosstorekitskstorereviewcontroller

Mechanism to detect display of iOS 10.3 app rating dialog?


TL;DR: Is there some way on iOS to detect the presence/display of the Storekit App Rating dialog added in iOS 10.3?

I've recently added support for the new app rating dialog to my apps using:

[SKStoreReviewController requestReview];

However, I'm aware there's some caveats of usage (as documented here), namely that the dialog may or may not be presented on calling the above function, the not cases including if the customer has already rated the app or the customer has dimissed the dialog 3 times.

I'm also aware that Apple doesn't expect presentation of the dialog to be directly invoked by a user action and therefore the presence of the dialog to be reported:

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.

But that doesn't stop the UX team putting these buttons in the graphic designs and asking "can we know if the dialog was shown"?

So, my question is, is there some other indirect way that the presentation of this dialog can be determined?

I've recently been doing some automated testing of both Android and iOS apps using Appium and using Xpaths to find the native UI elements, so just wondering if the same can be achieved from within the context of an iOS app.


Solution

  • Your question got me thinking, and it is easier than I would have thought.

    My first thought was to check UIWindow related things - a quick look at the documentation revealed, that there are UIWindow related notifications - great! I made a quick project, subscribed to all of them and presented the review controller. This popped up in the logs :

    method : windowDidBecomeVisibleNotification:  
    object -> <SKStoreReviewPresentationWindow: 0x7fe14bc03670; baseClass = UIApplicationRotationFollowingWindow; frame = (0 0; 414 736); opaque = NO; gestureRecognizers = <NSArray: 0x61800004de30>; layer = <UIWindowLayer: 0x61800003baa0>>
    

    So in order to detect if the review controller was shown, you'd need to subscribe to a notification and inspect it's object property to find out its class :

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(windowDidBecomeVisibleNotification:)
                                                     name:UIWindowDidBecomeVisibleNotification
                                                   object:nil];
    }
    
    - (void)windowDidBecomeVisibleNotification:(NSNotification *)notification {
        if ([notification.object isKindOfClass:NSClassFromString(@"SKStoreReviewPresentationWindow")]) {
            NSLog(@"the review request was shown!");
        }
    }
    

    Now bear in mind that SKStoreReviewPresentationWindow is not publicly accessible - so you can't simply write [SKStoreReviewPresentationWindow class], and tricking the system by using NSClassFromString is just that - tricking the system. Unfortunately the other most interesting notification, UIWindowDidResignKey, was not issued - I hoped that the main window would resign, but unfortunately not. Some further debugging also showed that the main window remains key and not hidden. You could of course try comparing the notification.object to [UIApplication sharedApplication].window, but there were also other windows being shown - UITextEffectsWindow and UIRemoteKeyboardWindow, especially when the alert was first shown, and both of them are also not public.

    I'd consider this solution a hack - it is prone to changes by Apple that will break it. But most importantly, this could be grounds for rejection during review, so use at your own risk. I tested this on iPhone 7+ Simulator, iOS 10.3, Xcode 8.3.2


    Now, since we now know that it is kinda possible to detect if the review controller was shown, a more interesting problem is how to detect that it was NOT shown. You'd need to introduce some timeout after which you'd do something because the alert was not shown. This can feel like your app hanged, so it would be a bad experience for your users. Also, I noticed that the review controller is not shown immediately, so it even makes more sense why Apple doesn't recommend showing it after pressing a button.