swiftmacosmac-catalystnstouchbar

Allow user customization of Touch Bar in macCatalyst


In a catalyst Swift project a UIViewController subclass creates a TouchBar like this:

override func makeTouchBar() -> NSTouchBar? {
    let touchBar = NSTouchBar()
    touchBar.delegate = self
    touchBar.defaultItemIdentifiers = [<<some>>]
    touchBar.customizationAllowedItemIdentifiers = [<<more>>]
    touchBar.customizationIdentifier = "com.me.myapp.touchbar"
    return touchBar
}

The default items show up on the TouchBar, but there is no menu item to customize them. Reading the TouchBar documentation there are two options:

1: NSApplication

The Customization Menu Item A user invokes the customization UI for a particular NSTouchBar object, when it is visible in the Touch Bar, by choosing the bar customization menu item. To enable this menu item you must explicitly opt-in, which you can do in the following ways:

  • If you want the system to automatically name, place, validate, and activate this menu item in your app’s menus, set the isAutomaticCustomizeTouchBarMenuItemEnabled property of your app object (of type NSApplication) to true.
  • To explicitly place the customization menu item in one of your app’s menus, employ the toggleTouchBarCustomizationPalette(_:) method of your app object. When you do this, the system still names and validates the menu item, and hides it on systems that do not have a Touch Bar.

Trying to do either of those yields a 'NSApplication' is unavailable in Mac Catalyst error. So after some googling around I found this:

NSClassFromString("NSApplication")?.setValue(true, forKeyPath: "sharedApplication.automaticCustomizeTouchBarMenuItemEnabled")
print(NSClassFromString("NSApplication")?.value(forKeyPath: "sharedApplication.isAutomaticCustomizeTouchBarMenuItemEnabled"))

This compiles and executes fine. It even prints out Optional(1) where there was an Optional(0) before. But the menu item still is not there. I tried this at the point where I generate the TouchBar, where I generate the Menu, and in func application(_, didFinishLaunchingWithOptions), but to no avail.

I cannot get the second one to not give me an error.

2: TouchBar

Curiously the NSTouchBar has the same property, but running

NSTouchBar.isAutomaticCustomizeTouchBarMenuItemEnabled = true

at any point (which compiles, too) yields no difference.

Question

Is this a missing feature or is there a way to achieve it. And if so: how?

Edit

I am customizing the menu bar. Due to an error, setting the same shortcut for two options, causes the following output in Xcode:

2020-02-15 14:08:01.492510+0100 Myapp[71888:2189305] [MenuBuilder] Menu has duplicates --
<_UIImmutableKeyCommand: 0x60000374e700> -> Title: Toggle Center Column  Action: executeOptionForKeyCommand:  Input: 1 + (UIKeyModifierCommand)
<_UIImmutableKeyCommand: 0x60000374e640> -> Title: Toggle Left Column  Action: executeOptionForKeyCommand:  Input: 1 + (UIKeyModifierCommand)
Make a symbolic breakpoint at _UIMenuBuilderError to catch this in the debugger.
2020-02-15 14:08:01.492701+0100 Myapp[71888:2189305] *** Assertion failure in -[_UIMenuBuilder _insertMenu:intoParentMenu:newParentMenu:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore/UIKit-3901.4.905/UIMenu/UIMenuBuilder.m:456
2020-02-15 14:08:01.492950+0100 Myapp[71888:2189305] [General] inserted menu has duplicate submenu, command or key command, or a key command is missing input or action
2020-02-15 14:08:01.497025+0100 Myapp[71888:2189305] [General] (
    0   CoreFoundation                      0x00007fff2f3858ab __exceptionPreprocess + 250
    1   libobjc.A.dylib                     0x00007fff6563f805 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff2f3aed10 +[NSException raise:format:arguments:] + 88
    3   Foundation                          0x00007fff31aa7241 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
    4   UIKitCore                           0x00007fff6d31dded -[_UIMenuBuilder _insertMenu:intoParentMenu:newParentMenu:] + 1426
    5   UIKitCore                           0x00007fff6d31e9ef -[_UIMenuBuilder insertChildMenu:atStartOfMenuForIdentifier:] + 177
    6   Myapp                               0x000000010010a87b $s5Myapp11AppDelegateC9buildMenu4withySo13UIMenuBuilder_p_tF + 4987
    7   Myapp                               0x000000010010b150 $s5Myapp11AppDelegateC9buildMenu4withySo13UIMenuBuilder_p_tFTo + 64
    8   UIKitCore                           0x00007fff6d31c428 -[UIResponder _buildMenuFromChainWithBuilder:] + 115
    9   UIKitCore                           0x00007fff6d31c17f -[UIMenuSystem _newBuilderFromResponderChain:] + 72
    10  UIKitCore                           0x00007fff6d31c114 -[UIMenuSystem _automaticallyRebuildIfNeeded] + 88
    11  UIKitCore                           0x00007fff6d31c09e -[UIMenuSystem _rootMenu] + 27
    12  UIKitCore                           0x00007fff6d3144d9 -[_UIMenuBarController _rebuildRootCommandGroup] + 66
    13  Foundation                          0x00007fff3197ffab -[__NSObserver _doit:] + 296
    14  CoreFoundation                      0x00007fff2f2ff35f __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
    15  CoreFoundation                      0x00007fff2f2ff2f3 ___CFXRegistrationPost1_block_invoke + 63
    16  CoreFoundation                      0x00007fff2f2ff268 _CFXRegistrationPost1 + 372
    17  CoreFoundation                      0x00007fff2f2feebe ___CFXNotificationPost_block_invoke + 97
    18  CoreFoundation                      0x00007fff2f2ce7e2 -[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1575
    19  CoreFoundation                      0x00007fff2f2cdc82 _CFXNotificationPost + 1351
    20  Foundation                          0x00007fff319e6048 postQueueNotifications + 718
    21  CoreFoundation                      0x00007fff2f3090ee __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    22  CoreFoundation                      0x00007fff2f309014 __CFRunLoopDoObservers + 457
    23  CoreFoundation                      0x00007fff2f308832 __CFRunLoopRun + 1514
    24  CoreFoundation                      0x00007fff2f307bd3 CFRunLoopRunSpecific + 499
    25  HIToolbox                           0x00007fff2de5d65d RunCurrentEventLoopInMode + 292
    26  HIToolbox                           0x00007fff2de5d39d ReceiveNextEventCommon + 600
    27  HIToolbox                           0x00007fff2de5d127 _BlockUntilNextEventMatchingListInModeWithFilter + 64
    28  AppKit                              0x00007fff2c4cdba4 _DPSNextEvent + 990
    29  AppKit                              0x00007fff2c4cc380 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1352
    30  AppKit                              0x00007fff2c4be09e -[NSApplication run] + 658
    31  AppKit                              0x00007fff2c490465 NSApplicationMain + 777
    32  AppKit                              0x00007fff2c7b369c _NSApplicationMainWithInfoDictionary + 16
    33  UIKitMacHelper                      0x00007fff602de8f1 UINSApplicationMain + 322
    34  UIKitCore                           0x00007fff6d186273 UIApplicationMain + 2105
    35  Myapp                               0x000000010010b39b main + 75
    36  libdyld.dylib                       0x00007fff669ad7fd start + 1
)
2020-02-15 14:08:27.979587+0100 Myapp[71888:2189305] [Layout] Unable to simultaneously satisfy constraints:
(
    "<NSAutoresizingMaskLayoutConstraint:0x600002183ac0 h=-&- v=-&- _SC_RESULTS_TABLE.minY == 0   (active, names: _SC_RESULTS_TABLE:0x100dc3140, '|':SCTMenuView:0x100dca2e0 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x600002183f70 h=-&- v=-&- V:|-(202)-[_SC_RESULTS_TABLE]   (active, names: _SC_RESULTS_TABLE:0x100dc3140, '|':SCTMenuView:0x100dca2e0 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x60000217f200 h=--& v=--& SCTMenuView:0x100dca2e0.height == 59   (active)>"
)

Will attempt to recover by breaking constraint 
<NSAutoresizingMaskLayoutConstraint:0x600002183f70 h=-&- v=-&- V:|-(202)-[_SC_RESULTS_TABLE]   (active, names: _SC_RESULTS_TABLE:0x100dc3140, '|':SCTMenuView:0x100dca2e0 )>

Set the NSUserDefault NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints to YES to have -[NSWindow visualizeConstraints:] automatically called when this happens.  And/or, set a symbolic breakpoint on LAYOUT_CONSTRAINTS_NOT_SATISFIABLE to catch this in the debugger.
2020-02-15 14:08:29.504823+0100 Myapp[71888:2189305] [MenuBuilder] Menu has duplicates --
<_UIImmutableKeyCommand: 0x60000375e400> -> Title: Toggle Center Column  Action: executeOptionForKeyCommand:  Input: 1 + (UIKeyModifierCommand)
<_UIImmutableKeyCommand: 0x60000375e340> -> Title: Toggle Left Column  Action: executeOptionForKeyCommand:  Input: 1 + (UIKeyModifierCommand)
Make a symbolic breakpoint at _UIMenuBuilderError to catch this in the debugger.
2020-02-15 14:08:29.504936+0100 Myapp[71888:2189305] *** Assertion failure in -[_UIMenuBuilder _insertMenu:intoParentMenu:newParentMenu:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore/UIKit-3901.4.905/UIMenu/UIMenuBuilder.m:456
2020-02-15 14:08:29.506015+0100 Myapp[71888:2189305] [General] inserted menu has duplicate submenu, command or key command, or a key command is missing input or action
2020-02-15 14:08:29.514279+0100 Myapp[71888:2189305] [General] (
    0   CoreFoundation                      0x00007fff2f3858ab __exceptionPreprocess + 250
    1   libobjc.A.dylib                     0x00007fff6563f805 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff2f3aed10 +[NSException raise:format:arguments:] + 88
    3   Foundation                          0x00007fff31aa7241 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
    4   UIKitCore                           0x00007fff6d31dded -[_UIMenuBuilder _insertMenu:intoParentMenu:newParentMenu:] + 1426
    5   UIKitCore                           0x00007fff6d31e9ef -[_UIMenuBuilder insertChildMenu:atStartOfMenuForIdentifier:] + 177
    6   Myapp                               0x000000010010a87b $s5Myapp11AppDelegateC9buildMenu4withySo13UIMenuBuilder_p_tF + 4987
    7   Myapp                               0x000000010010b150 $s5Myapp11AppDelegateC9buildMenu4withySo13UIMenuBuilder_p_tFTo + 64
    8   UIKitCore                           0x00007fff6d31c428 -[UIResponder _buildMenuFromChainWithBuilder:] + 115
    9   UIKitCore                           0x00007fff6d31c17f -[UIMenuSystem _newBuilderFromResponderChain:] + 72
    10  UIKitCore                           0x00007fff6d31c114 -[UIMenuSystem _automaticallyRebuildIfNeeded] + 88
    11  UIKitCore                           0x00007fff6d3cf5aa -[UIMenuSystem _keyCommands] + 27
    12  UIKitCore                           0x00007fff6d3cf375 -[UIApplication _keyCommands] + 105
    13  UIKitCore                           0x00007fff6dd17069 -[UIResponder _keyCommandForEvent:target:] + 182
    14  UIKitCore                           0x00007fff6dd17405 -[UIResponder _keyCommandForEvent:target:] + 1106
    15  UIKitCore                           0x00007fff6dd17405 -[UIResponder _keyCommandForEvent:target:] + 1106
    16  UIKitCore                           0x00007fff6dd17405 -[UIResponder _keyCommandForEvent:target:] + 1106
    17  UIKitCore                           0x00007fff6dd17405 -[UIResponder _keyCommandForEvent:target:] + 1106
    18  UIKitCore                           0x00007fff6dd17405 -[UIResponder _keyCommandForEvent:target:] + 1106
    19  UIKitCore                           0x00007fff6e0a36ce -[UIApplication(iOSMacSupport) _physicalKeyEvent:unmodified:shiftModified:commandModified:modifierFlags:isDown:timestampMachAbs:] + 517
    20  UIKitCore                           0x00007fff6e0a3803 -[UIApplication(iOSMacSupport) _sendKeyEvent:unmodified:shiftModified:commandModified:modifierFlags:isDown:timestampMachAbs:] + 116
    21  UIKitCore                           0x00007fff6e0a1c38 __58-[UIApplication(iOSMacSupport) _initiateIOSMacConnections]_block_invoke_3.396 + 124
    22  UIKitMacHelper                      0x00007fff602f4e3c -[UINSInputView _sendKeyEvent:isDown:] + 452
    23  UIKitMacHelper                      0x00007fff602f4ebd -[UINSInputView keyUp:] + 47
    24  AppKit                              0x00007fff2c675b98 -[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 6512
    25  AppKit                              0x00007fff2c674005 -[NSWindow(NSEventRouting) sendEvent:] + 349
    26  AppKit                              0x00007fff2c672ccf -[NSApplication(NSEvent) sendEvent:] + 2739
    27  AppKit                              0x00007fff2c4be0cf -[NSApplication run] + 707
    28  AppKit                              0x00007fff2c490465 NSApplicationMain + 777
    29  AppKit                              0x00007fff2c7b369c _NSApplicationMainWithInfoDictionary + 16
    30  UIKitMacHelper                      0x00007fff602de8f1 UINSApplicationMain + 322
    31  UIKitCore                           0x00007fff6d186273 UIApplicationMain + 2105
    32  Myapp                               0x000000010010b39b main + 75
    33  libdyld.dylib                       0x00007fff669ad7fd start + 1
)
2020-02-15 14:08:30.338067+0100 Myapp[71888:2189305] *** Assertion failure in -[NSApplication _commonBeginModalSessionForWindow:relativeToWindow:modalDelegate:didEndSelector:contextInfo:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1894.30.142/AppKit.subproj/NSApplication.m:3694
2020-02-15 14:08:30.338318+0100 Myapp[71888:2189305] [General] Modal session requires modal window
2020-02-15 14:08:30.348833+0100 Myapp[71888:2189305] [General] (
    0   CoreFoundation                      0x00007fff2f3858ab __exceptionPreprocess + 250
    1   libobjc.A.dylib                     0x00007fff6563f805 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff2f3aed10 +[NSException raise:format:arguments:] + 88
    3   Foundation                          0x00007fff31aa7241 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
    4   AppKit                              0x00007fff2c7cf9d0 -[NSApplication _commonBeginModalSessionForWindow:relativeToWindow:modalDelegate:didEndSelector:contextInfo:] + 1583
    5   AppKit                              0x00007fff2c7cf39b -[NSApplication beginModalSessionForWindow:] + 37
    6   AppKit                              0x00007fff2c7cf2fc __35-[NSApplication runModalForWindow:]_block_invoke_2 + 39
    7   AppKit                              0x00007fff2c7cf2c2 __35-[NSApplication runModalForWindow:]_block_invoke + 70
    8   AppKit                              0x00007fff2c7ceb4c _NSTryRunModal + 100
    9   AppKit                              0x00007fff2c7cea31 -[NSApplication runModalForWindow:] + 128
    10  AppKit                              0x00007fff2cd21be6 __82-[NSTouchBarCustomizationController toggleCustomizationPalette:forceControlStrip:]_block_invoke_4 + 148
    11  AppKit                              0x00007fff2cd244c7 ___NSRunLoopTimerCreateWithHandler_block_invoke + 34
    12  CoreFoundation                      0x00007fff2f324804 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    13  CoreFoundation                      0x00007fff2f3243be __CFRunLoopDoTimer + 859
    14  CoreFoundation                      0x00007fff2f323e9e __CFRunLoopDoTimers + 317
    15  CoreFoundation                      0x00007fff2f308aed __CFRunLoopRun + 2213
    16  CoreFoundation                      0x00007fff2f307bd3 CFRunLoopRunSpecific + 499
    17  HIToolbox                           0x00007fff2de5d65d RunCurrentEventLoopInMode + 292
    18  HIToolbox                           0x00007fff2de5d2a9 ReceiveNextEventCommon + 356
    19  HIToolbox                           0x00007fff2de5d127 _BlockUntilNextEventMatchingListInModeWithFilter + 64
    20  AppKit                              0x00007fff2c4cdba4 _DPSNextEvent + 990
    21  AppKit                              0x00007fff2c4cc380 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1352
    22  AppKit                              0x00007fff2c4be09e -[NSApplication run] + 658
    23  AppKit                              0x00007fff2c490465 NSApplicationMain + 777
    24  AppKit                              0x00007fff2c7b369c _NSApplicationMainWithInfoDictionary + 16
    25  UIKitMacHelper                      0x00007fff602de8f1 UINSApplicationMain + 322
    26  UIKitCore                           0x00007fff6d186273 UIApplicationMain + 2105
    27  Myapp                               0x000000010010b39b main + 75
    28  libdyld.dylib                       0x00007fff669ad7fd start + 1
)
2020-02-15 14:08:31.815095+0100 Myapp[71888:2189305] [AXRuntimeCommon] Unknown client: Myapp

But then the customize TouchBar action shows up in the menu (all my custom ones are gone), the ❗MISSING LABEL❗ issue is easily fixed.

So I wondered whether it was my menu customization code causing the error, but uncommenting the entire override func buildMenu(with builder: UIMenuBuilder) in my AppDelegate does not cause the customization menu to appear – only the shortcut collision does.

image


Solution

  • In my Swift Catalyst app (macOS 10.15.3, Xcode 11.3.1), I am able to call

    NSTouchBar.isAutomaticCustomizeTouchBarMenuItemEnabled = true
    

    in my override func makeTouchBar() method, and the "Customize Touch Bar..." menu item appears in the "View" menu tab. For the missing labels, the following works:

    func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
            if identifier == yourIdentifier {
                let item = NSPickerTouchBarItem(...)
    
                item.customizationLabel = "View Segmented Control"
    
                return item
            }
            return nil
        }