Last active
June 5, 2024 20:09
-
-
Save steipete/30c33740bf0ebc34a0da897cba52fefe to your computer and use it in GitHub Desktop.
Mac Catalyst: Get the NSWindow from a UIWindow (Updated for macOS 11 Big Sur, also works with Catalina)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Don't forget to prefix your category! | |
#import <UIKit/UIKit.h> | |
NS_ASSUME_NONNULL_BEGIN | |
@interface UIWindow (PSPDFAdditions) | |
#if TARGET_OS_UIKITFORMAC | |
/** | |
Finds the NSWindow hosting the UIWindow. | |
@note This is a hack. Iterates over all windows to find match. Might fail. | |
*/ | |
@property (nonatomic, readonly, nullable) id nsWindow; | |
#endif | |
@end | |
NS_ASSUME_NONNULL_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "UIWindow+PSPDFAdditions.h" | |
@implementation UIWindow (PSPDFAdditions) | |
#if TARGET_OS_UIKITFORMAC | |
- (nullable NSObject *)nsWindow { | |
id delegate = [[NSClassFromString(@"NSApplication") sharedApplication] delegate]; | |
const SEL hostWinSEL = NSSelectorFromString([NSString stringWithFormat:@"_%@Window%@Window:", @"host", @"ForUI"]); | |
@try { | |
// There's also hostWindowForUIWindow 🤷♂️ | |
PSPDF_SILENCE_CALL_TO_UNKNOWN_SELECTOR(id nsWindow = [delegate performSelector:hostWinSEL withObject:self];) | |
// macOS 11.0 changed this to return an UINSWindowProxy | |
let attachedWin = NSSelectorFromString([NSString stringWithFormat:@"%@%@", @"attached", @"Window"]); | |
if ([nsWindow respondsToSelector:attachedWin]) { | |
nsWindow = [nsWindow valueForKey:NSStringFromSelector(attachedWin)]; | |
} | |
return nsWindow; | |
} @catch (...) { | |
NSLog(@"Failed to get NSWindow for %@.", self); | |
} | |
return nil; | |
} | |
#endif | |
@end |
Works, but returns a UINSWindow type rather than a NSWindow. YOU cannot cast it as UIWIndow nor as an NSWindow. What do you do with it? Trying to change its size and position.
UINSWindow
is a subclass of NSWindow
. You can't cast NSWindow in an UIKit context, but you can call things on it. We use e.g. NSWindowDidBecomeKeyNotification
and then check for the window to update the selection color.
This is a cleaner code using Dynamic:
extension UIWindow {
var nsWindow: NSObject? {
Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(self)
}
}
And this is the first approach:
extension UIWindow {
var nsWindow: Any? {
let windows = Dynamic.NSApplication.sharedApplication.windows.asArray
return windows?.first { Dynamic($0).uiWindows.containsObject(self) == true }
}
}
This works great. just need to be sure to let the UI settle down with delays before you try to access the window. Using these to lock the aspect ratio in a MacCatalyst app:
delay(2) {
cover.removeFromSuperview()
#if targetEnvironment(macCatalyst)
let ns = window.nsWindow
let frame = ns?.value(forKey: "frame")
let size = (frame as! CGRect).size
ns!.setValue(CGSize(1.0, size.height/size.width), forKey: "aspectRatio")
#endif
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Please note that the host window is unavailable yet in
scene:willConnectToSession:options:
, so you need todispatch_async(dispatch_get_main_queue(), ^{ });
.