Blog

We write about software development and our products

Since upgrading to macOS Sonoma, we noticed app rating windows started getting presented in the background as inactive windows:

This is unfortunate because users may not see the alert until long after it’s presented, and at that point, it may appear out of context. This affects both reviews requested with SKStoreReviewController.requestReview() from AppKit and the newer @Environment(\.requestReview) action from SwiftUI. After digging deeper, we realized this is fallout from the new cooperative activation functionality in AppKit:

When someone uses your app and another app unexpectedly steals focus, the user experience can be compromised. Not only is it inconvenient for people to switch back to their original app, if they’re typing when the switch occurs, they might accidentally enter text into the wrong app, inadvertently disclosing sensitive information.

Cooperative activation addresses this problem by making app activation a request instead of a command. Instead of apps stealing focus, they now request focus from the system when they’re ready. This means apps should set expectations accordingly when requesting activation and not assume the system will grant them activation. The system dynamically determines whether to grant activation state based on the context. This lets you request focus for your app when needed, and hand off focus from one app to another.

The App Store rating / review windows are presented by a different process called storeuid. When they come up, AppKit stops that process from stealing focus, however in this case, that’s what we want. We were able to work around this by calling…

if #available(macOS 14.0, *) {
    NSApp.yieldActivation(toApplicationWithBundleIdentifier:
        "com.apple.storeuid")
}

… prior to calling SKStoreReviewController.requestReview(). This works even if storeuid is not running at the time of the call. While it’s unfortunate every Mac developer on macOS Sonoma now has to work around this issue, at least there is a workaround.