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

SwiftUI中NSWindow.willCloseNotification触发所有窗口问题及解决问询

SwiftUI多平台应用监听特定窗口关闭通知的问题与解决方法

问题原因

你使用的NotificationCenter.default.publisher(for: NSWindow.willCloseNotification)是全局订阅,它会监听系统中所有NSWindow发出的关闭通知。虽然AppKit文档提到通知会发送给特定窗口对象,但默认的订阅没有过滤通知的object字段(即通知对应的窗口实例),因此所有订阅了该通知的View都会收到任意窗口的关闭事件,导致每个窗口关闭时所有View的清理闭包都被触发。

解决方法

要实现仅监听当前View所在窗口的关闭事件,核心是获取当前View对应的NSWindow实例,然后针对性地订阅通知或设置代理:

方法一:订阅特定窗口的通知

通过自定义NSViewRepresentable捕获当前View所在的窗口,然后订阅该窗口的willCloseNotification:

// 用于捕获当前View所在窗口的辅助View
struct WindowCaptureView: NSViewRepresentable {
    let onWindowFound: (NSWindow) -> Void
    
    func makeNSView(context: Context) -> NSView {
        let view = NSView()
        // 延迟执行确保view已加入窗口层级
        DispatchQueue.main.async {
            if let window = view.window {
                onWindowFound(window)
            }
        }
        return view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {}
}

// 在顶层View中使用
NavigationStack {
    // 你的视图内容
}
.background(WindowCaptureView { window in
    // 仅订阅当前窗口的关闭通知
    let closePublisher = NotificationCenter.default.publisher(
        for: NSWindow.willCloseNotification,
        object: window
    )
    _ = closePublisher.sink { _ in
        // 执行当前窗口的清理操作
        print("当前窗口关闭,执行清理")
    }
})

通过指定object: window,发布者只会转发该窗口发出的关闭通知,实现精准监听。

方法二:设置NSWindowDelegate

通过窗口代理的windowWillClose方法处理清理逻辑,同样需要先捕获当前窗口:

// 自定义窗口代理类
class WindowCleanupDelegate: NSObject, NSWindowDelegate {
    private let cleanupAction: () -> Void
    
    init(cleanupAction: @escaping () -> Void) {
        self.cleanupAction = cleanupAction
        super.init()
    }
    
    func windowWillClose(_ notification: Notification) {
        cleanupAction()
    }
}

// 捕获窗口并设置代理的辅助View
struct WindowDelegateView: NSViewRepresentable {
    let cleanupAction: () -> Void
    @State private var delegate: WindowCleanupDelegate?
    
    func makeNSView(context: Context) -> NSView {
        let view = NSView()
        DispatchQueue.main.async {
            if let window = view.window {
                delegate = WindowCleanupDelegate(cleanupAction: cleanupAction)
                window.delegate = delegate
            }
        }
        return view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {}
}

// 在顶层View中使用
NavigationStack {
    // 你的视图内容
}
.background(WindowDelegateView {
    // 执行清理操作
})

这种方法更贴合AppKit的原生窗口生命周期处理逻辑,适合需要更复杂窗口管理的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 23:07:50