如何让fullSizeContentView样式的NSWindow匹配SwiftUI视图固有尺寸?
让带fullSizeContentView的NSWindow完全匹配SwiftUI视图固有尺寸的问题
我希望带有fullSizeContentView样式的NSWindow能够完全匹配具有固有内容尺寸的SwiftUI视图大小。之前看过一些方案,但那些都需要在顶层设置固定frame,我不想这么做,希望窗口大小完全贴合视图。
以下是可在Xcode 14.1中运行的Playground代码片段:
import AppKit import SwiftUI class MyWindow: NSWindow { override func setFrame(_ frameRect: NSRect, display flag: Bool) { print("\(Date().timeIntervalSince1970) setFrame called \(frameRect)") super.setFrame(frameRect, display: flag) } } let window = MyWindow() window.styleMask = [ .titled, .closable, .resizable, .fullSizeContentView ] window.toolbar = nil window.titlebarAppearsTransparent = true window.titleVisibility = .hidden window.isMovable = true window.isMovableByWindowBackground = true window.standardWindowButton(.closeButton)?.isHidden = false window.standardWindowButton(.miniaturizeButton)?.isHidden = true window.standardWindowButton(.zoomButton)?.isHidden = true print("\(Date().timeIntervalSince1970) Before content \(window.frame)") window.contentView = NSHostingView(rootView: ContentView()) print("\(Date().timeIntervalSince1970) After setting content \(window.frame)") window.makeKeyAndOrderFront(nil) print("\(Date().timeIntervalSince1970) After makeKeyAndOrderFront \(window.frame)") DispatchQueue.main.asyncAfter(deadline: .now() + 1) { print("\(Date().timeIntervalSince1970) After 1 second \(window.frame)") } struct ContentView: View { var body: some View { Text("Hello") .font(.system(size: 200)) .background(.blue) .fixedSize() .ignoresSafeArea() } }
当前运行后窗口底部会留有多余空间,窗口截图如下:
打印的frame变更信息:
1674086812.362426 setFrame called (100.0, 100.0, 100.0, 100.0) 1674086812.363435 Before content (100.0, 100.0, 100.0, 100.0) 1674086812.373186 setFrame called (100.0, -63.0, 431.0, 263.0) 1674086812.3741732 After setting content (100.0, -63.0, 431.0, 263.0) 1674086812.374618 setFrame called (100.0, 85.0, 431.0, 263.0) 1674086812.375651 After makeKeyAndOrderFront (100.0, 85.0, 431.0, 263.0) 1674086812.4359 setFrame called (100.0, 57.0, 431.0, 291.0) 1674086813.41998 After 1 second (198.0, 99.0, 431.0, 291.0)
问题原因
- fullSizeContentView的布局逻辑:启用
fullSizeContentView后,内容视图会延伸到标题栏下方,但系统仍会默认保留标题栏的高度计算,导致窗口整体高度被额外加上了标题栏的默认高度(28pt),这就是底部多余空间的来源。 - SwiftUI视图尺寸的延迟计算:SwiftUI视图的固有尺寸不是初始化时立即确定的,而是在渲染流程中异步计算完成。窗口显示后,视图尺寸最终确定,会触发NSWindow重新调整frame适配,这就是多次调用
setFrame的原因。 - ignoresSafeArea的局限性:
ignoresSafeArea()只影响SwiftUI内部布局,不会告知NSWindow忽略标题栏的额外空间计算,窗口仍按传统结构计算整体尺寸。
解决办法
方法1:手动调整窗口frame匹配视图尺寸
设置contentView后,手动获取视图固有尺寸并修正窗口frame:
// 设置完window.contentView后添加这段代码 if let hostingView = window.contentView as? NSHostingView<ContentView> { hostingView.sizeToFit() let contentSize = hostingView.frame.size // 减去标题栏默认高度28pt,抵消fullSizeContentView的额外高度 let windowSize = NSSize(width: contentSize.width, height: contentSize.height - 28) window.setFrame(window.frame.withSize(windowSize), display: true) }
方法2:通过NSWindowDelegate控制窗口尺寸
实现代理方法强制窗口尺寸匹配内容视图:
class WindowDelegate: NSObject, NSWindowDelegate { func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize { guard let hostingView = sender.contentView as? NSHostingView<ContentView> else { return frameSize } hostingView.sizeToFit() let contentSize = hostingView.frame.size return NSSize(width: contentSize.width, height: contentSize.height - 28) } } // 创建window后设置代理 let delegate = WindowDelegate() window.delegate = delegate
方法3:在完整App中使用SwiftUI原生WindowGroup
如果是开发完整macOS App,直接用SwiftUI的WindowGroup可自动适配:
@main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() .fixedSize() .background(.blue) } .windowStyle(.hiddenTitleBar) .windowResizability(.contentSize) } }
内容的提问来源于stack exchange,提问作者narduk
相关产品推荐
相关产品推荐

