SwiftUI 3.0 macOS应用如何正确实现窗口缩放功能
实现方案
你之前用GeometryReader拿不到正确尺寸是因为GeometryReader默认会撑满父容器,拿到的是父视图尺寸而非子内容尺寸,用PreferenceKey从子视图向上传递真实尺寸即可解决,完整实现步骤如下:
1. 定义尺寸捕获工具和全局状态
首先定义PreferenceKey捕获内容视图的真实尺寸,同时创建Observable对象存储内容尺寸供AppKit层调用:
import SwiftUI import AppKit // 捕获内容视图实际尺寸的PreferenceKey struct ContentSizeKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() } } // 全局存储内容尺寸的状态对象 class WindowState: ObservableObject { @Published var contentSize: CGSize = .zero }
2. 调整ContentView上报自身尺寸
在内容视图的background层嵌套GeometryReader,拿到的尺寸就是内容的真实大小,通过PreferenceKey向上传递:
struct ContentView: View { var body: some View { Text("This is an experimental piece of text.") .padding(50) .border(Color.red, width: 5) .fixedSize() // 捕获当前视图的真实尺寸 .background(GeometryReader { geo in Color.clear .preference(key: ContentSizeKey.self, value: geo.size) }) } }
3. 实现自定义WindowDelegate关联状态
不要全局扩展NSWindowController,自定义delegate绑定存储尺寸的状态对象,计算最优尺寸时要加上窗口标题栏的高度:
class CustomWindowDelegate: NSObject, NSWindowDelegate { let windowState: WindowState init(windowState: WindowState) { self.windowState = windowState } func windowWillUseStandardFrame(_ window: NSWindow, defaultFrame newFrame: NSRect) -> NSRect { // 叠加标题栏高度,避免内容被挤压 let titleBarHeight = window.frame.height - window.contentView!.frame.height let optimalSize = CGSize( width: windowState.contentSize.width, height: windowState.contentSize.height + titleBarHeight ) return NSRect(origin: window.frame.origin, size: optimalSize) } }
4. 主App结构绑定逻辑
在主App中注入状态、监听尺寸变化、给窗口绑定自定义delegate,同时设置全局最小窗口尺寸:
@main struct ZoomApp: App { @StateObject private var windowState = WindowState() @State private var windowDelegate: CustomWindowDelegate? var body: some Scene { WindowGroup { ContentView() .frame(minWidth: 50, maxWidth: .infinity, minHeight: 50, maxHeight: .infinity) // 接收内容视图上报的尺寸更新全局状态 .onPreferenceChange(ContentSizeKey.self) { newSize in windowState.contentSize = newSize } // 获取当前窗口绑定自定义delegate .background(WindowAccessor { window in guard let window = window, window.delegate == nil else { return } let delegate = CustomWindowDelegate(windowState: windowState) window.delegate = delegate self.windowDelegate = delegate }) } // 全局设置窗口最小尺寸,禁止缩到更小 .windowResizability(.contentSize(minimumSize: CGSize(width: 50, height: 50))) } } // 辅助工具:获取当前SwiftUI视图对应的NSWindow实例 struct WindowAccessor: NSViewRepresentable { let callback: (NSWindow?) -> Void func makeNSView(context: Context) -> NSView { let view = NSView() DispatchQueue.main.async { callback(view.window) } return view } func updateNSView(_ nsView: NSView, context: Context) {} }
该实现完全符合需求:窗口小于最小尺寸时无法继续缩放,点击缩放按钮会自动适配内容最优尺寸,正常拖拽可以自由放大窗口。
内容的提问来源于stack exchange,提问作者fankibiber
相关产品推荐
相关产品推荐

