Search code examples
swiftmacosswiftuimac-catalyst

How to disabled fullscreen button on MacCatalyst


UIWindow on macCatalyst with close, minimize and fullscreen buttons

Currently, I have this to stop resizing window:

#if targetEnvironment(macCatalyst)
windowScene.sizeRestrictions?.minimumSize = CGSize(width: 480, height: 900)
windowScene.sizeRestrictions?.maximumSize = CGSize(width: 480, height: 900)
#endif

let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()

but the fullscreen button makes it full screen anyway.


Solution

  • It's a bit complicated but possible. Here is an approach (I dropped all target macros to simplify post).

    Result:

    enter image description here

    Code:

    // on next event after UIWindow has made key it is possible to find NSWindow in runtime
    
        func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    
            ...
            window.makeKeyAndVisible()
    
            DispatchQueue.main.async { // < wait for NSWindow available
                SilentBridge.disableCloseButton(for: self.nsWindow(from: window))
            }
        }
    
    // added helper function to SceneDelegate to find NSWindow
    
        func nsWindow(from window: UIWindow) -> NSObject? {
            guard let nsWindows = NSClassFromString("NSApplication")?.value(forKeyPath: "sharedApplication.windows") as? [NSObject] else { return nil }
            for nsWindow in nsWindows {
                let uiWindows = nsWindow.value(forKeyPath: "uiWindows") as? [UIWindow] ?? []
                if uiWindows.contains(window) {
                    return nsWindow
                }
            }
            return nil
        }
    

    Objective-C part is preferred (it's just simpler to work with non-declared selectors). Add new Objective-C class via Xcode template and confirm creating bridge. Afterwards it is needed to add below class header file in generated *-Bridging-Header.h and all should work.

    // SilentBridge.h
    
    @import Foundation;
    
    @interface SilentBridge : NSObject
    + (void)disableCloseButtonFor:(NSObject  * _Nullable)window;
    @end
    
    // SilentBridge.m
    
    #import "SilentBridge.h"
    @import Foundation;
    
    // Forward declarations to allow direct calls in below method
    @interface NSObject(SilentBridge) 
    - (id)standardWindowButton:(NSInteger)value;
    - (void)setEnabled:(BOOL)flag;
    @end
    
    @implementation SilentBridge
    
    + (void)disableCloseButtonFor:(NSObject *)window {
        if ([window respondsToSelector:@selector(standardWindowButton:)]) {
            id closeButton = [window standardWindowButton:2];
            if ([closeButton respondsToSelector:@selector(setEnabled:)]) {
                [closeButton setEnabled:NO];
            }
        }
    }
    @end