如何在SwiftUI生命周期中为三列视图添加工具栏分隔符

4
我正在寻找一种实现类似于 Mail.app 的三列布局的工具栏的方法。同时,Notes.app 几乎使用相同的工具栏,两个应用程序之间唯一重要的区别是 Notes.app 窗口样式为 HiddenTitleBarWindowStyle,而 Mail.app 窗口样式为 Default|TitleBarWindowStyle。
以下内容应该是正确的:
  1. 如果侧边栏被折叠,将显示列表和详细视图。
  2. 分隔列表和详细视图的分割线一直延伸到工具栏上方。(这可以通过使用 HiddenTitleBarWindowStyle 实现)sidebar collapsed - fully divided view
  3. 如果标题太长而无法适应导航列表,则垂直分隔线将被打破:列表仍然与详细视图分开,但现在工具栏看起来像一个只有小的类似于 Divider() 的线条的 DefaultWindowStylesidebar collapsed - divider line

我需要哪种组合的 WindowStyleWindowToolbarStyle.toolbar 配置才能实现这个设置?

编辑

我注意到无法移除Notes.app显示的分隔线。但我在文档中没有找到任何相关元素的参考。

Notes.app Customizable Toolbar

代码示例

我将问题简化为一个主要包含工具栏内容的简单应用程序。在我的原始代码中,我使用了两个嵌套的NavigationView,而在示例中,我只使用了一个NavigationView和两个列表。但是,Toolbar的结果是相同的。

带有DefaultWindowStyle的工具栏

import SwiftUI

@main
struct ToolbarTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(titleBarIsHidden: true)
        }
        .windowToolbarStyle(UnifiedWindowToolbarStyle())
        .commands {
            SidebarCommands()
        }
    }
}

struct ContentView: View {
    @State var destination: String = "Toolbar Test"
    @State var detail: String = ""
    
    var body: some View {
        NavigationView {
            List {
                Button(action: {self.destination = "Item with the identifier:  1"}, label: {
                    Text("Item 1")
                })
                .buttonStyle(DefaultButtonStyle())
                Button(action: {self.destination = "Item 2"}, label: {
                    Text("Item 2")
                })
                .buttonStyle(DefaultButtonStyle())
            }
            .listStyle(SidebarListStyle())
            List {
                NavigationLink(
                    destination: DetailView(text: "\(destination)\(detail)").onAppear{self.detail = "Detail 1"},
                    label: {
                        Text("\(destination) – Detail 1")
                    })
                NavigationLink(
                    destination: DetailView(text: "\(destination)\(detail)").onAppear{self.detail = "Detail 2"},
                    label: {
                        Text("\(destination) – Detail 2")
                    })
            }
            .listStyle(InsetListStyle())
            Text("\(destination)\(detail)")
        }
        .navigationTitle(destination)
        .navigationSubtitle(detail)
        .toolbar(id: "nav") {
            ToolbarItem(id: "plus", placement: ToolbarItemPlacement.principal, showsByDefault: true) {
                HStack {
                    Button(action: {print("pressed")}, label: {
                        Image(systemName: "plus.circle")
                    })
                }
            }
            ToolbarItem(id: "spacer", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
                HStack {
                    Spacer()
                }
            }
            ToolbarItem(id: "sidebar.end", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
                Button(action: {print("pressed")}, label: {
                    Image(systemName: "sidebar.right")
                })
            }
        }
    }
}

这个示例将生成一个Toolbar,它永远不会显示分割整个Toolbar为两个部分的分隔线。另外,第一个ToolbarItem位于Toolbar的中心位置。我尝试了所有的ToolbarItemPlacement,但没有一个导致该项移动到与标题相邻的最左边。

Default Window Style

使用HiddenTitleBarWindowStyle的工具栏

@main
struct ToolbarTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentViewForHiddenTitleBar()
        }
        .windowStyle(HiddenTitleBarWindowStyle()) // added hidden title style
        .windowToolbarStyle(UnifiedWindowToolbarStyle())
        .commands {
            SidebarCommands()
        }
    }
}

struct ContentViewForHiddenTitleBar: View {
    @State var destination: String = "Toolbar Test"
    @State var detail: String = ""
    
    var body: some View {
        NavigationView {
            List {
                Button(action: {self.destination = "Item with the identifier:  1"}, label: {
                    Text("Item 1")
                })
                .buttonStyle(DefaultButtonStyle())
                Button(action: {self.destination = "Item 2"}, label: {
                    Text("Item 2")
                })
                .buttonStyle(DefaultButtonStyle())
            }
            .listStyle(SidebarListStyle())
            // add geometry reader to trim title width in toolbar
            GeometryReader { geometry in
                List {
                    NavigationLink(
                        destination: DetailView(text: "\(destination)\(detail)").onAppear{self.detail = "Detail 1"},
                        label: {
                            Text("\(destination) – Detail 1")
                        })
                    NavigationLink(
                        destination: DetailView(text: "\(destination)\(detail)").onAppear{self.detail = "Detail 2"},
                        label: {
                            Text("\(destination) – Detail 2")
                        })
                }
                // there is no title anymore so let's fake it.
                .toolbar(id: "list navigation") {
                    ToolbarItem(id: "title", placement: ToolbarItemPlacement.navigation, showsByDefault: true) {
                        VStack(alignment: .leading) {
                            Text(destination)
                                .font(.headline)
                                .frame(maxWidth: .infinity, alignment: .leading)
                            Text(detail)
                                .font(.subheadline)
                                .opacity(0.6)
                                .frame(maxWidth: .infinity, alignment: .leading)
                        }
                        .frame(width: geometry.size.width)
                    }
                }
            }
            .listStyle(InsetListStyle())
            Text("\(destination)\(detail)")
        }
        .navigationTitle(destination)
        .navigationSubtitle(detail)
        .toolbar(id: "nav") {
            // primary action will place the item next to the divider line.
            ToolbarItem(id: "plus", placement: ToolbarItemPlacement.primaryAction, showsByDefault: true) {
                HStack {
                    Button(action: {print("pressed")}, label: {
                        Image(systemName: "plus.circle")
                    })
                }
            }
            ToolbarItem(id: "spacer", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
                HStack {
                    Spacer()
                }
            }
            ToolbarItem(id: "sidebar.end", placement: ToolbarItemPlacement.confirmationAction, showsByDefault: true) {
                Button(action: {print("pressed")}, label: {
                    Image(systemName: "sidebar.right")
                })
            }
        }
    }
}

这个示例将生成一个Toolbar,它将始终显示完整高度的分隔符,即使标题太长。因此,添加了一个GeometryReader。这很好,直到侧边栏崩溃。 ToolbarItems的放置位置将不正确。另外,在自定义Toolbar时,可能会有删除标题的可能性,这是不应该的。

Hidden Title Bar Window Style Hidden Title Bar Window Style with Collapsed Sidebar


你尝试了哪个?各个有什么问题?你的代码在哪里? - Asperi
我已经更新了问题,并提供了代码示例。这可能不是我迄今为止尝试过的所有组合,但这些看起来最有前途。HiddenTitleBarWindowStyle 看起来似乎永远无法达到目标,因为假标题对我来说完全错误。 - Enie
1个回答

3
默认的标题栏样式没问题,您只需要将工具栏项附加到顶部NavigationView的子视图上即可,例如:
var body: some View {
    NavigationView {
        
        List {
            ...
        }
        .listStyle(SidebarListStyle())
        .toolbar {
            ToolbarItem {
                Button(action: { }, label: {
                    Image(systemName: "sidebar.right")
                })
            }
        }
        
        List {
            ...
        }
        .listStyle(InsetListStyle())
        .toolbar {
            ToolbarItem {
                Button(action: { }, label: {
                    Image(systemName: "plus.circle")
                })
            }
        }
        
        Text("\(destination)\(detail)")
    }
    .navigationTitle(destination)
    .navigationSubtitle(detail)
}

我没有给第三列(文本)附加任何工具栏项目,但是您可以这样做,只需确保将相同的工具栏项目附加到您的DetailView中,因为当用户导航时,其工具栏将替换文本的工具栏。(如果您不确定我的意思,请尝试一下,您很快就会明白我在说什么 :)


2
你知道吗,我花了好几天的时间才搞明白这个问题。但是有了你的见解,只用了两分钟就得到了我想要的结果。将工具栏项目添加到第二个列表和DetailView中,正好实现了我想要的分割效果。非常感谢你,祝你新年快乐! - Enie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接