Mac Catalyst Menu Bar/Toolbar Actions Not Validating Properly After Changing Active Windows

I have a multiple window Mac Catalyst app. I'm using a NSToolbar and the menu bar via UIMenuBuilder.

I noticed after changing windows the menu bar isn't always validating properly. For example my app implements "Undo" and "redo". So I can reproduce the issue using these steps:

  1. Perform an action that can be undone.
  2. Open a new window. This new window has its own undo manager.
  3. In the Menu bar select Edit -> Undo
  4. Undo validates even though the current window has nothing on its local undo stack. If invoked undo is performed on the inactive window which definitely seems wrong.

The same thing sometimes happens in reverse (that is, undo doesn't validate when it should after switching windows). This also happens with other actions after switching windows.

Sometimes I can get the actions to validate by hitting the Tab key to move focus then shift tabbing back, which seems to force proper lookup in the responder chain (but sometimes that doesn't work).

It seems that Catalyst is losing track of the real active window/window scene for some reason and is validating actions on the wrong window scene.

Anyone experience this and know where I could be going wrong and/or know of a possible workaround?

I tried subclassing UIApplication and implementing the methods there (and then forwarding them to the active UIWindowScene). However this doesn't work, the wrong window scene has its activationState set to UISceneActivationStateForegroundActive when the problem occurs.

Replies

It appears in AppKit land the expected NSWindow is the main window when the described issue occurs but in the Mac Catalyst world the wrong UIWindowScene has the foreground active state which is good news because that means a workaround should be possible.

Another quick question: would it be considered intended behavior for two UIWindowScenes to have their activationState set to UISceneActivationStateForegroundActive at the same time?

Seems like there's definitely some bugs in there when switching windows/window scenes. I'm just trying debug something that I bet is related to this (UITextView loses its focus when you click on another window and then back to the original window again). I've also seen oddness when a window looks like it is active but menu items aren't enabled that should be.

You aren't using NSUIViewToolbarItem in your toolbars are you? Seems to cause all kinds of problems. It ends up creating its own scene to host the view and that was the cause of my UITextView focus issues. I filed a FB (FB12182334) and seems to be a known Catalyst issue.

  • Thanks a lot for this comment. I was struggling for a day with this issue until I saw your solution. After I removed a NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: barButtonItem) from the toolbar, all my windows and tabs responded to key commands as expected.

Add a Comment

In Catalyst, menu item validation is internally bridged from AppKit to UIKit, so it usually depends on two things:

  1. the AppKit first responder on the AppKit keyWindow (NSResponder chain validation)
  2. if that NSResponder represents UIKit content (i.e. a UIWindowScene), further validation in that scene (UIResponder chain validation, focus, etc.)

Normally, you shouldn't have to worry about any of this, since the AppKit first responder should nearly always represent an NSWindow's main UIWindowScene.

But in your case, it sounds like something is mis-directing the responder chain validation. It is possible that this is the same issue as FB12182334 (see above), or it could be something else. It would be greatly appreciated if you could isolate the issue in a small sample app, and file the issue (with sample app) through Feedback Assistant.

P.S. It is normal for multiple scenes to be Foreground+Active at the same time, since this primarily indicates that a scene can expect to receive user input, and this can be true for multiple windows at the same time. More information on this can be found here:

After returning to this Catalyst project after some time away from it this issue is still occurring in Xcode 15 Sonoma. Basically the responder chain and the focus system breaks after making another app the frontmost app (menu bar owning) and then navigating back my app (making my app frontmost, menubar owning, etc).

When I make my app frontmost again on Mac the focus system stops working. Tab key does nothing (it is expected to change focus) and many menu bar actions are not validating.

@rttCanada Yes. It does seem like this could be related to NSUIViewToolbarItem.

I have a NSUIViewToolbarItem subclass which wraps a UIButton subclass (in the system style). My subclass of UIButton just overrides canBecomeFocused and returns NO (because otherwise tabbing unexpectedly moved focus to this button in the toolbar which seemed wrong).

//Wrapped in a NSUIViewToolbarItem subclass 
@implementation SystemStyledButtonInToolbarDontFocusOnMePleaseItMakesNoSenseUseTheMenuBarOrKeyboardShortcutToInvokeThisActionIfYouWantToGoMouseFree

-(BOOL)canBecomeFocused
{
    return NO;
}

@end

My toolbar is customizable and if I remove this button from the toolbar it seems to fix the problem (though more testing is needed to say for sure).

All they would need to do to fix this is to use a subclass on UIWindow for the scene that is created by NSUIViewToolbarItem (_UIViewHostingScene which is a subclass of UIWindowScene) and return NO from -canBecomeKeyWindow and -canBecomeFocused.

Can confirm this by using Objective-C superpowers. Just swizzle -canBecomeKeyWindow and -canBecomeFocused and return NO for both these methods.

-(BOOL)jjj_canBecomeKeyWindow
{
    BOOL originalImp = [self jjj_canBecomeKeyWindow];
    
    if ([self.windowScene isKindOfClass:NSClassFromString(@"_UIViewHostingScene")])
    {
       //NEVER, EVER.
        return NO;
    }
    
    
    return originalImp;
}

-(BOOL)jjj_canBecomeFocused
{
    BOOL originalImp = [self jjj_canBecomeFocused];
    
    if ([self.windowScene isKindOfClass:NSClassFromString(@"_UIViewHostingScene")])
    {
        //don't EVER!
        return NO;
    }
    
    
    return originalImp;
}

And then the responder chain/focus system doesn't break when you switch back to your app window.

Nice fix. In my case I used some appkit code to create the toolbar item view instead of using UIView. It would be far nicer though if I could just use a subclass of UIView again. I just tested my sample app on Sonoma/Xcode 15 and it looks like they fixed the bug. Would be nice if there were actual release notes for Mac Catalyst code. Will experiment with your fix in my app. Thanks for posting it.

Would be nice if there were actual release notes for Mac Catalyst code.

+1 on that. It would also be nice to know if our bugs are being acknowledged with some communication, (if a human can tell us if the bug was reproduced on their end where they could confirm the bug and indicate that a fix is at least being worked on and is to be expected). Usually you submit a bug and all you get is crickets, no indication of whether or not a fix is a "priority" and can be expected in a reasonable timeframe. Every once in a while I get surprised but it's a very low percentage.

I just tested my sample app on Sonoma/Xcode 15 and it looks like they fixed the bug

I'm not sure if it's fixed entirely as I came back to this on Sonoma. But since it's triggered by the responder chain/focus system logic so it'll probably trigger differently from app to app depending on what's in the responder chain. Even if it is fixed we're gonna have to keep the workaround in place for another year for users who don't do minor updates.

I actually ended on the same workaround as you so we think alike. After I confirmed the source of the bug with the swizzled code I ended up adding AppKit code to build the toolbar item. Even though the swizzling code works, building the toolbar item in AppKit to workaround this seems way safer. Using NSStringFromClass to check for the existence of a private class can break at anytime (if they change the class name, etc). But the AppKit NSToolbarItem stuff Apple really can't change even if they wanted to because it'd break too many apps.