SwiftUI学习之导航栏

一、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 作为替代方案。)
deepseek_mermaid_20250718_f5dcaa.png
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") } // // 链接标签
            
        )
    }
}
deepseek_mermaid_20250718_a57306.png

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)能够完整表示导航堆栈中的每个视图。您对数组的修改会:

  1. 即时影响容器当前显示的视图
  2. 决定用户在导航堆栈中的浏览路径
    若采用 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)的导航链接。同理,若用户选择关联特定标记的导航链接,列表会更新该选择值,使其他代码可读取此状态。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容