SwiftUI NavigatorStack 导航容器
NavigationStack 是一个用状态驱动、类型安全的声明式导航容器,它通过管理视图堆栈和导航路径来实现 SwiftUI 应用中的页面导航(专注于单栏场景)
NavigationStack 需要 iOS 16.0+以上版本支持。
核心要素
NavigationStack (导航容器)
│
├── 管理 NavigationPath (状态存储)
│
├── 包含 navigationDestination (路由配置)
│ │
│ └── 判断数据类型 → 映射对应的视图
│
└── 视图层(导航的起点)
NavigationPath 是导航路径容器,用于管理 NavigationStack 的导航状态和历史记录
navigationDestination 是视图修饰符,用于定义数据类型到目标视图的映射关系,相当于导航系统的路由表
基本用法
1、简单页面跳转
可NavigationStack配合NavigationLink实现(不需要使用NavigationPath记录、管理路径)
struct ContentView: View {
var body: some View {
NavigationStack {
List {
NavigationLink("前往详情页", value: "详情内容")
NavigationLink("设置", value: "设置页面")
}
.navigationDestination(for: String.self) { value in
DetailView(content: value)
}
}
}
}
struct DetailView: View {
let content: String
var body: some View {
Text("详情: \(content)")
.navigationTitle("详情页")
}
}
2、简单页面跳转:多类型路由映射
很多时候,navigationDestination映射的value类型并非只有一种:
struct Test: View {
var body: some View {
NavigationStack {
List {
// 使用 value 参数 - 必须遵循 Hashable(swift值类型数据默认遵循Hashable协议)
NavigationLink("使用 value", value: "字符串值")
NavigationLink("使用数字", value: 42)
}
.navigationDestination(for: String.self) { value in
Text("字符串值: \(value)")
}
.navigationDestination(for: Int.self) { value in
Text("整数值: \(value)")
}
}
}
}
3、简单页面跳转:多类型路由映射2
也可使用枚举管理多种数据类型:
//注意value类型,需要遵循Hashable
enum Route: Hashable {
case product(Int)
case profile(String)
case settings
}
struct MultiTypeNavigationView: View {
var body: some View {
NavigationStack {
VStack(spacing: 20) {
NavigationLink("产品详情", value: Route.product(123))
NavigationLink("用户资料", value: Route.profile("张三"))
NavigationLink("设置", value: Route.settings)
}
.navigationDestination(for: Route.self) { route in
switch route {
case .product(let id):
ProductDetailView(productId: id)
case .profile(let username):
ProfileView(username: username)
case .settings:
SettingsView()
}
}
}
}
}
4、多层页面跳转
可使用NavigationStack、NavigationPath实现
NavigationPath提供了以下方法用于管理路径:
append() : 跳转到新页面
removeLast(): 返回上一页
removeLast(n): 返回前n页
removeAll(): 返回首页
count: 显示当前导航深度
Codable: 实现状态持久化和深度链接
import SwiftUI
// 定义路由枚举
enum Route: Hashable {
case detail(String)
case settings
case profile(Int)
}
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List {
Button("跳转到详情页") {
path.append(Route.detail("Hello World"))
}
Button("跳转到设置") {
path.append(Route.settings)
}
Button("跳转到用户资料") {
path.append(Route.profile(123))
}
Button("多层级跳转") {
path.append(Route.detail("第一层"))
path.append(Route.settings)
path.append(Route.profile(456))
}
}
.navigationTitle("首页")
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let text):
DetailView(text: text, path: $path)
case .settings:
SettingsView(path: $path)
case .profile(let userId):
ProfileView(userId: userId, path: $path)
}
}
}
}
}
struct DetailView: View {
let text: String
@Binding var path: NavigationPath
var body: some View {
VStack {
Text("详情页: \(text)")
.font(.title)
Button("前往下一层") {
path.append(Route.detail("从详情页跳转"))
}
Button("返回首页") {
path.removeLast(path.count)
}
Button("返回上一层") {
path.removeLast()
}
}
}
}
struct SettingsView: View {
@Binding var path: NavigationPath
var body: some View {
VStack {
Text("设置页面")
.font(.title)
Button("返回") {
path.removeLast()
}
}
}
}
struct ProfileView: View {
let userId: Int
@Binding var path: NavigationPath
var body: some View {
VStack {
Text("用户资料: \(userId)")
.font(.title)
Button("跳转到详情") {
path.append(Route.detail("来自用户资料"))
}
}
}
}
5、Hashable 的作用
导航必需:NavigationLink 的 value 参数必须遵循 Hashable
路径管理:NavigationPath 依赖 Hashable 来跟踪导航状态
唯一性判断:用于比较两个实例是否代表相同的导航目标
5.1、NavigationLink 的 Hashable 要求
struct NavigationLinkRequirement: View {
var body: some View {
NavigationStack {
List {
// 形式1:使用 value 参数 - 必须遵循 Hashable
NavigationLink("使用 value", value: "字符串值")
NavigationLink("使用数字", value: 42)
// 这会导致编译错误,因为 MyData 不遵循 Hashable
// NavigationLink("无效", value: MyData())
// 形式2:使用 destination 参数 - 不需要 Hashable
NavigationLink("使用 destination") {
MyCustomView()
}
// 传统形式 - 不需要 Hashable
NavigationLink("传统形式", destination: Text("目标视图"))
}
.navigationDestination(for: String.self) { value in
Text("字符串值: \(value)")
}
.navigationDestination(for: Int.self) { value in
Text("整数值: \(value)")
}
}
}
}
// 不遵循 Hashable 的类型
struct MyData {
let content: String
}
struct MyCustomView: View {
var body: some View {
Text("自定义视图")
}
}
5.2、NavigationPath 的 Hashable 要求
struct NavigationPathExample: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack(spacing: 20) {
Text("NavigationPath 演示")
.font(.title)
Button("添加字符串") {
// 字符串遵循 Hashable
path.append("新页面")
}
Button("添加整数") {
// 整数遵循 Hashable
path.append(100)
}
Button("添加自定义类型") {
// 只要遵循 Hashable 就可以
path.append(MyHashableData(name: "测试", id: 1))
}
// 这会导致运行时错误
Button("添加非 Hashable 类型(会崩溃)") {
// path.append(MyData()) // 取消注释会崩溃
}
Button("查看路径深度: \(path.count)") {
print("当前路径深度: \(path.count)")
}
Button("返回") {
if !path.isEmpty {
path.removeLast()
}
}
Button("返回根视图") {
path = NavigationPath() // 重置路径
}
}
.navigationDestination(for: String.self) { value in
Text("字符串页面: \(value)")
}
.navigationDestination(for: Int.self) { value in
Text("整数页面: \(value)")
}
.navigationDestination(for: MyHashableData.self) { data in
Text("自定义数据: \(data.name) - \(data.id)")
}
}
}
}
// 遵循 Hashable 的自定义类型
struct MyHashableData: Hashable {
let name: String
let id: Int
}
6、导航栏UI自定义
NavigationStack 导航结构图:
NavigationStack (容器层)
│
├── ️ NavigationBar (导航栏层)
│ │
│ ├── ️ [toolbar: .topBarLeading]
│ │ ← 返回按钮 / 菜单按钮
│ │
│ ├── ️ [navigationTitle]
│ │ ← 页面标题 (居中显示)
│ │
│ └── ️ [toolbar: .topBarTrailing]
│ ← 编辑按钮 / 更多操作
│
└── Content (内容层)
← 你的主要视图内容
← 可以独立滚动
NavigationStack { // ← 根容器
ContentView() // ← 内容层
.navigationTitle("标题") // ← ️ 中央标题
.toolbar {
ToolbarItem(placement: .topBarLeading) { // ← ️ 左侧
Button("返回") { ... }
}
ToolbarItem(placement: .topBarTrailing) { // ← ️ 右侧
Button("编辑") { ... }
}
}
}
自定义导航栏外观,参考案例:
struct CustomNavigationView: View {
var body: some View {
NavigationStack {
List {
NavigationLink("标准页面", value: "standard")
NavigationLink("自定义标题页面", value: "custom")
NavigationLink("隐藏导航栏页面", value: "hidden")
}
.navigationDestination(for: String.self) { value in
switch value {
case "standard":
StandardView()
case "custom":
CustomTitleView()
case "hidden":
HiddenNavBarView()
default:
Text("未知页面")
}
}
.navigationTitle("导航演示")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("设置") {
print("设置按钮点击")
}
}
}
}
.tint(.purple) // 统一色调
}
}
struct StandardView: View {
var body: some View {
Text("标准页面")
.navigationTitle("标准标题")
.navigationBarTitleDisplayMode(.automatic)
}
}
struct CustomTitleView: View {
var body: some View {
VStack {
Text("自定义标题页面")
// 自定义标题视图
Color.blue
.frame(height: 200)
.overlay(
Text("大标题")
.font(.largeTitle)
.foregroundColor(.white)
)
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
VStack {
Text("自定义标题")
.font(.headline)
Text("副标题")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
}
}
struct HiddenNavBarView: View {
var body: some View {
Text("隐藏导航栏页面")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
.toolbar(.hidden, for: .navigationBar)
}
}
SwiftUI NavigatorStack 导航容器的更多相关文章
- “四核”驱动的“三维”导航 -- 淘宝新UI(需求分析篇)
前言 孔子说:"软件是对客观世界的抽象". 首先声明,这里的"三维导航"和地图没一毛钱关系,"四核驱动"和硬件也没关系,而是为了复杂的应用而 ...
- MMDrawerController第三方库的使用(根据导航item+滚动条progressView实现的手势滑动切换视图的)
https://github.com/mutualmobile/MMDrawerController MMDrawerControlleris边抽屉导航容器视图控制器用来支持越来越多的应用程序利用抽屉 ...
- 浅谈WPF页间导航
浅谈WPF页间导航 使用导航的目的是从一个页面进入到另一个页面.无论是预先决定的线性顺序(向导)还是基于层次的用户驱动程序(大部分网站的形式),或者动态生成的路径,主要有3种方法实现:调用Naviga ...
- 用CSS变形创建圆形导航
http://www.w3cplus.com/css3/building-a-circular-navigation-with-css-transforms.html 本文由陈毅根据SARA SOUE ...
- Flex 容器基本概念
申明文章出处:http://www.adobe.com/cn/devnet/flex/articles/flex-containers-tips.html Flex 4 容器可以提供一套默认的布局:B ...
- flex容器解析
通常在Flex种有两种形式的容器:布局和导航. 在容器中我们可以同时设置一些空间和子容器,我们可以叫在容器内定义的任何组件为该容器的孩子. 在一个Flex程序的根部是一个叫做Application C ...
- JQUERY 插件开发——MENU(导航菜单)
JQUERY 插件开发——MENU(导航菜单) 故事背景:由于最近太忙了,已经很久没有写jquery插件开发系列了.但是凭着自己对这方面的爱好,我还是抽了一些时间来过一下插件瘾的.今天的主题是导航菜单 ...
- 使用Flex4容器若干技巧
本文适用于正在寻找使用Flex 4容器和布局的快速参考指南的开发人员. 尽管这不一定是一个复杂问题,但这似乎是许多开发人员的挫折的来源,特别是对于那些Flex刚刚入门的开发人员. 当开发人员不知道如何 ...
- layui禁用侧边导航栏点击事件
layui是一款优秀的前端模块化css框架,作者是贤心 —— 国内的一位前端大佬. 我用layui做过两个完整的项目,对她的感觉就是,这货非常适合做后台管理界面,且基于jquery,很容易上手.当然, ...
- WPF导航总结
使用导航的目的是从一个页面进入到另一个页面.无论是预先决定的线性顺序(向导)还是基于层次的用户驱动程序(大部分网站的形式),或者动态生成的路径,主要有3种方法实现:调用Navigate方法,使用Hyp ...
随机推荐
- HDU6808 Go Running(未解决问题
https://vjudge.net/contest/386568#problem/G Zhang3 is the class leader. Recently she's implementing ...
- LeetCode----Python 基础知识
0 python 基础知识 0.1 深拷贝.浅拷贝 在 Python 中,复制一个对象有两种方式:浅拷贝和深拷贝. 浅拷贝(Shallow Copy) 浅拷贝是指创建一个新的对象,但只复制原始对象中的 ...
- Bash 命令的解析
base脚本文件的第一行 #! /bin/bash 解析 1. brace expansion 花括号扩展 echo {1..10} mkdir data{1,2,3,4} mkdir data-{a ...
- GPT4All-J: An Apache-2 Licensed GPT4All Model
Demo, data, and code to train open-source assistant-style large language model based on GPT-J and LL ...
- 每天一个安卓测试开发小知识之 (四) ---常用的adb shell命令第二期 pm命令
每天一个安卓测试开发小知识之 (四) ---常用的adb shell命令第二期 pm命令 上一期我们简单介绍了如何进入\退出 adb shell以及 adb shell 的常用命令,本期继续介绍 pm ...
- k8s控制器定时把k8s apiserver内存和cpu打得很高
近期发现,k8s apiserver的内存和cpu定时(每隔10h)被客户一个控制器打的很高,有个小突刺.排查发现,用户的控制器开启了resyncPeriod,默认值就是10h. 一般来说contro ...
- 01_LaTeX的基本概念
\(\LaTeX{}\) 的基本概念 本文主体内容来自一份 (不太) 简短的 LATEX2ε 介绍. 欢迎使用 \(\LaTeX{}\)!本文开头介绍了\(\LaTeX{}\)的来源,然后介绍了\(\ ...
- iOS设置启动页后的广告页
转载请注明出处!!! 很多app(如淘宝.美团等)在启动图加载完毕后,还会显示几秒的广告,一般都有个跳过按钮可以跳过这个广告,有的app在点击广告页之后还会进入一个广告页面,点击返回进入首页.就像下面 ...
- 剑指offer-3、从尾到头打印链表
题目描述 输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回). 如输入{1,2,3}的链表如下图: 返回一个数组为[3,2,1] 0 <= 链表长度 <= 10000 ...
- 在黑土地里种上 AI 的种子
一到冬天,哈尔滨再次坐上顶流的位置.从哈尔滨冰雪大世界到亚布力滑雪场,每一处细节都透露出这座城市对文化的尊重与创新的渴望,吸引着来自全国的"小土豆". 哈尔滨的创新远不止于文旅,还 ...