在 macOS 上的 SwiftUI NavigationView 中切换侧边栏

Toggle Sidebar in SwiftUI NavigationView on macOS

我有一个带 NavigationView 的 macOS 应用程序,我想在 window 的工具栏中使用默认的 ToggleSidebar 项目。

目前我在 NSToolbarDelegatetoolbarWillAddItem(_) 中将 ToolbarItem 的目标设置为 AppDelegate。

我实现的 AppDelegate 内部

@objc func toggleSidebar(_ sender: Any) {
    ((window.contentView?.subviews.first?.subviews.first?.subviews.first as? NSSplitView)?.delegate as? NSSplitViewController)?.toggleSidebar(self)
}

此解决方案目前有效。如果 SwiftUI 的实现会改变这个中断。

那么如何才能更好地做到这一点?

我不为此使用 NavigationView。改用 HSplitView 并使用

中的隐藏标志

在 SwiftUI 2.0 中,您可以使用 NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) 和这样的工具栏按钮:

.toolbar {
    ToolbarItem(placement: .navigation) {
        #if os(macOS)
        Button(action: toggleSidebar, label: {
            Image(systemName: "sidebar.left")
        })
        #endif
    }
}

func toggleSidebar() {
    #if os(macOS)
    NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
    #endif
}

从 macOS Big Sur beta 4 开始,您可以使用 SwiftUI 2.0 添加默认侧边栏命令。

var body: some Scene {
    WindowGroup {
        NavigationView {
            Group {
                SidebarView()
                ContentView()
            }
        }
    }
    .commands {
        SidebarCommands()
    }
}

此代码将添加“切换边栏”快捷方式:

侧边栏查看代码:

var body: some View {
    List {
        ForEach(0..<5) { index in
            Text("\(index)")
        }
    }
    .listStyle(SidebarListStyle())
}

虽然您可以尝试在 keyWindow?.contentViewControllerkeyWindow?.firstResponder? 上执行 #selector(NSSplitViewController.toggleSidebar(_:)),但在某些情况下似乎无法始终如一地工作。

相反,您可以使用此命令:

NSApp.sendAction(#selector(NSSplitViewController.toggleSidebar(_:)), to: nil, from: nil)

它将 toggleSidebar 选择器发送到第一个可以对其做出反应的对象,这意味着当前 window 中唯一的侧边栏。这种行为有更好的记录 on Apple's documentation website.


此方法是 SidebarCommands() 菜单项使用的默认实现。这是通过添加 Toggle Sidebar 菜单项找到的,然后像这样获取它的选择器:

let menu = NSApp.mainMenu!.items.first(where: { [=11=].title == "View" })!
let submenu = menu.submenu!.items.first(where: { [=11=].title == "Toggle Sidebar" })!
submenu.target // nil
submenu.action // #selector(toggleSidebar:)

这意味着它很可能比其他方法更一致(和受支持)。