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) | 但是使用这个会导致手机默认的手势右滑返回失效,体验不好 |
如果要将按钮放置在工具栏中,请使用 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
而不是简单的 ToolbarItem
,然后在其中放置多个按钮:
NavigationStack {
Text("Hello, World!").padding()
.navigationTitle("SwiftUI")
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button("First") {
print("Pressed")
}
Button("Second") {
print("Pressed")
}
}
}
}
// 如果想分隔 ToolbarItemGroup 内的按钮,请在任意位置添加 Spacer() 间隔视图
我们可以为 toolbar
中的元素设置为以下一些类型:
.topBarLeading | 左上方 |
topBarTrainling | 右上方 |
.bottomBar | |
.principal | 专门用于设置导航栏中间【**主标题】**的位置 |
.primaryAction | 根据平台认为最重要的按钮的位置来定位按钮 |
.secondaryAction | 用于不需要频繁操作的按钮,在 iOS 这样的按钮将折叠为单个详细信息的按钮 |
.confirmationAction | 当您希望用户同意某件事时,例如同意服务条款 |
.destructiveAction | 当用户需要选择销毁正在使用的任何内容时,例如确认他们想要删除他们创建的一些数据 |
.cancellationAction | 当用户需要撤销他们所做的更改时,例如放弃他们所做的更改 |
.navigation | 用于使用户在数据之间移动的按钮,例如在 Web 浏览器中后退和前进 |
语义布局:具有特定含义的放置,而不是仅仅依赖于它们的位置。语义布局有两个好处:
//例如:下面的按钮用作【确认】按钮
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Tap Me") { ... }
}
}
List
视图,一并使用.toolbar {
EditButton()
}
//【起始页面设置】
NavigationStack {
//使用 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 {
// 2.可以自己添加按钮解除(dismiss)当前视图,即回到之前视图
Button("Go Back") {
dismiss()
}
}
// 3.隐藏标题栏的默认返回按钮,因为上面已经自己加了返回按钮
.navigationBarBackButtonHidden(true)
// 4.在工具栏自定义返回按钮,替换系统原有的返回按钮:
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
HStack {
Button {
dismiss()
} label: {
Image(systemName: "chevron.left")
Text("返回")
}
}
}
}
}
}
当隐藏默认的返回按钮后,会有一个问题,就是系统的屏幕右滑返回上一级的这个手势特性没有了,特别得不偿失。这时需要添加以下扩展:
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
注意:仅部分平台支持工具栏自定义。此 API 最适合在 iPadOS 和 macOS 上使用,因为这些地方复杂的工具栏更为常见
SwiftUI 的工具栏允许用户自定义任何工具栏项目,只需要五个小步骤:
.secondaryAction
类别中<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 将使用其包含的按钮的标签。