About the callback of SKStoreReviewController.requestReview()
Asked Answered
T

2

6

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?

Tarmac answered 2/1, 2019 at 15:19 Comment(1)
not sure if this will help you #43745657Valance
M
2

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.

Mullein answered 15/1, 2019 at 22:10 Comment(6)
Probably because it's considered pretty hacky code, and that it could be cleaned a bit more, or that fact I didn't explain it very well.Mullein
That's fine. I don't think there exists unhacky code coz Apple documentation doesn't say anything about the call back. Thx for the effort!Tarmac
Yeah hope they can fix it in the futureMullein
Don't work on iOS 13 but in iOS 12 working, any idea for IOS 13?Rent
I will look into iOS 13 tonight or tomorrow. First thing that comes to mind is checking if the window description has changed (partialDescForStoreReviewWindow in step 3). Just set a break point and check when you open the review window that the description contains that string. If not you know what to do.Mullein
When dismiss SKStoreReviewController step 3 not calledRent
A
0

Sometimes iOS app is losing the responder chain, like in the above example of showing StoreKit prompt. What we can do is to detect such events in UIApplication.sendAction and reactivate the first responder chain via becomeFirstResponder. UIKit will reestablish the first responder chain and we can resend the same event.

class MyApplication: UIApplication {

    func reactivateResponderChainWhenFirstResponderEventWasNotHandled() {
        becomeFirstResponder()
    }

    override func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool {
        let wasHandled = super.sendAction(action, to: target, from: sender, for: event)
        if wasHandled == false, target == nil {
            reactivateResponderChainWhenFirstResponderEventWasNotHandled()
            return super.sendAction(action, to: target, from: sender, for: event)
        }
        return wasHandled
    }
}

This works for me on iOS 13 and does not require any private API access.

Alible answered 6/3, 2020 at 6:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.