目标是可以轻松访问任何级别的SwiftUI视图层次结构的托管窗口。目的可能有所不同- 关闭窗口,退出第一响应者,替换根视图或contentViewController。与UIKit / AppKit集成有时也需要通过窗口的路径,因此…
我在这里遇到和尝试过的
像这样的东西
let keyWindow = shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .map({$0 as? UIWindowScene}) .compactMap({$0}) .first?.windows .filter({$0.isKeyWindow}).first
或通过在每个SwiftUI视图中添加UIViewRepresentable / NSViewRepresentable来使用view.window看起来丑陋,沉重且不可用的窗口。
view.window
因此,我该怎么做?
这是我的实验结果,很适合我,因此可能对您有所帮助。已通过Xcode 11.2 / iOS 13.2 / macOS 15.0测试
该想法是使用本机SwiftUI环境概念,因为一旦注入的环境值将自动用于整个视图层次结构。所以
1)定义环境密钥。注意,需要记住避免在保留窗口上循环引用
struct HostingWindowKey: EnvironmentKey { #if canImport(UIKit) typealias WrappedValue = UIWindow #elseif canImport(AppKit) typealias WrappedValue = NSWindow #else #error("Unsupported platform") #endif typealias Value = () -> WrappedValue? // needed for weak link static let defaultValue: Self.Value = { nil } } extension EnvironmentValues { var hostingWindow: HostingWindowKey.Value { get { return self[HostingWindowKey.self] } set { self[HostingWindowKey.self] = newValue } } }
2)将托管窗口注入到根ContentView中,而不是创建窗口(在AppDelegate或SceneDelegate中仅一次)
// window created here let contentView = ContentView() .environment(\.hostingWindow, { [weak window] in return window }) #if canImport(UIKit) window.rootViewController = UIHostingController(rootView: contentView) #elseif canImport(AppKit) window.contentView = NSHostingView(rootView: contentView) #else #error("Unsupported platform") #endif
3)仅在需要的地方使用,只需声明环境变量
struct ContentView: View { @Environment(\.hostingWindow) var hostingWindow var body: some View { VStack { Button("Action") { // self.hostingWindow()?.close() // macOS // self.hostingWindow()?.makeFirstResponder(nil) // macOS // self.hostingWindow()?.resignFirstResponder() // iOS // self.hostingWindow()?.rootViewController?.present(UIKitController(), animating: true) } } } }