navigationBar (舍弃)


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()
}

自定义返回按钮

https://www.pixeldock.com/blog/enable-the-swipe-back-gesture-aka-interactive-pop-gesture-when-using-a-uinavigationcontroller-with-custom-back-button/

1. 替换默认返回按钮

//【起始页面设置】
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("返回")
										}
                }
            }
       }
	}

}

2. 扩展 UINavigationController 实现侧滑返回

当隐藏默认的返回按钮后,会有一个问题,就是系统的屏幕右滑返回上一级的这个手势特性没有了,特别得不偿失。这时需要添加以下扩展:

extension UINavigationController: UIGestureRecognizerDelegate {

    override open func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }

    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return viewControllers.count > 1
    }

}

自定义 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 将使用其包含的按钮的标签。