-
-
Save steipete/30c33740bf0ebc34a0da897cba52fefe to your computer and use it in GitHub Desktop.
// 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 |
Both objective-c and swift attempts return nil for me in catalyst. It gets the NSApplication just fine but the windows property is nil. Does this no longer work or is there a trick to get it working?
I've switched to a different approach a while back - see the updated code. This is also faster, however it does use private API. Be careful.
The macro does the folllowing:
#define PSPDF_SILENCE_CALL_TO_UNKNOWN_SELECTOR(expression) _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") expression _Pragma("clang diagnostic pop")
I've switched to a different approach a while back - see the updated code. This is also faster, however it does use private API. Be careful.
This works fantastic. Much appreciated!
Please note that the host window is unavailable yet in scene:willConnectToSession:options:
, so you need to dispatch_async(dispatch_get_main_queue(), ^{ });
.
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
}
@joelk Can you provide an example of the usage? It keeps returning an empty array