SwiftUI macOS 11.0 实现窗口大小与位置的保存与恢复
实现macOS 11.0下SwiftUI文档应用的窗口状态记忆功能
要让文档应用记住窗口的位置和大小,需要完成三个核心步骤:在文档模型中存储窗口状态、获取并监听窗口事件、加载时恢复窗口状态。以下是完整的修改方案:
1. 修改文档模型,添加窗口状态存储
首先扩展你的V_WorkflowDocument,添加窗口frame的存储属性,并实现对应的编解码逻辑(NSRect默认不支持Codable,需要手动拆解存储):
import SwiftUI import UniformTypeIdentifiers struct V_WorkflowDocument: FileDocument { // 文档正文内容 var text: String = "" // 新增:存储窗口位置与大小 var windowFrame: NSRect? static var readableContentTypes: [UTType] { [.plainText] } init() {} init(configuration: ReadConfiguration) throws { if let data = configuration.file.regularFileContents { let decoder = JSONDecoder() let documentData = try decoder.decode(DocumentData.self, from: data) text = documentData.text windowFrame = documentData.windowFrame } else { text = "" } } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { let documentData = DocumentData(text: text, windowFrame: windowFrame) let encoder = JSONEncoder() let data = try encoder.encode(documentData) return .init(regularFileWithContents: data) } // 辅助结构体,处理Codable编解码 private struct DocumentData: Codable { let text: String let windowFrame: NSRect? enum CodingKeys: String, CodingKey { case text case originX, originY, width, height } init(text: String, windowFrame: NSRect?) { self.text = text self.windowFrame = windowFrame } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) text = try container.decode(String.self, forKey: .text) guard let x = try container.decodeIfPresent(CGFloat.self, forKey: .originX), let y = try container.decodeIfPresent(CGFloat.self, forKey: .originY), let width = try container.decodeIfPresent(CGFloat.self, forKey: .width), let height = try container.decodeIfPresent(CGFloat.self, forKey: .height) else { windowFrame = nil return } windowFrame = NSRect(x: x, y: y, width: width, height: height) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(text, forKey: .text) if let frame = windowFrame { try container.encode(frame.origin.x, forKey: .originX) try container.encode(frame.origin.y, forKey: .originY) try container.encode(frame.size.width, forKey: .width) try container.encode(frame.size.height, forKey: .height) } } } }
2. 创建ContentView并监听窗口事件
创建独立的ContentView,负责获取窗口实例、恢复窗口状态,以及在窗口关闭时保存状态到文档:
struct ContentView: View { @Binding var document: V_WorkflowDocument @State private var window: NSWindow? var body: some View { TextEditor(text: $document.text) .onAppear { // 延迟获取窗口,确保窗口已完全加载 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { window = NSApplication.shared.windows.first { $0.isKeyWindow } // 恢复窗口状态 if let savedFrame = document.windowFrame { window?.setFrame(savedFrame, display: true) } else { // 设置默认窗口大小 window?.setFrame(NSRect(x: 100, y: 100, width: 800, height: 600), display: true) } // 设置窗口代理,监听关闭事件 window?.delegate = self } } } } // 扩展ContentView实现NSWindowDelegate,处理窗口关闭事件 extension ContentView: NSWindowDelegate { func windowWillClose(_ notification: Notification) { guard let closingWindow = notification.object as? NSWindow else { return } // 保存当前窗口frame到文档 document.windowFrame = closingWindow.frame // 强制保存文档(确保状态写入文件) if let fileURL = document.fileURL { try? document.save(to: fileURL, for: .forOverwriting) } } }
3. 更新DocumentScene关联ContentView
修改DocumentScene,让DocumentGroup使用我们创建的ContentView:
@main struct MyApp: App { var body: some Scene { DocumentScene() } } struct DocumentScene: Scene { var body: some Scene { DocumentGroup(newDocument: V_WorkflowDocument()) { file in ContentView(document: file.$document) } } }
关键说明
- macOS 11.0兼容性:该方案完全基于macOS 11.0支持的API,没有使用后续版本新增的SwiftUI特性。
- 未保存文档处理:如果是新建未保存的文档,关闭后再打开会使用默认窗口大小,因为未保存的文档没有持久化的文件路径,无法存储窗口状态。
- 窗口状态准确性:通过
windowWillClose事件确保窗口状态在关闭前被保存,避免丢失;延迟获取窗口实例保证了窗口已完全初始化,恢复状态更可靠。
内容的提问来源于stack exchange,提问作者user2555515
相关产品推荐
相关产品推荐

