SwiftUI

Toolbars

深入学习 SwiftUI 中的 Toolbars,包括工具栏的创建、配置、定制和最佳实践

概述

Toolbars 是 SwiftUI 提供的一套 API,用于在应用中添加和管理工具栏。工具栏为用户提供了对常用命令和控件的即时访问。根据平台和上下文的不同,系统可能会在应用内容的上方或下方显示工具栏。

在 iOS 和 iPadOS 中,工具栏通常显示在导航栏中;在 macOS 中,工具栏显示在窗口标题栏下方;在 watchOS 中,工具栏项可能会以不同的方式呈现。

填充工具栏

toolbar(content:)

使用 toolbar(content:) 修饰符向视图添加工具栏项。该修饰符接受一个使用 @ToolbarContentBuilder 构建的闭包,可以包含 ToolbarItemToolbarItemGroup

struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("主内容")
                .navigationTitle("我的应用")
                .toolbar {
                    ToolbarItem(placement: .primaryAction) {
                        Button("添加") {
                            // 添加操作
                        }
                    }
                }
        }
    }
}

ToolbarItem

ToolbarItem 表示可以放置在工具栏或导航栏中的单个项目。它是一个泛型结构体,接受 ID 和 Content 类型参数。

struct EditorView: View {
    @State private var isFavorite = false
    
    var body: some View {
        NavigationStack {
            Text("编辑器内容")
                .navigationTitle("编辑器")
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            isFavorite.toggle()
                        } label: {
                            Image(systemName: isFavorite ? "star.fill" : "star")
                        }
                    }
                    
                    ToolbarItem(placement: .navigationBarLeading) {
                        Button("取消") {
                            // 取消操作
                        }
                    }
                }
        }
    }
}

ToolbarItemGroup

ToolbarItemGroup 表示一组可以放置在工具栏或导航栏中的 ToolbarItem。将相关的工具栏项组合在一起可以在不同平台上获得正确的布局和间距。

struct TextEditorView: View {
    @State private var text = ""
    @State private var bold = false
    @State private var italic = false
    @State private var fontSize = 12.0
    
    var displayFont: Font {
        let font = Font.system(
            size: CGFloat(fontSize),
            weight: bold ? .bold : .regular
        )
        return italic ? font.italic() : font
    }
    
    var body: some View {
        NavigationStack {
            TextEditor(text: $text)
                .font(displayFont)
                .toolbar {
                    ToolbarItemGroup {
                        Slider(
                            value: $fontSize,
                            in: 8...120,
                            minimumValueLabel: Text("A").font(.system(size: 8)),
                            maximumValueLabel: Text("A").font(.system(size: 16))
                        ) {
                            Text("字体大小 (\(Int(fontSize)))")
                        }
                        .frame(width: 150)
                        
                        Toggle(isOn: $bold) {
                            Image(systemName: "bold")
                        }
                        
                        Toggle(isOn: $italic) {
                            Image(systemName: "italic")
                        }
                    }
                }
                .navigationTitle("我的笔记")
        }
    }
}

ToolbarItemPlacement

ToolbarItemPlacement 定义了工具栏项的放置位置。SwiftUI 提供了多个预定义的位置选项,这些选项在不同平台上会有不同的表现:

通用位置:

  • .automatic - 自动确定位置
  • .principal - 主要位置(通常在标题区域)
  • .primaryAction - 主要操作位置
  • .secondaryAction - 次要操作位置
  • .destructiveAction - 破坏性操作位置
  • .cancellationAction - 取消操作位置
  • .confirmationAction - 确认操作位置

导航栏位置(iOS/iPadOS):

  • .navigationBarLeading - 导航栏左侧
  • .navigationBarTrailing - 导航栏右侧

底部栏位置:

  • .bottomBar - 底部栏
  • .status - 状态位置
struct ToolbarPlacementExample: View {
    var body: some View {
        NavigationStack {
            Text("内容")
                .navigationTitle("位置示例")
                .toolbar {
                    ToolbarItem(placement: .navigationBarLeading) {
                        Button("返回") { }
                    }
                    
                    ToolbarItem(placement: .principal) {
                        Text("标题区域")
                            .font(.headline)
                    }
                    
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button("完成") { }
                    }
                    
                    ToolbarItem(placement: .bottomBar) {
                        Button("底部操作") { }
                    }
                }
        }
    }
}

设置工具栏可见性

toolbar(_:for:)

使用 toolbar(_:for:) 修饰符指定 SwiftUI 管理的工具栏的可见性。该修饰符已在较新版本中被弃用,推荐使用 toolbarVisibility(_:for:)

struct HiddenToolbarExample: View {
    var body: some View {
        NavigationStack {
            ContentView()
                .toolbar(.hidden, for: .navigationBar)
        }
    }
}

可以同时隐藏多个工具栏:

TabView {
    NavigationStack {
        ContentView()
            .toolbar(.hidden, for: .navigationBar, .tabBar)
    }
}

ToolbarPlacement(工具栏类型)

ToolbarPlacement 结构体用于与 toolbarBackground(_:for:)toolbar(_:for:) 等修饰符配合使用,以自定义不同工具栏的外观。

主要的工具栏类型包括:

  • .navigationBar - 导航栏
  • .tabBar - 标签栏
  • .bottomBar - 底部栏
  • .windowToolbar - 窗口工具栏(macOS)

配置工具栏角色

toolbarRole(_:)

使用 toolbarRole(_:) 修饰符配置填充工具栏内容的语义角色。SwiftUI 在渲染工具栏内容时会使用此角色。

struct BrowserView: View {
    var body: some View {
        NavigationStack {
            ContentView()
                .navigationTitle("浏览器")
                .toolbarRole(.browser)
                .toolbar {
                    ToolbarItem(placement: .primaryAction) {
                        Button("添加") {
                            // 添加操作
                        }
                    }
                }
        }
    }
}

ToolbarRole

ToolbarRole 定义了工具栏的语义角色,包括:

  • .automatic - 自动确定角色
  • .browser - 浏览器角色
  • .editor - 编辑器角色
  • .navigationStack - 导航栈角色

工具栏标题显示模式

toolbarTitleDisplayMode(_:)

配置工具栏标题的显示模式。在 iOS 中,这会影响导航栏标题的大小和位置。

struct TitleDisplayModeExample: View {
    var body: some View {
        NavigationStack {
            List(1..<50) { index in
                Text("项目 \(index)")
            }
            .navigationTitle("列表")
            .toolbarTitleDisplayMode(.inline)
        }
    }
}

ToolbarTitleDisplayMode

标题显示模式包括:

  • .automatic - 自动确定
  • .inline - 内联模式(小标题)
  • .large - 大标题模式

自定义工具栏

可定制工具栏

使用 toolbar(id:content:) 创建可定制的工具栏,允许用户自定义工具栏项的显示和顺序。

struct CustomizableToolbarExample: View {
    var body: some View {
        NavigationStack {
            ContentView()
                .toolbar(id: "main") {
                    ToolbarItem(id: "add", placement: .primaryAction) {
                        Button("添加") { }
                    }
                    
                    ToolbarItem(id: "edit", placement: .secondaryAction) {
                        Button("编辑") { }
                    }
                }
        }
    }
}

ToolbarSpacer

ToolbarSpacer 在工具栏中创建灵活或固定的空间。

struct SpacerExample: View {
    var body: some View {
        NavigationStack {
            Text("内容")
                .toolbar {
                    ToolbarItemGroup {
                        Button("左侧") { }
                        Spacer()
                        Button("右侧") { }
                    }
                }
        }
    }
}

工具栏标题菜单

toolbarTitleMenu(content:)

为工具栏标题添加菜单。在 iOS 中,点击导航栏标题会显示此菜单。

struct TitleMenuExample: View {
    @State private var selectedFilter = "全部"
    
    var body: some View {
        NavigationStack {
            List {
                Text("内容")
            }
            .navigationTitle(selectedFilter)
            .toolbarTitleMenu {
                Button("全部") {
                    selectedFilter = "全部"
                }
                Button("收藏") {
                    selectedFilter = "收藏"
                }
                Button("最近") {
                    selectedFilter = "最近"
                }
            }
        }
    }
}

移除默认工具栏项

toolbar(removing:)

使用 toolbar(removing:) 移除默认的工具栏项。这在某些场景下很有用,比如在编辑模式下移除默认的编辑按钮。

struct RemovingDefaultItemsExample: View {
    var body: some View {
        NavigationStack {
            List {
                Text("项目 1")
                Text("项目 2")
            }
            .toolbar(removing: .sidebarToggle)
        }
    }
}

工具栏背景和样式

设置工具栏背景

可以使用 toolbarBackground(_:for:) 设置工具栏的背景颜色或材质。

struct ToolbarBackgroundExample: View {
    var body: some View {
        NavigationStack {
            List(1..<50) { index in
                Text("项目 \(index)")
            }
            .navigationTitle("列表")
            .toolbarBackground(.blue, for: .navigationBar)
        }
    }
}

设置工具栏背景可见性

使用 toolbarBackgroundVisibility(_:for:) 控制工具栏背景的可见性。

struct ToolbarBackgroundVisibilityExample: View {
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(spacing: 20) {
                    ForEach(1..<50) { index in
                        Text("项目 \(index)")
                            .frame(maxWidth: .infinity)
                            .padding()
                            .background(Color.gray.opacity(0.2))
                    }
                }
            }
            .navigationTitle("滚动视图")
            .toolbarBackgroundVisibility(.hidden, for: .navigationBar)
        }
    }
}

工具栏颜色方案

使用 toolbarColorScheme(_:for:) 设置工具栏的颜色方案(浅色或深色)。

struct ToolbarColorSchemeExample: View {
    var body: some View {
        NavigationStack {
            List(1..<50) { index in
                Text("项目 \(index)")
            }
            .navigationTitle("深色工具栏")
            .toolbarColorScheme(.dark, for: .navigationBar)
        }
    }
}

内容工具栏

contentToolbar(for:content:)

contentToolbar(for:content:) 用于填充特定内容视图类型的工具栏。这在处理特定类型的内容(如文本编辑器或图像查看器)时很有用。

struct ContentToolbarExample: View {
    var body: some View {
        NavigationStack {
            TextEditor(text: .constant(""))
                .contentToolbar(for: .textEditor) {
                    ToolbarItem {
                        Button("格式化") { }
                    }
                }
        }
    }
}

搜索工具栏行为

searchToolbarBehavior(_:)

配置搜索在工具栏中的行为。

struct SearchToolbarBehaviorExample: View {
    @State private var searchText = ""
    
    var body: some View {
        NavigationStack {
            List(1..<50) { index in
                Text("项目 \(index)")
            }
            .searchable(text: $searchText)
            .searchToolbarBehavior(.automatic)
        }
    }
}

平台特定功能

macOS 窗口工具栏

在 macOS 上,可以使用 windowToolbarFullScreenVisibility(_:) 配置全屏模式下窗口工具栏的可见性。

struct MacOSToolbarExample: View {
    var body: some View {
        ContentView()
            .windowToolbarFullScreenVisibility(.visible)
    }
}

隐藏工具栏项

使用 toolbarItemHidden(_:) 隐藏控制组工具栏项中的单个视图。

struct HiddenItemExample: View {
    @State private var showAdvanced = false
    
    var body: some View {
        NavigationStack {
            Text("内容")
                .toolbar {
                    ToolbarItemGroup {
                        Button("基础") { }
                        Button("高级") { }
                            .toolbarItemHidden(!showAdvanced)
                    }
                }
        }
    }
}

最佳实践

  1. 使用 ToolbarItemGroup 组织相关项:将相关的工具栏项组合在 ToolbarItemGroup 中,以确保在不同平台上获得正确的布局和间距。

  2. 选择合适的 placement:根据操作的重要性和语义选择合适的 ToolbarItemPlacement,让用户能够快速找到常用功能。

  3. 保持工具栏简洁:不要在工具栏中放置过多项目。系统会根据可用空间决定渲染多少项目,剩余项目可能会被放入溢出菜单。

  4. 考虑平台差异:同一个 placement 在不同平台上可能有不同的表现,测试时要考虑目标平台的特性。

  5. 使用语义化的 ToolbarRole:为工具栏设置合适的角色,帮助 SwiftUI 更好地渲染工具栏内容。

  6. 响应式设计:在 iPad 上,考虑使用可定制工具栏,让用户根据自己的需求调整工具栏布局。

总结

SwiftUI 的 Toolbars API 提供了强大而灵活的工具栏管理能力。通过 toolbar(content:) 修饰符和相关的配置选项,我们可以创建适应不同平台和场景的工具栏界面。理解 ToolbarItemToolbarItemGroupToolbarItemPlacement 的使用方式,以及如何配置工具栏的可见性、角色和样式,是构建优秀 SwiftUI 应用的重要技能。

通过合理使用这些 API,我们可以为用户提供直观、高效的操作界面,提升应用的整体用户体验。

在 GitHub 上编辑

上次更新于