首页 > 解决方案 > 如何使用 FocusedValues 将子视图状态链接到菜单

问题描述

SwiftUI 新手在这里。在为 MacOS 应用程序构建菜单时,我很难理解如何@FocusedBinding和工作。FocusedValues我正在尝试使用窗口工具栏中的按钮构建Apple HIG UI 模式,以更改列表视图并匹配视图菜单中的菜单项。就像 Finder 窗口有四种不同的视图模式一样。

我已经阅读了Apple 的 Landmarks 教程Apple 开发论坛中的 Frameworks Engineer 的示例代码Majid 的教程

苹果文档FocusedValues“由焦点视图及其祖先导出的状态集合”。我假设集合是全局的,我可以focusedValue在任何子视图中设置 a,并从我的代码中的任何位置读取或绑定到任何 FocusedValues。

因此,我不明白为什么下面的第一个示例有效,而第二个示例无效?

这有效:

import SwiftUI

@main
struct TestiApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .frame(minWidth: 200, minHeight: 300)
                .onAppear {
                    NSWindow.allowsAutomaticWindowTabbing = false
                }
        }
        .windowStyle(HiddenTitleBarWindowStyle())
        .commands {
            MenuCommands()
        }
    }
}


struct ContentView: View {
    // In the working version of the code selectedView is defined here
    // in the ContentView, which is a direct child of the WindowGroup 
    // that has the .commands modifier.
    @State private var selectedView: Int = 0
    // For demonstration purposes I have simplified the authorization
    // code to a hardcoded boolean.
    private var isAuthorized: Bool = true
    
    var body: some View {
        switch isAuthorized {
        case true:
            // focusedValue is set here to the selectedView binding.
            // I don't really understand why do it here, but it works.
            AuthorizedView(selectedView: $selectedView)
                .focusedValue(\.selectedViewBinding, $selectedView)
        default:
            NotYetAuthorizedView()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


struct AuthorizedView: View {
    // selectedView is passed to this view as an argument and 
    // bound from ContentView.
    @Binding var selectedView: Int

    var body: some View {
        List {
            Text("Something here")
            Text("Something more")
            Text("Even more")
        }
        .toolbar {
            // The Picker element sets and gets the bound selectedView value
            Picker("View", selection: $selectedView) {
                Text("View 1").tag(0)
                Text("View 2").tag(1)
            }
            .pickerStyle(SegmentedPickerStyle())
        }
    }
}


struct NotYetAuthorizedView: View {
    var body: some View {
        VStack {
            Text("You need permission to access this ")
                .padding()
        }
    }
}


struct MenuCommands: Commands {

    private struct MenuContent: View {
        // Command menu binds the selectedView value through focusedValues.
        // MenuContent is a View, because otherwise the binding doesn't
        // work (I read there's a bug in SwiftUI...).
        @FocusedBinding(\.selectedViewBinding) var selectedView: Int?
        
        var body: some View {
            Button("View 1") {
                selectedView = 0
            }
            .keyboardShortcut("1")
            
            Button("View 2") {
                selectedView = 1
            }
            .keyboardShortcut("2")
        }
    }

    var body: some Commands {
        CommandGroup(before: .toolbar) {
            MenuContent()
        }
    }
}


struct SelectedViewBinding: FocusedValueKey {
    typealias Value = Binding<Int>
}

extension FocusedValues {
    var selectedViewBinding: SelectedViewBinding.Value? {
        get { self[SelectedViewBinding.self] }
        set { self[SelectedViewBinding.self] = newValue }
    }
}

但是,如果我对 ContentView 和 AuthorizedView 进行以下更改,则项目可以正常编译,但selectedView命令菜单和命令菜单之间的绑定不再起作用:

struct ContentView: View {
    // selectedView definition has been removed from ContentView
    // and moved to AuthorizedView.
    private var isAuthorized: Bool = true
    
    var body: some View {
        switch isAuthorized {
        case true:
            AuthorizedView()
            // Also setting the focusedValue here has been removed
        default:
            NotYetAuthorizedView()
        }
    }
}

struct AuthorizedView: View {
    // Moved selectedView definition here
    @State private var selectedView: Int = 0

    var body: some View {
        List {
            Text("Something here")
            Text("Something more")
            Text("Even more")
        }
        .toolbar {
            Picker("View", selection: $selectedView) {
                Text("View 1").tag(0)
                Text("View 2").tag(1)
            }
            .pickerStyle(SegmentedPickerStyle())
            .focusedValue(\.selectedViewBinding, $selectedView)
            // I am now setting the focusedValue here, which seems 
            // more logical to me...
        }
    }
}

我更喜欢第二个例子,因为selectedView状态被封装在AuthorizedView它所属的地方。

标签: macosswiftui

解决方案


推荐阅读