如何重新打开窗口并恢复其关闭时的精确状态?
问题描述
我需要实现重新打开窗口时恢复其关闭时的精确状态(用于展示中间的沉浸式空间),但直接调用dismissWindow(id:)和openWindow(id:)会创建窗口的新实例,无法保留原有状态。尝试过两种方案,但都存在缺陷:
- 保存状态并传入
openWindow:主信息流页面包含动态滚动视图,读取和设置滚动偏移量等操作会引发诸多问题。 - 通过
WindowGroup { TabView {}.opacity(showWindow ? 1 : 0) }隐藏/显示窗口:系统窗口栏和关闭按钮仍然可见,达不到沉浸式效果。
可行解决方案
1. 用SceneStorage持久化窗口核心状态(含滚动位置)
对于SwiftUI的滚动视图,结合ScrollViewReader和SceneStorage可以精准保存并恢复滚动位置,同时持久化其他视图状态(如选中的Tab、展开的单元格等)。SceneStorage会自动关联窗口ID,重新打开同ID窗口时自动恢复状态。
示例代码:
struct MainFeedView: View { // 持久化滚动位置的元素ID @SceneStorage("mainFeedScrollPosition") private var scrollPosition: String? // 持久化选中的Tab索引 @SceneStorage("selectedTabIndex") private var selectedTabIndex = 0 var body: some View { TabView(selection: $selectedTabIndex) { ScrollView { ScrollViewReader { proxy in LazyVStack { ForEach(0..<100) { index in Text("Feed Item \(index)") .id("item-\(index)") .frame(height: 80) .background(Color.gray.opacity(0.2)) } } .onAppear { // 恢复滚动位置 if let scrollPosition = scrollPosition { proxy.scrollTo(scrollPosition, anchor: .top) } } } } .tag(0) Text("Second Tab") .tag(1) } // 监听滚动,保存当前可见区域的核心元素ID .onReceive(NotificationCenter.default.publisher(for: UIScrollView.didScrollNotification)) { notification in guard let scrollView = notification.object as? UIScrollView else { return } let visibleCenter = CGPoint( x: scrollView.contentOffset.x + scrollView.bounds.midX, y: scrollView.contentOffset.y + scrollView.bounds.midY ) if let indexPath = scrollView.indexPathForRow(at: visibleCenter) { scrollPosition = "item-\(indexPath.row)" } } } }
2. 不销毁窗口,通过调整窗口状态隐藏(macOS)
如果是macOS应用,直接操作NSWindow实例,将窗口最小化或移出屏幕,而非调用dismissWindow,这样窗口实例完全保留,状态不会丢失。
示例代码:
import SwiftUI struct MainWindowView: View { @Environment(\.openWindow) private var openWindow var body: some View { Button("切换到沉浸式空间") { // 根据窗口ID定位目标窗口 if let window = NSApplication.shared.windows.first(where: { $0.identifier?.rawValue == "MainWindow" }) { // 最小化窗口,保留实例 window.miniaturize(nil) // 也可以将窗口移到屏幕外: // window.setFrame(CGRect(x: -1000, y: -1000, width: window.frame.width, height: window.frame.height), display: false) } // 打开沉浸式空间窗口 openWindow(id: "ImmersiveSpace") } } } @main struct MyApp: App { var body: some Scene { WindowGroup(id: "MainWindow") { MainWindowView() } WindowGroup(id: "ImmersiveSpace") { ImmersiveSpaceView() } } }
3. 自定义窗口样式隐藏系统元素(iOS/macOS)
如果必须用隐藏/显示的方式,可通过自定义窗口样式隐藏系统栏和按钮,配合透明度实现完全隐藏,同时保留窗口实例。
iOS/iPadOS 示例:
struct MainFeedView: View { @Binding var showWindow: Bool var body: some View { TabView { // 主信息流内容 } .toolbar(.hidden, for: .navigationBar) .statusBarHidden(!showWindow) .opacity(showWindow ? 1 : 0) .disabled(!showWindow) } }
macOS 示例:
struct MainFeedView: View { @Binding var showWindow: Bool var body: some View { TabView { // 主信息流内容 } .frame(minWidth: 800, minHeight: 600) .toolbar(.hidden, for: .windowToolbar) .opacity(showWindow ? 1 : 0) .onChange(of: showWindow) { newValue in guard let window = NSApplication.shared.windows.first(where: { $0.contentView?.subviews.contains(where: { $0 is NSHostingView }) ?? false }) else { return } // 隐藏/显示系统窗口按钮 window.standardWindowButton(.closeButton)?.isHidden = !newValue window.standardWindowButton(.miniaturizeButton)?.isHidden = !newValue window.standardWindowButton(.zoomButton)?.isHidden = !newValue } } }
内容的提问来源于stack exchange,提问作者Jack Guo
相关产品推荐
相关产品推荐

