Search code examples
macoscocoamacos-mojaveskstorereviewcontroller

Detect if SKStoreReviewController has displayed app rating window on macOS


The way to ask for review

SKStoreReviewController.requestReview()

On iOS folks found pretty easy way how to find if rating window has been presented link (just by counting number of windows within the app)

How to reliably detect this on macOS? (Counting windows alternatives?)

Store


Solution

  • Found that CGWindowListCopyWindowInfo + NSRunningApplication are the right things to inspect:

    dispatch_time_t twoSecondsFromNow = DISPATCH_TIME_NOW + 2.0;
    dispatch_after(twoSecondsFromNow, dispatch_get_main_queue(), ^{
    
        //HERE REQUEST REVIEW
        [SKStoreReviewController requestReview];
    
        dispatch_time_t secondFromNow = DISPATCH_TIME_NOW + 1.0;
        dispatch_after(secondFromNow, dispatch_get_main_queue(), ^{
            CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
            NSArray <NSString*>*windowNames = [(__bridge NSArray *)windowList valueForKey:@"kCGWindowOwnerName"];
            CFRelease(windowList);
            if ([windowNames containsObject:@"storeuid"]) {
    
                //REPORT TO PREFERENCES THAT WE SUCCESSFULLY ASKED FOR REVIEW
                NSLog(@"Rating Window was presented");
            }
        });
    });
    

    Possible alternative (as of 10.14.2 with significant delays for deactivations) :

    - (void)findRunningApplication
    {
        NSArray<NSRunningApplication *> *runningApplications = [[NSWorkspace sharedWorkspace] runningApplications];
        for (NSRunningApplication *app in runningApplications) {
            if ([[app bundleIdentifier] isEqualToString:@"com.apple.storeuid"]) {
                [self setRunningApplication:app];
            }
        }
    }
    
    - (void)setRunningApplication:(NSRunningApplication *)runningApplication
    {
        if (runningApplication != _runningApplication) {
    
            if (runningApplication == nil) {
                [_runningApplication removeObserver:self forKeyPath:@"active"];
            } else {
                [runningApplication addObserver:self forKeyPath:@"active" options:NSKeyValueObservingOptionNew context:StoreInspectorKVOContext];
            }
            _runningApplication = runningApplication;
        }
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        if (context == StoreInspectorKVOContext) {
            if (object == [self runningApplication] && [keyPath isEqualToString:@"active"]) {
                [self runningApplicationActiveHasChanged];
            }
        } else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
    

    One can even get what rating has user clicked with global monitor event + position from cgwindow

    NSEvent *monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskLeftMouseDown)
                                                              handler:^(NSEvent *event) {
                                                                  NSLog(@"%@",event);
                                                              }];
    
    
    
    2019-01-13 23:58:12.727979+0100 testWindows2[11466:620853] NSEvent: type=LMouseDown loc=(994.746,172.551) time=103867.3 flags=0 win=0x0 winNum=4852 ctxt=0x0 evNum=9320 click=1 buttonNumber=0 pressure=1 deviceID:0x300000014400000 subtype=NSEventSubtypeTouch
    2019-01-13 23:58:12.729521+0100 testWindows2[11466:620853] {
        kCGWindowAlpha = 1;
        kCGWindowBounds =     {
            Height = 157;
            Width = 420;
            X = 786;
            Y = 23;
        };
        kCGWindowIsOnscreen = 1;
        kCGWindowLayer = 0;
        kCGWindowMemoryUsage = 1248;
        kCGWindowNumber = 4590;
        kCGWindowOwnerName = storeuid;
        kCGWindowOwnerPID = 7017;
        kCGWindowSharingState = 1;
        kCGWindowStoreType = 1;
    }