navigationBar (舍弃)

在过去 SwiftUI 版本中,可以通过 navigationBarNavigationBarItems 修饰符去设置导航栏。但后面已被 toolBar 代替。

<aside> 💡 实际上,navigationBarItems 只适用于 iOS 和 iPadOS。这意味着只能在移动设备上使用,而不能在macOS上使用。 而 toolbar 是iOS 14 引入的新修饰符,它允许我们向【导航栏、底部栏、键盘上方以及模态视图的工具栏…】中添加按钮或其他视图。与navigationBarItems不同,toolbar 在 iOS 和 macOS 上都可以使用,这使得创建通用视图变得更加容易。

</aside>

NavigationStack {

	List { ... }

	// 在导航栏上添加一些按钮
	.navigationBarItems(        
		// 添加前面的按钮        
		leading:        
			Button(action: {
		      self.presentationMode.wrappedValue.dismiss()
	    }, label: {
		      Text("取消")
	    }),
		// 添加后面的按钮(注意前面有个逗号)
		trailing:        
			HStack{
          Button( action: {}, label:{ } )
          Button( action: {}, label:{ Image(systemName: "gear") } )
      } 
	)
}

toolbar

toolbar() 修饰符允许我们将栏按钮项目放置在顶部或底部空间的任何位置,但前提是视图已经嵌入在 NavigationStack 中;toolbar() 工具栏会根据页面的情况,选择其指代的是 navigationBar, tabBar, toolbar 中的哪一种。

样式设置

设置 toolbar 的样式,只需在 NavigationStack 内的根节点(也可以理解成二级视图的最后)添加以下修饰符。

// 例如以下代码可以在推送到下一级页面时隐藏toolbar
TabView {
    NavigationStack {
        NavigationLink("Tap Me") {
            SecondView("Detail View")
                .toolbar(.hidden, for: .tabBar)
        }
        .navigationTitle("Primary View")
    }
}

// 甚至可以随时更改传入 toolbar 的值,实现切换导航栏的效果
struct DetailView: View {
    @State private var showNavigationBar = true
    var body: some View {
        Text("Detail View")
            .toolbar(showNavigationBar ? .visible : .hidden)
            .onTapGesture { withAnimation { showNavigationBar.toggle() } }
    }
}

常用的修饰符有以下这些:

隐藏 .toolbar(.hidden, for: .tabBar) 如果没有指定 for 参数,没明确要隐藏的确切的工具栏,则该隐藏请求将向上找到最近的容器,通常会将导致导航栏被隐藏,因为这是最近的容器
隐藏背景色 .toolbarBackground(.hidden) 采用这种方法,请确保主要内容和工具栏内容重叠时不会发生冲突
设置背景 .toolbarBackground(LinearGradient(),for: .navigationBar) 设置背景为渐变视图或图片等
设置背景色 .toolbarBackground(.orange, for: .navigationBar, .tabBar) 这里 for 参数有两个值,代表会同时为标签栏和导航栏着色
注意:这里背景的上色是在系统认为有必要时才使用,而不是始终使用;即 toolbar 最初还是默认颜色,当列表滚动一定距离时,才会更改颜色
设置其字体颜色 .toolbarColorScheme(.light, for: .navigationBar) 同上,该设置默认不生效,只有滚动一定距离时,才生效
强制背景持续生效 .toolbarBackground(.visible, for: .navigationBar) 让 toolbar 样式在加载时立即生效,而不依赖滚动(可解决上面问题)
修改返回按钮颜色 NavigationStack { … }.tint(.black) iOS 16 及以上版本适用该方法,注意要放到 NavigationStack 视图后面
隐藏默认返回按钮 .navigationBarBackButtonHidden(true) 但是使用这个会导致手机默认的手势右滑返回失效,体验不好

ToolbarItem

如果要将按钮放置在工具栏中,请使用 toolbar() 创建一个 ToolbarItem 对象,然后设置参数 placement 来决定放置的位置

// 例如:放置在底部工具栏 .bottomBar
NavigationStack {
    Text("Hello, World!")
        .navigationTitle("SwiftUI")
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                Button("Press Me") {
                    print("Pressed")
                }
            }
        }
}

// 例如放置在顶部工具栏的最前面(左侧) .topBarLeading
NavigationStack {
    Text("")
    .toolbar {
        ToolbarItem(placement: .topBarLeading) {
            Button("Tap Me") {
                // button action here
            }
        }
    }
}

ToolbarItemGroup

如果要获得多个按钮,请使用 ToolbarItemGroup 而不是简单的 ToolbarItem ,然后在其中放置多个按钮:

NavigationStack {
    Text("Hello, World!").padding()
        .navigationTitle("SwiftUI")
        .toolbar {
            ToolbarItemGroup(placement: .bottomBar) {
                Button("First") {
                    print("Pressed")
                }
                Button("Second") {
                    print("Pressed")
                }
            }
        }
}

// 如果想分隔 ToolbarItemGroup 内的按钮,请在任意位置添加 Spacer() 间隔视图

Placement 位置参数

我们可以为 toolbar 中的元素设置为以下一些类型:

.topBarLeading 左上方
topBarTrainling 右上方
.bottomBar
.principal 专门用于设置导航栏中间【**主标题】**的位置
.primaryAction 根据平台认为最重要的按钮的位置来定位按钮
.secondaryAction 用于不需要频繁操作的按钮,在 iOS 这样的按钮将折叠为单个详细信息的按钮
.confirmationAction 当您希望用户同意某件事时,例如同意服务条款
.destructiveAction 当用户需要选择销毁正在使用的任何内容时,例如确认他们想要删除他们创建的一些数据
.cancellationAction 当用户需要撤销他们所做的更改时,例如放弃他们所做的更改
.navigation 用于使用户在数据之间移动的按钮,例如在 Web 浏览器中后退和前进

语义布局:具有特定含义的放置,而不是仅仅依赖于它们的位置。语义布局有两个好处:

//例如:下面的按钮用作【确认】按钮
.toolbar {
		ToolbarItem(placement: .confirmationAction) {
			Button("Tap Me") { ... }
		}
}

EditButton 编辑模式

.toolbar {
		EditButton()
}

自定义返回按钮

起始页面设置:

struct Navigation_BackButtonHidden: View {
	var body: some View {
		NavigationView {   
			ZStack {             
				VStack(spacing: 25) {                    
					......
					//使用 navigationLink 导航到另外一个视图,destination 参数后面就是目标页面的名称
					NavigationLink("Go To Detail”, destination: BackButtonHiddenDetail())            
				}                          
			}
			//设置标题栏
			.navigationBarTitle(Text("Navigation Views"))   
		}    
	}
}

目标页面设置:自定义返回按钮主要在此设置

struct BackButtonHiddenDetail: View {

	// 1.在目标视图里声明环境参数来取得环境值,才能支持返回到上一级的起点页面
	@Environment(\\.dismiss) var dismiss

	var body: some View {
			ZStack {           
				VStack(spacing: 25) {                    
					......
					//使用 Button 返回到主视图
					Button("Go Back") {
						// 2.解除/忽略(dismiss)正在呈现的内容,即可回到之前的视图
						self.presentationMode.wrappedValue.dismiss()
					}       
				}                          
			}
			// 3.隐藏标题栏的默认返回按钮,因为上面已经自己加了一个返回按钮
			.navigationBarBackButtonHidden(true)
			
			// 4.上面是通过一个自定义 Button 返回,也可以在工具栏自定义返回按钮:
			.toolbar {
            ToolbarItem(placement: .navigationBarLeading) {
                HStack {
                    Button(action: {
                        dismiss()  // 返回到上一个页面
                    }) {
                        Image(systemName: "chevron.left")
                        Text("返回")
                    }
                    .foregroundColor(.white)
                }
            }
       }

	}
}

自定义 toolbar 项目

注意:仅部分平台支持工具栏自定义。此 API 最适合在 iPadOS 和 macOS 上使用,因为这些地方复杂的工具栏更为常见

SwiftUI 的工具栏允许用户自定义任何工具栏项目,只需要五个小步骤:

  1. 为【工具栏】提供一个唯一、稳定的字符串标识符
  2. 为每个可自定义的【工具栏项目】,提供一个唯一的、稳定的字符串标识符
  3. 将自定义的按钮放置在 .secondaryAction 类别中
  4. 决定哪些按钮默认可见。
  5. 为工具栏启用【编辑器模式】,以便所有 secondary actions 都成为工具栏按钮

<aside> 💡 “唯一、稳定”的标识符要求很重要,因为这是 SwiftUI 用来记住用户设置的方式:“工具栏 X 有按钮 A,然后是 C,然后是 F ”

</aside>

NavigationStack {
    Text("SwiftUI")
        .navigationTitle("Welcome")
        .toolbar(id: "options") {
            // this is a primary action, so will always be visible
            ToolbarItem(id: "settings", placement: .primaryAction) {
                Button("Settings") { }
            }

            // this is a standard secondary action, so will be customizable
            ToolbarItem(id: "help", placement: .secondaryAction) {
                Button("Help") { }
            }

            // another customizable button
            ToolbarItem(id: "email", placement: .secondaryAction) {
                Button("Email Me") { }
            }

            // a third customizable button, but this one won't be in the toolbar by default
            ToolbarItem(id: "credits", placement: .secondaryAction, showsByDefault: false) {
                Button("Credits") { }
            }
        }
        .toolbarRole(.editor)
}

// 当您运行该代码时,iOS 将在工具栏的后缘看到一个详细信息按钮,点击该按钮将显示一个可进行自定义的“自定义工具栏”菜单

默认情况下,这将使所有 secondaryAction 按钮单独可自定义,但如果您将两个或多个按钮包装在 ControlGroup 中,它们将出于自定义目的而附加(用户必须添加两个或都不添加)。 ControlGroup 非常适合字体调整之类的事情,如下所示:

NavigationStack {
    Text("SwiftUI")
        .navigationTitle("Welcome")
        .toolbar(id: "font") {
            ToolbarItem(id: "font", placement: .secondaryAction) {
                ControlGroup {
                    Button {
                        // decrease font
                    } label: {
                        Label("Decrease font size", systemImage: "textformat.size.smaller")
                    }

                    Button {
                        // increase font
                    } label: {
                        Label("Increase font size", systemImage: "textformat.size.larger")
                    }
                } label: {
                    Label("Font Size", systemImage: "textformat.size")
                }
            }
        }
        .toolbarRole(.editor)
}

// 如果您没有为 ControlGroup 添加标签,SwiftUI 将使用其包含的按钮的标签。