Skip to content

Instantly share code, notes, and snippets.

@steipete
Last active June 5, 2024 20:09
Show Gist options
  • Save steipete/30c33740bf0ebc34a0da897cba52fefe to your computer and use it in GitHub Desktop.
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)
// 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
#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
@LeoNatan
Copy link

Please note that the host window is unavailable yet in scene:willConnectToSession:options:, so you need to dispatch_async(dispatch_get_main_queue(), ^{ });.

@polymerchm
Copy link

polymerchm commented Dec 3, 2019

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.

@steipete
Copy link
Author

steipete commented Dec 4, 2019

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.

@mhdhejazi
Copy link

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 }
    }
}

@polymerchm
Copy link

polymerchm commented May 23, 2020

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