一尘不染

如何在SwiftUI视图中访问自己的窗口?

swift

目标是可以轻松访问任何级别的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看起来丑陋,沉重且不可用的窗口。

因此,我该怎么做?


阅读 344

收藏
2020-07-07

共1个答案

一尘不染

这是我的实验结果,很适合我,因此可能对您有所帮助。已通过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)
            }
        }
    }
}
2020-07-07