MacCatalyst Multi window App not calling Scene Delegate

I have a working Mac Catalyst app which opens several ViewControllers simultaneously, each in their own Scene. Once the scenes are opened and all foreground, I would like each ViewController to be notified when the user taps that window so its Scene becomes Active, i.e., topmost, first Responder and focus.

However, after the initial scene creation and activation, the Scene Delegate sceneDidBecomeActive method does not get triggered by any user interaction, despite the targeted window cycling between Active and Inactive.

Neither does UIKit post the "didBecomeActiveNotification" event.

Inside the ViewControllers, the "viewDidAppear" method does not get invoked either.

Is there a UISceneConfigurationOption required to handle an activate event?

How can I detect an Activate event on an existing scene/ViewController?

Post not yet marked as solved Up vote post of Aviametrix Down vote post of Aviametrix
1.4k views

Replies

On Mac Catalyst, windows do not become inactive when they lose key, and do not become active when they become key or frontmost. As such, you can’t use scene active/inactive callbacks to follow this state.

You might want to follow the key status of your UIWindows instead. Use UIWindow.didBecomeKeyNotification and .didResignKeyNotification to keep track of this state.

  • I have tried using the window event to detect change in focus: nc.addObserver(forName: .init("NSWindowDidBecomeMainNotification"), object: nil, queue: nil) { notification in print("This window became focused:", notification.object!) self.turnoffConstraintForTextSideStack() } This event posts on any of the open scenes changing focus, and my routine executes, changing the layout to Catalyst mode, then, in a few milliseconds, it changes itself back to the iOS configuration, so so redraw/relayout is occurring after my desired layout.

    Can I stop the reversion to iOS layout? or failing that, How can I promote/push my layout to be final, after viewWillAppear?

  • I appreciate your answer! I had actually tried your suggestion to use the window event but as per above, it triggered on any scene moving to front (actually not a terrible problem), and my constraints get manipulated, but then a few msec later, the layout gets redrawn and reverts to iOS. Any thoughts?

Add a Comment

You can observe the activeAppearance trait. Try this:

final class ViewController: UIViewController {
    @IBOutlet weak var statusLabel: UILabel!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        updateWindowStatusLabel()
    }

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        if previousTraitCollection == nil || previousTraitCollection!.activeAppearance != traitCollection.activeAppearance {
            updateWindowStatusLabel()
        }
    }

    private func updateWindowStatusLabel() {
        if traitCollection.activeAppearance == .active {
            statusLabel.text = "Appears active"
        } else {
            statusLabel.text = "Appears inactive"
        }
    }
}
  • Thanks Frame! I’ll give it a try in the AM.

  • Thank you!!!! This issue has been plaguing me for over a year! Utilizing traitCollection.activeAppearance solves the problem! https://stackoverflow.com/a/75440759/3765792

    or simply if you want to grab the active window UIApplication.shared.windows.filter { $0.isKeyWindow }.first { $0.traitCollection.activeAppearance == .active }

Add a Comment

I fixed the layout reversion issue by calling my method that mods the layout constraints from an override of viewDidLayoutSubbviews in each affected VC

For the moment, I am sticking with the NSWindowDidBecomeMainNotification event to trigger activation events in my viewControllers, despite the slight increase in overhead by observing the event in all open VC.