如何禁用 MacCatalyst 上的全屏按钮
How to disabled fullscreen button on MacCatalyst
目前,我要停止调整大小 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()
但是 全屏 按钮仍然可以全屏显示。
有点复杂,但有可能。这是一种方法(我删除了所有目标宏以简化 post)。
结果:
代码:
// 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 部分是首选(使用未声明的选择器更简单)。通过 Xcode 模板添加新的 Objective-C class 并确认创建网桥。之后需要在生成的 *-Bridging-Header.h
中添加以下 class 头文件,一切都应该有效。
// 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
这是另一种不需要 Objective-C、选择器或异步调用的方法。它也不需要目标宏,iOS 将简单地跳过 if let NSApplication
。将它粘贴到首先出现的视图控制器中。请注意,这会禁用所有 windows 上的绿色全屏按钮。如果您想区分,请使用 Asperi 的 Swift 部分的想法。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
func bitSet(_ bits: [Int]) -> UInt {
return bits.reduce(0) { [=10=] | (1 << ) }
}
func property(_ property: String, object: NSObject, set: [Int], clear: [Int]) {
if let value = object.value(forKey: property) as? UInt {
object.setValue((value & ~bitSet(clear)) | bitSet(set), forKey: property)
}
}
// disable full-screen button
if let NSApplication = NSClassFromString("NSApplication") as? NSObject.Type,
let sharedApplication = NSApplication.value(forKeyPath: "sharedApplication") as? NSObject,
let windows = sharedApplication.value(forKeyPath: "windows") as? [NSObject]
{
for window in windows {
let resizable = 3
property("styleMask", object: window, set: [], clear: [resizable])
let fullScreenPrimary = 7
let fullScreenAuxiliary = 8
let fullScreenNone = 9
property("collectionBehavior", object: window, set: [fullScreenNone], clear: [fullScreenPrimary, fullScreenAuxiliary])
}
}
}
目前,我要停止调整大小 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()
但是 全屏 按钮仍然可以全屏显示。
有点复杂,但有可能。这是一种方法(我删除了所有目标宏以简化 post)。
结果:
代码:
// 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 部分是首选(使用未声明的选择器更简单)。通过 Xcode 模板添加新的 Objective-C class 并确认创建网桥。之后需要在生成的 *-Bridging-Header.h
中添加以下 class 头文件,一切都应该有效。
// 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
这是另一种不需要 Objective-C、选择器或异步调用的方法。它也不需要目标宏,iOS 将简单地跳过 if let NSApplication
。将它粘贴到首先出现的视图控制器中。请注意,这会禁用所有 windows 上的绿色全屏按钮。如果您想区分,请使用 Asperi 的 Swift 部分的想法。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
func bitSet(_ bits: [Int]) -> UInt {
return bits.reduce(0) { [=10=] | (1 << ) }
}
func property(_ property: String, object: NSObject, set: [Int], clear: [Int]) {
if let value = object.value(forKey: property) as? UInt {
object.setValue((value & ~bitSet(clear)) | bitSet(set), forKey: property)
}
}
// disable full-screen button
if let NSApplication = NSClassFromString("NSApplication") as? NSObject.Type,
let sharedApplication = NSApplication.value(forKeyPath: "sharedApplication") as? NSObject,
let windows = sharedApplication.value(forKeyPath: "windows") as? [NSObject]
{
for window in windows {
let resizable = 3
property("styleMask", object: window, set: [], clear: [resizable])
let fullScreenPrimary = 7
let fullScreenAuxiliary = 8
let fullScreenNone = 9
property("collectionBehavior", object: window, set: [fullScreenNone], clear: [fullScreenPrimary, fullScreenAuxiliary])
}
}
}