目标是在 SwiftUI View 层次结构的任何级别轻松访问托管窗口。目的可能不同 - 关闭窗口、辞去第一响应者、替换 Root View 或 contentViewController。与 UIKit/AppKit 集成有时也需要通过窗口的路径,所以……
我在这里遇到并尝试过的,
像这样的东西
let keyWindow = shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
或通过在每个 SwiftUI View 中添加 UIViewRepresentable/NSViewRepresentable 来使用
view.window
获取窗口看起来丑陋,沉重,而且不可用。因此,我该怎么做?
最佳答案
SwiftUI Lift-Cycle (SwiftUI 2+)
这是一个解决方案(使用 Xcode 13.4 测试),仅适用于 iOS
@main
struct PlayOn_iOSApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
// ...
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
if connectingSceneSession.role == .windowApplication {
configuration.delegateClass = SceneDelegate.self
}
return configuration
}
}
SceneDelegate
并向两者确认 (!!!+) UIWindowSceneDelegate
和 ObservableObject
class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate {
var window: UIWindow? // << contract of `UIWindowSceneDelegate`
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
self.window = windowScene.keyWindow // << store !!!
}
}
EnvironmentObject
,因为(确认到 ObservableObject
的奖励)SwiftUI 自动将其注入(inject) ContentView
@EnvironmentObject var sceneDelegate: SceneDelegate
var body: some View {
// ...
.onAppear {
if let myWindow = sceneDelegate.window {
print(">> window: \(myWindow.description)")
}
}
}
Complete code in project is here
UIKit 生命周期
这是我的实验结果,看起来很适合我,因此可能也会有帮助。使用 Xcode 11.2/iOS 13.2/macOS 15.0 测试
这个想法是使用原生的 SwiftUI 环境概念,因为一旦注入(inject)的环境值就可以自动用于整个 View 层次结构。所以
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
}
}
}
// 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
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)
}
}
}
}
backup
关于ios - 如何在 SwiftUI View 中访问自己的窗口?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60359808/