一、NavigationView ( iOS 13.0–18.4Deprecated)
一种用于展示代表导航层次结构中可见路径的视图堆栈的视图。
使用 init(content:)
初始化器可以创建一个导航视图,该初始化器能直接将导航链接(NavigationLink)与其目标视图关联起来
struct ContentView: View {
var body: some View {
NavigationView{
VStack(spacing: 20){
NavigationLink(
destination: DetailView1(), // 目标视图1
label: { Text("通用") } // // 链接标签
)
NavigationLink(
destination: DetailView2(), // 目标视图2
label: { Text("蜂窝网络") }// 链接标签
)
NavigationLink(
destination: DetailView3(), // 目标视图3
label: { Text("开发者") }// 链接标签
)
}
}
.navigationTitle("设置")
}
}
- NavigationLink是控制导航展示的视图,是一个可交互的视图(类似按钮),当其和目标视图关联起来,点击或轻触NavigationLink将会跳转到目标视图
- NavigationLink需要在NavigationView 等导航容器视图中才会起作用。
二、NavigationStack (iOS 16.0+)
一个显示根视图,并允许你在根视图之上呈现其他附加视图的视图。
用导航栈在根视图上呈现多层级视图。用户通过点击 NavigationLink 向栈顶添加视图,并通过系统内置的返回控件(如返回按钮或滑动手势)移除视图。导航栈始终显示最近添加且未被移除的视图,且根视图不可被移除。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack{
VStack(spacing: 20){
NavigationLink(
destination: DetailView1(), // 目标视图1
label: { Text("通用") } // // 链接标签
)
NavigationLink(
destination: DetailView2(), // 目标视图2
label: { Text("蜂窝网络") }// 链接标签
)
NavigationLink(
destination: DetailView3(), // 目标视图3
label: { Text("开发者") }// 链接标签
)
}
}
.navigationTitle("设置")
}
}
- 基本使用和NavigationView 一样,都需要通过NavigationLink在容器内进行关联目标视图,实现点击跳转。
- NavigationView在iOS18.4时已经被官方弃用,不再推荐使用;
(官方文档:如果您的app最低支持 iOS 16及以上系统版本,请停止使用 NavigationView,并迁移至 NavigationStack 或 NavigationSplitView 作为替代方案。)
struct ViewA: View {
var body: some View {
NavigationStack{
VStack(spacing: 20){
NavigationLink(
destination: ViewB(), // 目标视图1
label: { Text("A") } // // 链接标签
)
}
}
.navigationTitle("设置")
}
}
struct ViewB: View {
var body: some View {
NavigationLink(
destination: ViewC(), // 目标视图1
label: { Text("B") } // // 链接标签
)
}
}
struct ViewC: View {
var body: some View {
NavigationLink(
destination: DetailView3(), // 目标视图1
label: { Text("C") } // // 链接标签
)
}
}
2.1 类型安全路由
NavigationStack 支持通过 任意数据类型 关联目标视图通过添加navigationDestination(for: )实现多个路由的跳转
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack(){
VStack(spacing: 20){
NavigationLink(value: "0") {
Text("通用")
}
NavigationLink(value: "1") {
Text("蜂鸟网络")
}
NavigationLink(value: "2") {
Text("通用")
}
}
.navigationDestination(for: String.self, destination: { a in
switch a {
case "0":
DetailView1()
case "1":
DetailView2()
case "2":
DetailView3()
default:
DetailView3()
}
})
.navigationTitle("设置")
}
}
}
将视图与数据分离可简化程序化导航的实现,因为您只需记录当前展示的数据即可管理导航状态。
2.2 通过代码控制导航链接的呈现
实现编程式导航,需引入一个用于跟踪导航栈状态的状态变量。例如,您可以创建一个颜色数组来存储前例中的栈状态,并将其初始化为空数组以表示起始空栈。
@State private var colors: [Color] = []
随后将该状态变量的 Binding 传递给 NavigationStack
NavigationStack(path: $colors) {
// ...
}
您可以通过该数组观察导航栈的当前状态,也可以通过修改数组来改变栈内容。例如:通过代码向数组添加 '.green' 元素,系统将自动导航至新的颜色详情视图(如下述方法所示)
import SwiftUI
struct ContentView: View {
@State private var colors: [Color] = []
var body: some View {
NavigationStack(path: $colors){
VStack(spacing: 20){
Button {
self.colors.append(.green)
} label: {
Text("点击")
}
}
.navigationDestination(for: Color.self, destination: { a in
switch a {
case Color.red:
DetailView1()
case Color.green:
DetailView2()
case Color.blue:
DetailView3()
default:
DetailView3()
}
})
.navigationTitle("设置")
}
}
}
三、NavigationSplitView(iOS 16.0+)
一种支持双栏或三栏布局的视图容器,其中前导列中的选择会控制后续列的呈现内容
struct NavigationSplitView<Sidebar, Content, Detail> where Sidebar : View ,Content : View, Detail : View
您可创建双栏或三栏的导航分栏视图(NavigationSplitView),通常将其作为场景(Scene)的根视图。用户在前导列中选择一个或多个项目后,相关内容将展示在后续分栏中。
要创建双栏导航分栏视图,请使用 init(sidebar:detail:) 初始化方法
@State private var employeeIds: Set<Employee.ID> = []
var body: some View {
NavigationSplitView {
List(model.employees, selection: $employeeIds) { employee in
Text(employee.name)
}
} detail: {
EmployeeDetails(for: employeeIds)
}
}
在上述示例中,导航分栏视图会与其首列中的 List 协同工作:当用户做出选择时,详情视图将自动更新。您对选择属性(selection property)的编程式修改,同样会同步影响列表外观和呈现的详情视图。
要创建三栏视图,请使用 init(sidebar:content:detail:) 初始化方法。第一列的选择将影响第二列内容,第二列的选择则影响第三列。例如,您可以展示部门列表 → 所选部门的员工列表 → 所选员工的详细信息
@State private var departmentId: Department.ID? // Single selection.
@State private var employeeIds: Set<Employee.ID> = [] // Multiple selection.
var body: some View {
NavigationSplitView {
List(model.departments, selection: $departmentId) { department in
Text(department.name)
}
} content: {
if let department = model.department(id: departmentId) {
List(department.employees, selection: $employeeIds) { employee in
Text(employee.name)
}
} else {
Text("Select a department")
}
} detail: {
EmployeeDetails(for: employeeIds)
}
}
四、移至新型导航组件
通过将旧版导航视图(NavigationView)替换为导航栈(NavigationStack)和导航分栏视图(NavigationSplitView),优化应用的导航行为。
4.1、单列导航更新
如果您的应用使用了通过 .navigationViewStyle(.stack) 设置为堆栈样式的 NavigationView(用户通过将新视图推入堆栈进行导航),请改用 NavigationStack。
特别需要注意的是,请停止使用以下方式:
NavigationView { // This is deprecated.
/* content */
}
.navigationViewStyle(.stack)
应改为创建导航栈
NavigationStack {
/* content */
}
4.2、多列导航更新
如果您的应用使用了双栏或三栏的 NavigationView,或者应用在某些情况下需要多栏布局而在其他情况下需要单栏布局(这在同时支持 iPhone 和 iPad 的应用中很常见),请改用 NavigationSplitView。
替代使用双栏导航视图
NavigationView { // This is deprecated.
/* column 1 */
/* column 2 */
}
使用 init(sidebar:detail:)
初始化器创建一个具有明确侧边栏和详细内容的分屏导航:
NavigationSplitView {
/* column 1 */
} detail: {
/* column 2 */
}
同样,替代使用之前三栏式导航视图的方式:
NavigationView { // This is deprecated.
/* column 1 */
/* column 2 */
/* column 3 */
}
使用 init(sidebar:content:detail:) 初始化方法创建具有明确侧边栏、内容区和详情组件的分屏导航。
NavigationSplitView {
/* column 1 */
} content: {
/* column 2 */
} detail: {
/* column 3 */
}
4.3、更新编程式导航实现方式
若当前使用带 isActive 参数的 NavigationLink 初始化方法实现编程式导航,请将导航逻辑迁移至父级堆栈。具体方式为:将导航链接改为使用 init(value:label:) 初始化方法,采用接收 path 参数的导航堆栈初始化方法(如 init(path:root:))
例如,若您当前拥有一个导航视图,其中的链接会响应独立状态变量而激活:
@State private var isShowingPurple = false
@State private var isShowingPink = false
@State private var isShowingOrange = false
var body: some View {
NavigationView { // This is deprecated.
List {
NavigationLink("Purple", isActive: $isShowingPurple) {
ColorDetail(color: .purple)
}
NavigationLink("Pink", isActive: $isShowingPink) {
ColorDetail(color: .pink)
}
NavigationLink("Orange", isActive: $isShowingOrange) {
ColorDetail(color: .orange)
}
}
}
.navigationViewStyle(.stack)
}
当代码的其他部分将某个状态变量设置为 true 时,具有匹配标记的导航链接会响应激活。”
将其重写为接收 path 输入的导航堆栈:
@State private var path: [Color] = [] // Nothing on the stack by default.
var body: some View {
NavigationStack(path: $path) {
List {
NavigationLink("Purple", value: .purple)
NavigationLink("Pink", value: .pink)
NavigationLink("Orange", value: .orange)
}
.navigationDestination(for: Color.self) { color in
ColorDetail(color: color)
}
}
}
此版本使用 navigationDestination(for:destination:) 视图修饰符,将呈现的数据与对应视图解耦。这使得路径数组(path array)能够完整表示导航堆栈中的每个视图。您对数组的修改会:
- 即时影响容器当前显示的视图
- 决定用户在导航堆栈中的浏览路径
若采用 NavigationPath 代替普通数据集合存储路径信息,还可支持更复杂的程序化导航场景。详见 NavigationStack 文档。
let colors: [Color] = [.purple, .pink, .orange]
@State private var selection: Color? = nil // Nothing selected by default.
var body: some View {
NavigationView { // This is deprecated.
List {
ForEach(colors, id: \.self) { color in
NavigationLink(color.description, tag: color, selection: $selection) {
ColorDetail(color: color)
}
}
}
Text("Pick a color")
}
}
4.4、更新基于选择器的导航实现
若您在 List 元素上使用带 selection 参数的 NavigationLink 初始化方法执行程序化导航,可将选择逻辑迁移至列表本身。
例如:假设您拥有一个导航视图,其中的链接会响应选择状态变量而激活:
@State private var isShowingPurple = false
@State private var isShowingPink = false
@State private var isShowingOrange = false
var body: some View {
NavigationView { // This is deprecated.
List {
NavigationLink("Purple", isActive: $isShowingPurple) {
ColorDetail(color: .purple)
}
NavigationLink("Pink", isActive: $isShowingPink) {
ColorDetail(color: .pink)
}
NavigationLink("Orange", isActive: $isShowingOrange) {
ColorDetail(color: .orange)
}
}
}
.navigationViewStyle(.stack)
}
使用相同的属性,您可以将 body 重写为
var body: some View {
NavigationSplitView {
List(colors, id: \.self, selection: $selection) { color in
NavigationLink(color.description, value: color)
}
} detail: {
if let color = selection {
ColorDetail(color: color)
} else {
Text("Pick a color")
}
}
}
列表与导航逻辑协同工作:当您在代码的其他部分修改选择状态变量时,会激活具有对应标记(color)的导航链接。同理,若用户选择关联特定标记的导航链接,列表会更新该选择值,使其他代码可读取此状态。