You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 19:18:19