Backwards compatibility of new NSSearchToolbarItem - macos

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.

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:
Create a new custom class, let's name it SmartSearchToolbarItem, and make it a subclass of NSToolbarItem.
In the storyboard, change the class of the Search Field from NSToolbarItem to SmartSearchToolbarItem.
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

Related

Xcode 14 Beta 5 throws an exception

Xcode 14 Beta 5 shows this exception:
[<_UINavigationBarContentViewLayout valueForUndefinedKey:]: this class is not key value coding-compliant for the key inlineTitleView.
I am getting this new exception in all my obj-c projects while using Xcode 14 Beta 5.
A few notes:
Exception appears on any simulator device (running iOS 16)
Exception appears right on the start inside int main(int argc, char * argv[])
No exception on real device running iOS 15
Exception can be ignored (no crash).
I wonder if anyone else encountered this.
This is a bug in Xcode 14. Other users have reported it here: https://developer.apple.com/forums/thread/712240
Initially, the issue was reported in Xcode 14 betas, but the bug was never fixed, and now, here we are. I reproduce the issue in the official release of Xcode 14.0.1.
Here are workarounds that I've tested that will work:
Use a physical device: The issue doesn't occur when testing on a physical iOS device. I tested on an iPhone 13 Pro running iOS 16.0 with Xcode 14.0.1. The error doesn't occur then/there.
Ignore Objective-C exceptions: When the issue occurs, an Objective-C exception is thrown. Those are usually critical errors, but this particular exception is bogus. By default, Xcode always pauses the debugger when your app throws an Objective-C exception, but you can tell Xcode to stop doing that. In Xcode, go to the View --> Navigators --> Breakpoints menu. There, you'll see any/all breakpoints you have set. But one of the breakpoints appears there by default: "All Objective-C exceptions"; it has a dark blue arrow next to it, indicating that, yes, Xcode will indeed pause the debugger on all Objective-C exceptions.
If you click on that blue arrow, it will turn light blue, disabling the breakpoint. From then on, Xcode will ignore this exception, but, unfortunately, it will ignore all exceptions, even real exceptions that you actually care about. This may be enough to get you unstuck, but we'd normally want Xcode to pause on thrown exceptions (because they're normally very serious).
Add a condition to the "All Objective-C Exceptions" breakpoint: Since it's not great to just completely disable this breakpoint, we can do something cleverer. Enable the breakpoint (make sure its arrow is dark blue), then right-click on it and "Edit Breakpoint..." There, you can paste in this condition: !(BOOL)[(id)[$arg1 reason] containsString:#"_UINavigationBarContentViewLayout"] That will cause the breakpoint to pause execution on all breakpoints except breakpoints that contain the string _UINavigationBarContentViewLayout. Pretty good, I think! But every developer on your team will have to do this on every machine they use for testing. (Credit goes to Grigory Entin's comment)
Monkey-patch UINavigationBarContentViewLayout: Objective-C is a dynamic language, allowing you to add methods to a class at
runtime. This is called monkey
patching. It's normally
unwise to do that, and it's normally especially unwise to do it to
Apple's code. Apple normally won't allow you to submit apps to the
store that monkey patch Apple's classes. But, as long as you're just
doing it on your machine, and just to workaround a bug in the Xcode
simulator, there's no harm in it, right?
The code sample here is based on a post from the Apple Developer Forum by moshiwu.
#import <objc/runtime.h>
#interface Xcode14Fixer : NSObject
#end
#implementation Xcode14Fixer
+ (void)load
{
Class cls = NSClassFromString(#"_UINavigationBarContentViewLayout");
SEL selector = #selector(valueForUndefinedKey:);
Method impMethod = class_getInstanceMethod([self class], selector);
if (impMethod) {
class_addMethod(cls, selector, method_getImplementation(impMethod), method_getTypeEncoding(impMethod));
}
}
- (id)valueForUndefinedKey:(NSString *)key
{
return nil;
}
#end
Put that code at the top of your AppDelegate.m file, and then, at the top of your - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { method, add this line: [Xcode14Fixer load];
moshiwu attempted to use #if DEBUG to ensure that the code would only be used in debug mode, but that didn't work for me. Instead, just promise yourself that you'll remember to remove this debug code before you ship an official build to Apple, or Apple will probably reject your build.

Xcode 7.1 missing class template methods on El Capitan

MacBook Pro (Retina, 15-inch, Mid 2015)
El Capitan 10.11.1
Xcode 7.1 (7B91b)
When I do File > New > File, Subclass of: UICollectionViewController, I get an implementation file containing only:
#import "CVCTest.h"
#implementation CVCTest
#end
So, there are none of the UICollectionViewDataSource and UICollectionViewDelegate method stubs I expect. This is true for new UITableViewController and UIViewController subclass files as well.
This seems to be a suddenly new phenomenon; I created a UICollectionViewController subclass in a project last week which came with the stubbed methods. Xcode seems to have lost the class file templates.
After deleting the Xcode app and the ~/Library/Developer/Xcode directory, rebooting, and installing Xcode 7.1 from Mac AppStore, the behavior is the same.
I checked with on colleague whose Xcode behaves this way and then with another whose Xcode behaves as expected.
Anyone else experiencing this? Any clue what might be causing this?
Ok. A critical piece of information I left out of my question is that I was subclassing a controller class for an iOS project. The "o.m.g" answer is that somehow
OS X source
was selected at the bottom of the left pane of the source type selection view, instead of
iOS source
With iOS source selected, I get the class template file with the expected stubbed methods.
wow.

NSPopover sample code does not detach window

I have not been able to get my NSPopover to detach to a window in my own projects, so to simplify I tried the Apple sample.
I downloaded a fresh copy of the Apple sample project: http://developer.apple.com/library/mac/samplecode/Popover/Introduction/Intro.html
It behaves the same, which is to say I can't drag the window to detach it either.
The project seems to provide all the correct windows and controllers and implements the detachableWindowForPopover: delegate method. However the method is never called.
Does anyone know the secret to detachable NSPopovers?
Found the answer while typing the question…
Mac OS X 10.10 Yosemite has a new delegate method:
(BOOL)popoverShouldDetach:(NSPopover *)popover
The default behavior on Yosemite is NO (should not detach). So delegates must implement this method in order for windows to be detachable. The sample project does not implement this method, so when compiled on Yosemite it will not detach (and also produces several deprecation warnings -- maybe I should have taken that hint that it needs an update).
Adding:
- (BOOL)popoverShouldDetach:(NSPopover *)popover {
return YES;
}
To MyWindowController.m fixes the problem.

How to solve changing location for Controls due to expansion in xcode 4.5 for UIView?

I have upgraded my xcode to 4.5 every thing is fine until I have added to my previous solution (which I had already developed it using xcode 4.3) new UIView which has a UIImageview control I have noticed that UIImage control location in xib differ than it's location when i run my app on ios5 devices
Any Idea how to solve this issue?
I'm not sure that i've understand your question correctly.
Try to hide device's statusbar.
You can do it in a current target settings or programmatically:
[UIApplication sharedApplication] setStatusBarHidden...

UIAlertView with text fields not moving up

I want to display a prompt to get a user's username and password.
I am doing this by displaying a UIAlertView and adding UITextFields as subviews to it.
Recently I moved my project over to XCode 4.2 and I updated all my project settings. My base SDK is set to iOS 5.0 and my deployment target is set to 4.0.
I then noticed that it was only being built for armv7 architectures and so I changed that to include armv6 as well since I would like to support the iPhone 3G as well.
After doing this I noticed that the UIAlertView wasn't moving up as before and now it was being covered by the keyboard when it was displayed. I read in the Apple documentation that you shouldn't subclass UIAlertViews (http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAlertView_Class/UIAlertView/UIAlertView.html) and so I changed the way I was doing things. This didn't solve the problem.
I have noticed that I can get it to work on my phone (an iPhone 4, so armv7 architecture) by setting the Build Setting - "Build active architecture only" to YES.
I suspect that the problem is something to do with trying to build for armv6 (and indeed removing this from the architectures I am trying to build for gives me alerts which move up correctly).
I suppose I should get to my question now... I'm struggling to understand why this is behaving the way it is. I read somewhere (can't find the link now) that you don't need to move the alert up manually when adding a text field to a UIAlertView in iOS 4 and above. Since I am building for at least iOS 4 shouldn't this work on both architectures?
Also, how can I get it to build for armv6 and still have alerts which move up correctly?
Edit: Maybe I was wrong but I have noticed that I have 2 instances of my phone which I can deploy the app to. Selecting the first one and building gives me an app where the UIAlertViews don't move up when they should, but selecting the second one makes it work properly. Would post a screenshot, but I'm a new user and so I don't have the permissions necessary yet...
Oki,
I've managed to come up with a solution which works and gets the UIAlertView to be in the correct position, but I'm still not entirely sure what the root cause of the problem is.
I solved it by responding to the UIAlertViewDelegate method willPresentAlertView: and doing the following
- (void) willPresentAlertView: (UIAlertView *) alertView {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
if (keyWindow.center.y - alertView.center.y < 0.001f) {
CGPoint center = [alertView center];
center.y = center.y - 108.0f;
[alertView setCenter:center];
}
}
It checks whether the UIAlertView is in the center of the screen and moves it up if it is (i.e. if the OS hasn't already calculated the correct position of it). Note: You would need to add a check for which specific orientation the device is in and then adjust the amount the alert is moved up accordingly if you wanted this to work for both portrait and landscape orientations.

Resources