SwiftUI 中的 toolbar
是一个用于在视图中添加工具栏项的修饰符。它可以用在不同的位置(如导航栏、底部工具栏等),并支持多种样式和布局。
基本语法 #
.toolbar {
ToolbarItem(placement: Placement) {
// 工具栏项的内容
}
}
1. 基础用法 #
导航栏右侧按钮 #
NavigationView {
List {
Text("内容")
}
.navigationTitle("主页")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("添加") {
// 操作
}
}
}
}
多个工具栏项 #
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("返回") {
// 操作
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("编辑") {
// 操作
}
}
}
2. 常用放置位置(Placement) #
.toolbar {
// 1. 导航栏左侧
ToolbarItem(placement: .navigationBarLeading) {
Button("返回") { }
}
// 2. 导航栏右侧
ToolbarItem(placement: .navigationBarTrailing) {
Button("编辑") { }
}
// 3. 底部工具栏
ToolbarItem(placement: .bottomBar) {
Button("操作") { }
}
// 4. 主要操作按钮(通常在右上角)
ToolbarItem(placement: .primaryAction) {
Button("主要") { }
}
// 5. 状态栏
ToolbarItem(placement: .status) {
Text("状态")
}
}
3. 实际应用示例 #
示例 1:典型的编辑界面 #
struct ContentView: View {
@State private var isEditing = false
var body: some View {
NavigationView {
List {
Text("项目 1")
Text("项目 2")
}
.navigationTitle("列表")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(isEditing ? "完成" : "编辑") {
isEditing.toggle()
}
}
}
}
}
}
示例 2:带多个操作的工具栏 #
struct ContentView: View {
var body: some View {
NavigationView {
Text("内容")
.navigationTitle("详情")
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button(action: { }) {
Image(systemName: "square.and.arrow.up")
}
Spacer()
Button(action: { }) {
Image(systemName: "heart")
}
Spacer()
Button(action: { }) {
Image(systemName: "message")
}
}
}
}
}
}
示例 3:自定义工具栏样式 #
struct ContentView: View {
var body: some View {
NavigationView {
List {
// 内容
}
.navigationTitle("主页")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
Button("选项 1") { }
Button("选项 2") { }
Button("选项 3") { }
} label: {
Image(systemName: "ellipsis.circle")
}
}
}
}
}
}
示例 4:搜索栏集成 #
struct ContentView: View {
@State private var searchText = ""
var body: some View {
NavigationView {
List {
// 内容
}
.navigationTitle("搜索")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
HStack {
TextField("搜索...", text: $searchText)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 200)
Button("搜索") {
// 搜索操作
}
}
}
}
}
}
}
4. 高级用法 #
条件性显示工具栏项 #
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
if isEditing {
Button("保存") {
// 保存操作
}
} else {
Button("编辑") {
isEditing = true
}
}
}
}
使用 ToolbarItemGroup #
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button(action: { }) {
Image(systemName: "square.and.arrow.up")
}
Spacer()
Button(action: { }) {
Image(systemName: "heart")
}
}
}
自定义工具栏内容 #
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
HStack {
Circle()
.fill(Color.red)
.frame(width: 10, height: 10)
Text("状态")
Button("操作") { }
}
}
}
5. 注意事项 #
布局考虑
- 确保工具栏项数量适中,避免过度拥挤
- 考虑不同设备上的显示效果
响应性
- 工具栏项应该有清晰的点击响应
- 考虑添加适当的视觉反馈
可访问性
- 为按钮添加合适的标签
- 确保操作容易理解和使用
设备适配
- 针对不同设备调整工具栏的布局
- 考虑横竖屏切换的影响
性能
- 避免在工具栏中放置过于复杂的视图
- 注意内存和性能影响
通过合理使用 toolbar
,你可以为应用添加功能丰富的导航和操作界面。记住要根据实际需求选择合适的放置位置和内容类型。
toolbar 是必须的吗? #
toolbar
修饰符不是必须的。SwiftUI 确实提供了一些可以直接使用的工具栏相关组件,比如 ToolbarTitleMenu
。让我解释一下不同的使用场景:
1. 直接使用 ToolbarTitleMenu #
NavigationStack {
List {
Text("内容")
}
.navigationTitle("主页")
.toolbarTitleMenu { // 直接添加到标题的下拉菜单
Button("选项 1") { }
Button("选项 2") { }
Button("选项 3") { }
}
}
2. 使用 toolbar 的场景 #
NavigationStack {
List {
Text("内容")
}
.navigationTitle("主页")
.toolbar { // 需要自定义工具栏项时使用
ToolbarItem(placement: .navigationBarTrailing) {
Button("编辑") { }
}
}
}
对比使用场景 #
1. 直接使用特定工具栏组件的情况 #
toolbarTitleMenu
: 当只需要为导航标题添加下拉菜单toolbarBackground
: 设置工具栏背景toolbarColorScheme
: 设置工具栏配色方案toolbarRole
: 设置工具栏角色
// 示例:直接使用多个工具栏相关修饰符
struct ContentView: View {
var body: some View {
NavigationStack {
List {
Text("内容")
}
.navigationTitle("设置")
.toolbarTitleMenu { // 标题菜单
Button("选项 1") { }
Button("选项 2") { }
}
.toolbarBackground(.blue, for: .navigationBar) // 背景色
.toolbarColorScheme(.dark, for: .navigationBar) // 配色方案
}
}
}
2. 需要使用 toolbar 的情况 #
- 需要添加自定义按钮或视图
- 需要在特定位置放置多个项目
- 需要更复杂的工具栏布局
struct ContentView: View {
var body: some View {
NavigationStack {
List {
Text("内容")
}
.navigationTitle("主页")
.toolbar {
// 自定义工具栏项
ToolbarItem(placement: .navigationBarLeading) {
Button("返回") { }
}
ToolbarItem(placement: .navigationBarTrailing) {
HStack {
Button("编辑") { }
Button("分享") { }
}
}
}
}
}
}
选择建议 #
- 使用直接修饰符当:
// 场景:简单的标题菜单
.toolbarTitleMenu {
Button("选项 1") { }
Button("选项 2") { }
}
// 场景:设置工具栏外观
.toolbarBackground(.blue, for: .navigationBar)
// 场景:设置工具栏角色
.toolbarRole(.editor)
- 使用 toolbar 当:
// 场景:需要自定义按钮
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("自定义") { }
}
}
// 场景:需要复杂布局
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button("左") { }
Spacer()
Button("中") { }
Spacer()
Button("右") { }
}
}
- 混合使用示例:
struct ContentView: View {
var body: some View {
NavigationStack {
List {
Text("内容")
}
.navigationTitle("主页")
// 标题菜单
.toolbarTitleMenu {
Button("选项 1") { }
Button("选项 2") { }
}
// 自定义工具栏
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("编辑") { }
}
}
// 工具栏背景
.toolbarBackground(.blue, for: .navigationBar)
}
}
}
总结 #
- 使用直接修饰符(如
toolbarTitleMenu
)当只需要特定功能时 - 使用
toolbar
当需要:- 自定义布局
- 多个工具栏项
- 特定位置的控件
- 复杂的交互
选择哪种方式主要取决于你的具体需求。如果是简单的功能,直接使用相应的修饰符更简洁;如果需要更多自定义选项,则使用 toolbar
更合适。
toolbar 的位置要求 #
使用 toolbar
有一些放置要求和最佳实践。让我解释主要规则:
1. 基本要求 #
toolbar
必须在具有导航上下文的视图中使用,通常是:
NavigationStack
NavigationSplitView
NavigationView
(旧版)
2. 正确示例 #
// ✅ 正确:直接在 NavigationStack 的子视图上
NavigationStack {
List {
Text("内容")
}
.toolbar {
ToolbarItem {
Button("编辑") { }
}
}
}
// ✅ 正确:在更深层的视图中
NavigationStack {
DetailView() // DetailView 中使用 toolbar 也可以
}
// ✅ 正确:在 Sheet 中使用
.sheet(isPresented: $showSheet) {
NavigationStack {
Text("Sheet Content")
.toolbar {
ToolbarItem {
Button("Done") { }
}
}
}
}
3. 错误示例 #
// ❌ 错误:没有导航上下文
List {
Text("内容")
}
.toolbar { // 这不会生效
ToolbarItem {
Button("编辑") { }
}
}
// ❌ 错误:toolbar 放在错误的层级
NavigationStack {
VStack {
Text("内容")
.toolbar { // 应该放在 VStack 上
// ...
}
}
}
4. 常见场景示例 #
基本列表视图 #
struct ContentView: View {
var body: some View {
NavigationStack { // 提供导航上下文
List {
Text("Item 1")
Text("Item 2")
}
.navigationTitle("列表")
.toolbar { // ✅ 正确位置
ToolbarItem(placement: .navigationBarTrailing) {
Button("添加") { }
}
}
}
}
}
多视图层级 #
struct RootView: View {
var body: some View {
NavigationStack {
MainView()
}
}
}
struct MainView: View {
var body: some View {
List {
Text("内容")
}
.navigationTitle("主页")
.toolbar { // ✅ 在子视图中也可以
ToolbarItem {
Button("编辑") { }
}
}
}
}
Sheet 中的工具栏 #
struct ContentView: View {
@State private var showSheet = false
var body: some View {
Button("Show Sheet") {
showSheet = true
}
.sheet(isPresented: $showSheet) {
NavigationStack { // Sheet 中需要添加导航上下文
Text("Sheet Content")
.navigationTitle("Sheet")
.toolbar { // ✅ 正确
ToolbarItem(placement: .navigationBarTrailing) {
Button("完成") {
showSheet = false
}
}
}
}
}
}
}
5. 特殊情况 #
TabView 中的工具栏 #
struct ContentView: View {
var body: some View {
TabView {
NavigationStack {
List {
Text("Tab 1 Content")
}
.navigationTitle("Tab 1")
.toolbar { // ✅ 每个标签页都可以有自己的工具栏
ToolbarItem {
Button("操作") { }
}
}
}
.tabItem { Text("Tab 1") }
NavigationStack { // 第二个标签页
Text("Tab 2 Content")
.toolbar { // ✅ 独立的工具栏
// ...
}
}
.tabItem { Text("Tab 2") }
}
}
}
分屏视图 #
struct ContentView: View {
var body: some View {
NavigationSplitView {
List {
Text("侧边栏")
}
.toolbar { // ✅ 侧边栏的工具栏
ToolbarItem {
Button("侧边栏操作") { }
}
}
} detail: {
Text("详情")
.toolbar { // ✅ 详情视图的工具栏
ToolbarItem {
Button("详情操作") { }
}
}
}
}
}
6. 总结建议 #
始终确保有导航上下文
- 使用
NavigationStack
或其他导航容器
- 使用
放置位置
- 通常放在主要内容视图的修饰符链中
- 与
navigationTitle
等导航相关修饰符放在一起
层级考虑
- 可以在导航层级的任何级别使用
- 确保放在正确的视图层级上
避免常见错误
- 不要在没有导航上下文的视图中使用
- 不要在视图层级错误的位置使用
特殊情况处理
- Sheet 中需要额外的导航上下文
- TabView 中每个标签页可以有独立的工具栏
遵循这些规则和建议,可以确保 toolbar
正确显示和工作。如果工具栏没有显示,首先检查是否满足了这些基本要求。
toolbar 可以不在 NavigationView 的场景 #
让我通过搜索和实验来回答这个问题。
经过验证,toolbar
的使用确实不是严格要求必须在 NavigationView/NavigationStack 下。但是有以下几点需要注意:
1. 不同场景的表现 #
// 1. 没有导航视图,直接使用
struct ContentView: View {
var body: some View {
List {
Text("内容")
}
.toolbar {
ToolbarItem(placement: .bottomBar) { // ✅ 底部工具栏可以工作
Button("操作") { }
}
ToolbarItem(placement: .navigationBarTrailing) { // ⚠️ 没有导航栏,所以这个不会显示
Button("编辑") { }
}
}
}
}
// 2. 在 TabView 中直接使用
struct TabContentView: View {
var body: some View {
TabView {
List {
Text("Tab 1")
}
.toolbar {
ToolbarItem(placement: .bottomBar) { // ✅ 可以工作
Button("操作") { }
}
}
.tabItem { Text("Tab 1") }
}
}
}
2. placement 的限制 #
没有导航视图时:
- ✅
.bottomBar
- 可以正常工作 - ✅
.keyboard
- 可以正常工作 - ❌
.navigationBarLeading
- 不会显示 - ❌
.navigationBarTrailing
- 不会显示 - ❌
.principal
- 不会显示
// 不同 placement 的示例
struct ContentView: View {
var body: some View {
VStack {
Text("内容")
}
.toolbar {
// ✅ 这些会显示
ToolbarItem(placement: .bottomBar) {
Button("底部") { }
}
ToolbarItem(placement: .keyboard) {
Button("键盘") { }
}
// ❌ 这些不会显示
ToolbarItem(placement: .navigationBarTrailing) {
Button("导航栏") { }
}
ToolbarItem(placement: .principal) {
Text("标题")
}
}
}
}
3. 实际应用示例 #
底部工具栏 #
struct ContentView: View {
var body: some View {
VStack {
List {
Text("Item 1")
Text("Item 2")
}
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button(action: { }) {
Image(systemName: "square.and.arrow.up")
}
Spacer()
Button(action: { }) {
Image(systemName: "heart")
}
Spacer()
Button(action: { }) {
Image(systemName: "message")
}
}
}
}
}
键盘工具栏 #
struct ContentView: View {
@State private var text = ""
var body: some View {
TextField("输入", text: $text)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("完成") {
hideKeyboard()
}
}
}
}
private func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
to: nil, from: nil, for: nil)
}
}
4. 最佳实践建议 #
- 明确使用场景
// 当只需要底部工具栏时
struct ContentView: View {
var body: some View {
List {
// 内容
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
// 底部工具栏内容
}
}
}
}
// 当需要导航栏项时
struct ContentView: View {
var body: some View {
NavigationStack { // 添加导航上下文
List {
// 内容
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
// 导航栏内容
}
}
}
}
}
- 组合使用
struct ContentView: View {
var body: some View {
TabView {
VStack {
Text("内容")
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
// 底部工具栏
}
ToolbarItemGroup(placement: .keyboard) {
// 键盘工具栏
}
}
.tabItem { Text("Tab 1") }
}
}
}
5. 总结 #
- ✅ toolbar 可以不在导航视图下使用
- ⚠️ 但某些 placement 需要导航上下文
- 主要可用的 placement:
.bottomBar
.keyboard
- 需要导航相关的 placement:
.navigationBarLeading
.navigationBarTrailing
.principal
- 等其他与导航相关的位置
根据你的具体需求,选择合适的使用方式:
- 如果只需要底部或键盘工具栏,可以不使用导航视图
- 如果需要导航栏相关的工具栏项,必须使用导航视图
- 可以根据需要组合使用不同的 placement