Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backwards compatibility of new NSSearchToolbarItem

With macOS 11 Apple has introduced a new NSToolbarItem called NSSearchToolbarItem that automatically resizes to accommodate typing when the focus switches to the toolbar item.

enter image description here enter image description here

Here Apple says this is backwards compatible with older versions of macOS: https://developer.apple.com/wwdc20/10104 (minute 11:50)

However launching my app with a NSSearchToolbarItem from interface builder on macOS 10.13 (High Sierra), crashes my app with the following Application Specific Information:

*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (NSSearchToolbarItem) for key (NS.objects); the class may be defined in source code or a library that is not linked' terminating with uncaught exception of type NSException

Launching it on 10.15 works fine. I haven't been able to test 10.14 yet.

Update 6 July 21 by Thomas Tempelmann

It turns out that this was a bug with older Xcode 12 versions and is now fixed in Xcode 12.5.1.

I had opened a bounty, because I had a seemingly related issue with the improper sizing of NSSegmentedControls inside the toolbar when running on High Sierra, but it turns out that this is a separate issue (which can be fixed by manually resetting the toolbar's minSize and maxSize to the control's frame.size).

Therefore, the solution is to use Xcode 12.5.1 or later.

like image 612
Daniel Avatar asked Nov 08 '20 15:11

Daniel


2 Answers

Adding the item in storyboard without any code works properly, I have just tested. So probably you have done something wrong in the code. Or it is fixed in the latest XCode.

What I have found thus far is that this works only on Catalina, even on Mojave it crashes. According to @ThomasTempelmann it is better in XCode 12.5.1, but I haven't tested that yet.

like image 170
Ivan Ičin Avatar answered Oct 01 '22 17:10

Ivan Ičin


There are two ways to solve this:

1. Use Xcode 12.5.1 or later

Build the app with Xcode 12.5.1 or later, wnhich appears to have fixed the compatibility with pre-10.14 systems.

2. Add the NSSearchToolbarItem in code

If you want to still be able to open the project with older Xcode versions (ie. Xcode 11 and earlier), you cannot place the new NSSearchToolbarItem into the storyboard, or older Xcode versions will refuse to open it.

In this case, you'd keep using the classic NSToolbarItem with an NSSearchField control inside it. The challenge is to replace it with an NSSearchToolbarItem when running macOS 11 or later.

I've tried several methods, such as explicitly removing the classic search toolbar item from the toolbar and then adding the new one, and implementing the delegate function to provide it. While that worked, it caused trouble when letting the user customize the toolbar: Then the dialog would keep showing the old search item along with the new one. The only way to resolve this was to access private functions (_setAllowedItems and _setDefaultItems), but I wasn't happy with that.

I finally found this fairly solution:

  1. Create a new custom class, let's name it SmartSearchToolbarItem, and make it a subclass of NSToolbarItem.
  2. In the storyboard, change the class of the Search Field from NSToolbarItem to SmartSearchToolbarItem.
  3. Add the following code to the SmartSearchToolbarItem implementation:
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101600
@interface NSSearchToolbarItem : NSObject
- (instancetype)initWithItemIdentifier:(NSToolbarItemIdentifier)itemIdentifier;
@end
#endif

@implementation SmartSearchToolbarItem

-(instancetype)initWithItemIdentifier:(NSToolbarItemIdentifier)itemIdentifier
{
    self = [super initWithItemIdentifier:itemIdentifier];   // this is necessary even if we won't use it, or we'll crash in Big Sur
    Class cls = NSClassFromString(@"NSSearchToolbarItem");
    if (cls) {
        self = (id) [[cls alloc] initWithItemIdentifier:itemIdentifier];
    }
    return self;
}

Not only will this automagically replace the classic search item with the new one in Big Sur and later, it will even – and that's the part I don't really understand – still work with connected IBActions and IBOutlets. So, there's no need to copy and properties in code.

Fixing Segmented Controls

And if you happen to have segmented controls in your toolbar, then you'll also need this code to adjust their sizes as placement, as they have different widths on Big Sur vs. earlier macOS systems (10.15 and 10.14 will be fine, but if you also support 10.13, you will need this definitely):

- (void)fixSegmentedToolbarItemWidths // call this from `viewWillAppear`
{
    if (@available(macOS 10.14, *)) {
        // no need to set the sizes here
    } else {
        BOOL didChange = NO;
        for (NSToolbarItem *item in self.view.window.toolbar.items) {
            NSControl *control = (NSControl*)item.view;
            if ([control isKindOfClass:NSSegmentedControl.class]) {
                [control sizeToFit];
                NSRect frame = control.frame;
                const int padding = 2;
                item.minSize = NSMakeSize(frame.size.width+padding, item.minSize.height);
                item.maxSize = item.minSize;
                didChange = YES;
            }
        }
        if (didChange) {
            [self.view.window.toolbar validateVisibleItems];
        }
    }
}

Sample code

Github page

like image 39
Thomas Tempelmann Avatar answered Oct 01 '22 18:10

Thomas Tempelmann