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.
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.
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.
Build the app with Xcode 12.5.1 or later, wnhich appears to have fixed the compatibility with pre-10.14 systems.
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:
SmartSearchToolbarItem
, and make it a subclass of NSToolbarItem
.NSToolbarItem
to SmartSearchToolbarItem
.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.
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];
}
}
}
Github page
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With