Search code examples
swiftbecomefirstresponderskstorereviewcontroller

About the callback of SKStoreReviewController.requestReview()


If the review popup initiated from a view controller shows up, there isn't a way to switch the window focus back to the view controller when the popup is dismissed due to lack of callback function of SKStoreReviewController.requestReview().

I would like to make a call to becomeFirstResponder() when the review popup is dismissed. Any idea?

Is there a way to extend the SKStoreReviewController and add a callback somehow?


Solution

  • Warning this will probably break at some point.

    Step 1: add this code to your didFinishLaunchingWithOptions

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        let windowClass: AnyClass = UIWindow.self
    
        let originalSelector: Selector = #selector(setter: UIWindow.windowLevel)
        let swizzledSelector: Selector = #selector(UIWindow.setWindowLevel_startMonitor(_:))
    
        let originalMethod = class_getInstanceMethod(windowClass, originalSelector)
        let swizzledMethod = class_getInstanceMethod(windowClass, swizzledSelector)
    
        let didAddMethod = class_addMethod(windowClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
    
        if didAddMethod {
            class_replaceMethod(windowClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
        } else {
            method_exchangeImplementations(originalMethod!, swizzledMethod!)
        }
    
        return true
    }
    

    Step 2: add this class

    class MonitorObject: NSObject {
        weak var owner: UIWindow?
    
        init(_ owner: UIWindow?) {
            super.init()
            self.owner = owner
            NotificationCenter.default.post(name: UIWindow.didBecomeVisibleNotification, object: self)
        }
    
        deinit {
             NotificationCenter.default.post(name: UIWindow.didBecomeHiddenNotification, object: self)
        }
    }
    

    Step 3: Add this UIWindow extension

    private var monitorObjectKey = "monitorKey"
    private var partialDescForStoreReviewWindow = "SKStore"
    
    extension UIWindow {
        // MARK: - Method Swizzling
        @objc func setWindowLevel_startMonitor(_ level: Int) {
            setWindowLevel_startMonitor(level)
    
            if description.contains(partialDescForStoreReviewWindow) {
                 let monObj = MonitorObject(self)
                 objc_setAssociatedObject(self, &monitorObjectKey, monObj, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
    

    Step 4: add this to ViewDidLoad of your controller where you want this

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeHiddenNotification(_:)), name: UIWindow.didBecomeHiddenNotification, object: nil)
    }
    

    Step 5: add the callback for the notification and check that the associated object is a match

    @objc func windowDidBecomeHiddenNotification(_ notification: Notification?) {
        if notification?.object is MonitorObject {
            print("hello")
        }
    }
    

    Now when a review dialog is closed the notification is triggered and 'print("hello") will be called.