KeyWindow

这个包提供了一种方式,可以将来自 key 窗口的值共享到应用程序的所有其他部分。(macOS/iPadOS 中的 key 窗口是当前响应键盘快捷键的窗口)。

此包的主要用例是使能够将来自 key 窗口的值传递到 SwiftUI 生命周期应用程序的 commands 部分中的视图。

import KeyWindow

@main
struct ExampleWindowReaderApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView().observeWindow()
        }.commands {
            CommandMenu("MyMenue") {
                MenuButton()
            }
        }
    }
}

注意: 重要的是使用 .observeWindow() 修饰符包裹你的应用程序的 ContentView,这使得 KeyWindow 能够观察这个窗口及其发布的值。

在我们的 ContentView 及其子视图中,我们可以像发布 Preferences 一样发布值。

首先,创建一个符合 KeyWindowValueKey 协议的结构体,并声明值的类型。

struct SelectedProjectWindowValueKey: KeyWindowValueKey {
    typealias Value = Binding<UUID>
}

struct SelectedProjectTitleWindowValueKey: KeyWindowValueKey {
    typealias Value = String
}

然后,为了在我们的视图中提供这些值,我们可以使用 .keyWindow(SelectedProjectWindowValueKey.self, $projectSelection) 来设置它们。 这将向上冒泡,当窗口变为 key 时,我们可以在我们的 MenuButton 中读取这些值。

struct MenuButton: View {
    @KeyWindowValue(SelectedProjectTitleWindowValueKey.self)
    var title: String
    
    @KeyWindowValueBinding(SelectedProjectWindowValueKey.self)
    var selectedProject: UUID?

    var body: some View {
        Button(action: {
            // Delete the selectedProject
            self.selectedProject = // some other project
        }, label: {
            Text("Delete \(title)")
        })
    }
}

注意: 你不能在 struct MyCommands: Commands 类型中使用 @KeyWindowValue@KeyWindowValueBinding。 你需要将 ButtonsCommands 的 body 中提取到它们自己的 Views 中,以便你可以使用这些属性包装器(这是 SwiftUI 的一个限制)。

有关共享来自 key 窗口的文档以便在 commands 部分中访问的更详细信息,请参阅 这篇博客文章,其中涵盖了此用例。

你也可以在其他视图中使用 @KeyWindowValue@KeyWindowValueBinding,但要小心确保它们不会导致 .keyWindow 修饰符被重复调用,否则可能会陷入循环。 最佳方法是在你的视图层次结构中设置所有 .keyWindow,仅在深度嵌套的子视图上使用 @KeyWindowValue@KeyWindowValueBinding

此外,此包还提供了可以读取的 EnvironmentValues.isKeyWindow,以检测视图是否在 key 窗口中。(你可以使用它来更改渲染样式)。 请注意,这与 scenePhase 不同,scenePhase 指示场景是否 active,而不是视图的窗口是否在 Key Window 中。 在 iPadOS 和 macOS 上,你可以有多个场景处于活动状态,并且在 macOS 上,每个场景可以有多个窗口,但任何给定时间只有一个窗口是 key 窗口。

为你的值类型找到正确的属性包装器

根据你尝试从 key 窗口共享的数据类型,你可以使用几种不同的属性包装器来读取该值

类型 属性包装器 注释
"String" , 42 或者 MyStruct @KeyWindowValue 出于性能原因,最好是这些值类型符合 equatable 协议
Binding<MyStruct> @KeyWindowValueBinding -
Binding<Optional<MyStruct>> @KeyWindowOptionalValueBinding -
Observable 对象 @KeyWindowObservableObject 在这种情况下,Key 需要符合 KeyWindowObservableObjectKey 协议并提供 defaultValue