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

SwiftUI macOS应用:实现窗口层级交互控制及焦点恢复方案问询

在SwiftUI macOS应用中实现层级式模态窗口(仅当前窗口可交互)

需求说明

在基于SwiftUI的macOS应用里,要实现以下效果:

  • 弹出新窗口时,之前的所有窗口(包括主窗口)都变成不可交互状态,只有最新弹出的子窗口保持活跃
  • 窗口层级严格遵循:主窗口 > 子窗口 > 子子窗口 > 更深层级子窗口,只有当前活跃的窗口能被操作
  • 关闭子窗口时,打开它的上一级窗口自动恢复控制权;所有子窗口都关闭后,主窗口恢复可交互状态

修改后的完整代码

import SwiftUI

struct ContentView: View {
    @State private var mainWindow: NSWindow?
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            
            Button("Open new Window") {
                guard let parentWindow = mainWindow else { return }
                let _ = ChildView(string: "ChildView")
                    .openInWindow(title: "ChildView", sender: self, parentWindow: parentWindow)
            }
        }
        .padding()
        .onAppear {
            // 获取主窗口引用
            mainWindow = NSApplication.shared.windows.first { $0.isMainWindow }
        }
    }
}

extension View {
    @discardableResult
    func openInWindow(title: String, sender: Any?, parentWindow: NSWindow) -> NSWindow {
        let controller = NSHostingController(rootView: self)
        let window = NSWindow(contentViewController: controller)
        
        // 设置窗口层级:子窗口层级比父窗口高一级
        window.level = parentWindow.level + 1
        // 窗口关闭时不自动释放,确保能处理后续逻辑
        window.isReleasedWhenClosed = false
        // 禁用父窗口交互
        parentWindow.isEnabled = false
        
        window.center()
        window.title = title
        window.makeKeyAndOrderFront(sender)
        
        // 设置窗口代理,处理关闭时的父窗口恢复逻辑
        window.delegate = WindowDelegate(parentWindow: parentWindow)
        
        return window
    }
}

// 窗口代理类,处理子窗口关闭时恢复父窗口状态
class WindowDelegate: NSObject, NSWindowDelegate {
    private weak var parentWindow: NSWindow?
    
    init(parentWindow: NSWindow) {
        self.parentWindow = parentWindow
        super.init()
    }
    
    func windowWillClose(_ notification: Notification) {
        guard let parentWindow = parentWindow else { return }
        // 恢复父窗口的交互性
        parentWindow.isEnabled = true
        // 激活父窗口,让它重新获得控制权
        parentWindow.makeKeyAndOrderFront(nil)
    }
}

struct ChildView: View {
    let string: String
    @State private var currentWindow: NSWindow?
    
    var body: some View {
        VStack {
            Text(string).bold()
            
            HStack {
                Button("Open new Window") {
                    guard let parentWindow = currentWindow else { return }
                    let _ = ChildView(string: "Another " + string)
                        .openInWindow(title: "Another " + string, sender: self, parentWindow: parentWindow)
                }

                Button("Dismiss") {
                    currentWindow?.close()
                }
                .buttonStyle(.borderedProminent)
            }
        }
        .frame(width: 400.0, height: 200.0)
        .onAppear {
            // 获取当前子窗口的引用
            currentWindow = NSApplication.shared.keyWindow
        }
    }
}

关键改动说明

  1. 窗口父子关联:给openInWindow扩展新增parentWindow参数,明确每个子窗口的父窗口,关闭时能精准定位上一级窗口
  2. 层级控制:通过window.level = parentWindow.level + 1确保子窗口层级始终高于父窗口,形成主窗口→子窗口→子子窗口的层级链
  3. 交互状态切换:打开子窗口时禁用父窗口的交互;子窗口关闭时,通过代理方法windowWillClose恢复父窗口的交互性并激活它
  4. 窗口生命周期管理:设置window.isReleasedWhenClosed = false,避免窗口被自动释放,确保代理事件能正常触发
  5. 窗口引用获取:在视图onAppear时获取当前窗口的引用,避免依赖NSApplication.shared.keyWindow可能出现的错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:52:25