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

visionOS 中如何实现重启应用时自动恢复窗口位置?

实现SwiftUI多窗口位置的持久化恢复

对于用@Environment(\.openWindow)创建的多窗口,要实现重启后恢复窗口位置,可以通过状态持久化存储结合窗口生命周期监听来实现,以下是具体方案:

1. 定义窗口状态模型与管理器

首先创建可序列化的窗口状态结构体,以及全局的状态管理器来加载、保存所有窗口的位置信息:

import SwiftUI

// 存储单个窗口的ID和位置大小
struct WindowState: Codable, Identifiable {
    let id: String
    var frame: CGRect
}

// 管理所有窗口状态的全局管理器
class WindowStateManager: ObservableObject {
    @Published var windowStates: [WindowState] = []
    
    // 从UserDefaults加载已保存的状态
    func loadStates() {
        guard let data = UserDefaults.standard.data(forKey: "appWindowStates") else { return }
        windowStates = (try? JSONDecoder().decode([WindowState].self, from: data)) ?? []
    }
    
    // 将当前状态保存到UserDefaults
    func saveStates() {
        guard let data = try? JSONEncoder().encode(windowStates) else { return }
        UserDefaults.standard.set(data, forKey: "appWindowStates")
    }
}

2. 在App中注入管理器并关联窗口ID

在你的App主结构体中,初始化状态管理器,并为每个通过openWindow打开的窗口分配唯一ID,同时监听窗口的出现/消失事件来应用或保存状态:

@main
struct MultiWindowDemoApp: App {
    @StateObject private var stateManager = WindowStateManager()
    
    var body: some Scene {
        // 基于字符串ID的WindowGroup,每个窗口对应唯一ID
        WindowGroup(for: String.self) { $windowID in
            WindowContentView(windowID: windowID)
                .environmentObject(stateManager)
                .onAppear {
                    // 窗口出现时,恢复已保存的位置
                    applySavedState(for: windowID)
                }
                .onDisappear {
                    // 窗口消失时,保存当前位置
                    saveCurrentState(for: windowID)
                }
        }
        .commands {
            CommandGroup(after: .newItem) {
                Button("打开窗口A") {
                    openWindow(id: "window_A")
                }
                Button("打开窗口B") {
                    openWindow(id: "window_B")
                }
            }
        }
        .onAppear {
            // 应用启动时加载所有保存的窗口状态
            stateManager.loadStates()
        }
    }
    
    // 恢复指定ID窗口的位置
    private func applySavedState(for windowID: String) {
        guard let state = stateManager.windowStates.first(where: { $0.id == windowID }),
              let window = NSApplication.shared.windows.first(where: { $0.identifier?.rawValue == windowID }) else {
            return
        }
        window.setFrame(state.frame, display: true)
    }
    
    // 保存指定ID窗口的当前位置
    private func saveCurrentState(for windowID: String) {
        guard let window = NSApplication.shared.windows.first(where: { $0.identifier?.rawValue == windowID }) else { return }
        
        let currentFrame = window.frame
        if let index = stateManager.windowStates.firstIndex(where: { $0.id == windowID }) {
            stateManager.windowStates[index].frame = currentFrame
        } else {
            stateManager.windowStates.append(WindowState(id: windowID, frame: currentFrame))
        }
        stateManager.saveStates()
    }
}

3. 实时监听窗口位置变化(可选)

如果希望用户拖动窗口时实时保存位置,而不是等到窗口消失,可以通过NSWindowDelegate监听窗口的移动/ resize事件:

struct WindowContentView: View {
    let windowID: String
    @EnvironmentObject private var stateManager: WindowStateManager
    
    var body: some View {
        VStack {
            Text("窗口 \(windowID)")
                .font(.title)
        }
        .frame(width: 300, height: 200)
        .onAppear {
            // 为当前窗口设置代理
            guard let window = NSApplication.shared.windows.first(where: { $0.identifier?.rawValue == windowID }) else { return }
            window.delegate = WindowStateDelegate(windowID: windowID, manager: stateManager)
            // 确保窗口有唯一标识符
            window.identifier = NSUserInterfaceItemIdentifier(rawValue: windowID)
        }
    }
}

// 窗口代理,监听位置和大小变化
class WindowStateDelegate: NSObject, NSWindowDelegate {
    let windowID: String
    let manager: WindowStateManager
    
    init(windowID: String, manager: WindowStateManager) {
        self.windowID = windowID
        self.manager = manager
        super.init()
    }
    
    func windowDidMove(_ notification: Notification) {
        updateWindowState(from: notification)
    }
    
    func windowDidResize(_ notification: Notification) {
        updateWindowState(from: notification)
    }
    
    private func updateWindowState(from notification: Notification) {
        guard let window = notification.object as? NSWindow else { return }
        let currentFrame = window.frame
        
        if let index = manager.windowStates.firstIndex(where: { $0.id == windowID }) {
            manager.windowStates[index].frame = currentFrame
        } else {
            manager.windowStates.append(WindowState(id: windowID, frame: currentFrame))
        }
        manager.saveStates()
    }
}

关键注意事项

  • 存储方式:示例中用UserDefaults存储,适合轻量数据;如果窗口数量多或需要更复杂的状态,可以改用Core Data或本地文件存储。
  • 窗口ID唯一性:必须确保每个窗口的ID唯一,否则会导致状态覆盖或恢复错误。
  • 关闭窗口的处理:如果希望用户关闭窗口后下次启动不再恢复,可以在windowWillClose代理方法中移除对应窗口的状态。
  • 跨平台适配:如果是iOS/iPadOS的多Scene窗口,原理类似,只需将NSWindow替换为UIWindowScene,监听对应的生命周期事件即可。

内容的提问来源于stack exchange,提问作者Ben Gottlieb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 19:59:59