5.1 导航模式概览
iOS 应用的导航结构主要由以下几种模式组成,它们往往组合使用:
| 导航方式 | 视觉效果 | 典型场景 |
|---|---|---|
NavigationStack | 从右侧推入新页面(Push) | 主从页面、详情页、设置层级 |
TabView | 底部标签栏切换 | App 主体结构(首页/搜索/我的) |
sheet | 从底部滑入(模态) | 表单、快速操作、预览 |
fullScreenCover | 全屏覆盖(模态) | 登录页、相机、引导页 |
Alert | 弹窗 | 确认操作、错误提示 |
ConfirmationDialog | 底部 Action Sheet | 多选操作 |
5.2 NavigationStack(iOS 16+)
NavigationStack 是 iOS 16 引入的新一代导航容器,取代了旧版的 NavigationView。它基于值驱动路由,比旧方式更强大、更可预测。
iOS 16 之前使用 NavigationView,存在多栏样式混乱、路由不可控等问题。iOS 16+ 推荐始终使用 NavigationStack,它支持深度链接、路由历史管理等高级特性。
基础用法:NavigationLink
struct BasicNavigationView: View {
var body: some View {
NavigationStack {
List {
// NavigationLink:点击后 push 到目标视图
NavigationLink("第一页", destination: FirstDetailView())
NavigationLink("第二页", destination: SecondDetailView())
// 自定义链接外观
NavigationLink(destination: ProfileView()) {
HStack {
Image(systemName: "person.circle")
Text("个人资料")
}
}
}
.navigationTitle("主页") // 导航栏标题
.navigationBarTitleDisplayMode(.large) // 大标题模式
}
}
}
// 详情页:使用 .inline 小标题
struct FirstDetailView: View {
var body: some View {
Text("这是第一页的详情")
.navigationTitle("详情")
.navigationBarTitleDisplayMode(.inline) // 小标题
}
}
navigationTitle 与 navigationBarItems
struct ListPageView: View {
@State private var items = ["苹果", "香蕉", "橙子"]
@State private var showAdd = false
var body: some View {
List(items, id: \.self) { Text($0) }
.navigationTitle("水果列表")
// toolbar:在导航栏添加按钮(推荐方式)
.toolbar {
// 右上角按钮
ToolbarItem(placement: .topBarTrailing) {
Button {
showAdd = true
} label: {
Image(systemName: "plus")
}
}
// 左上角按钮
ToolbarItem(placement: .topBarLeading) {
EditButton() // 系统内置编辑按钮
}
// 底部工具栏(需要 .bottomBar placement)
ToolbarItem(placement: .bottomBar) {
Text("\(items.count) 项").foregroundColor(.secondary)
}
}
}
}
// ToolbarItem placement 位置选项:
// .topBarLeading — 导航栏左侧
// .topBarTrailing — 导航栏右侧
// .bottomBar — 底部工具栏
// .principal — 导航栏中间(通常放自定义标题)
// .confirmationAction — 确认操作(如"完成")
// .cancellationAction — 取消操作
5.3 类型安全路由:navigationDestination
navigationDestination(for:) 是 iOS 16 引入的类型安全路由机制。通过将路由信息建模为值类型,实现了真正的数据驱动导航。
// 定义路由目的地类型(需要 Hashable)
struct Article: Hashable, Identifiable {
let id: Int
let title: String
let content: String
}
struct Author: Hashable, Identifiable {
let id: Int
let name: String
}
// NavigationStack + navigationDestination
struct ArticleListView: View {
let articles: [Article] = [
Article(id: 1, title: "SwiftUI 入门", content: "..."),
Article(id: 2, title: "状态管理", content: "..."),
]
var body: some View {
NavigationStack {
List(articles) { article in
// NavigationLink 传入值而非视图
NavigationLink(value: article) {
Text(article.title)
}
}
.navigationTitle("文章列表")
// 在 NavigationStack 或任意祖先视图中注册目的地
.navigationDestination(for: Article.self) { article in
ArticleDetailView(article: article)
}
.navigationDestination(for: Author.self) { author in
AuthorView(author: author)
}
}
}
}
5.4 NavigationPath — 编程式路由控制
NavigationPath 是一个类型擦除的路由栈,允许你用代码控制导航历史,实现深度链接、直接跳转等高级功能。
struct RootNavigationView: View {
// NavigationPath 管理路由历史栈
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) { // 绑定路由栈
VStack(spacing: 20) {
// 编程式 push:直接跳转到指定页面
Button("直接打开文章 #5") {
path.append(Article(id: 5, title: "高级话题", content: "..."))
}
// 跳转多层
Button("直接到三级页面") {
path.append(Category(id: 1, name: "技术")) // 第二层
path.append(Article(id: 3, title: "...", content: "...")) // 第三层
}
// 返回根页面(清空路由栈)
Button("回到根页面") {
path = NavigationPath() // 重置为空
}
// 返回上一页(移除最后一个路由)
Button("返回上一页") {
path.removeLast()
}
Text("当前路由深度:\(path.count)")
}
.navigationTitle("首页")
.navigationDestination(for: Article.self) { article in
ArticleDetailView(article: article)
}
}
}
}
5.5 TabView — 底部标签栏
TabView 是构建 App 主框架的标准方式。通过 tabItem 修饰符为每个标签页设置图标和标题。
struct MainTabView: View {
// 控制当前选中的标签(可省略,TabView 自动管理)
@State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
// 第一个标签:首页(包含独立的 NavigationStack)
NavigationStack {
HomeView()
}
.tabItem {
Label("首页", systemImage: "house.fill")
}
.tag(0) // 对应 selectedTab 的值
// 第二个标签:搜索
NavigationStack {
SearchView()
}
.tabItem {
Label("搜索", systemImage: "magnifyingglass")
}
.tag(1)
// 第三个标签:带角标
NotificationView()
.tabItem {
Label("通知", systemImage: "bell.fill")
}
.badge(3) // 红色角标数字
.tag(2)
// 第四个标签:个人中心
ProfileView()
.tabItem {
Label("我的", systemImage: "person.fill")
}
.tag(3)
}
.tint(.blue) // 选中标签的颜色
}
}
// 编程式切换标签
struct HomeView: View {
@Binding var selectedTab: Int
var body: some View {
Button("去搜索页") {
selectedTab = 1 // 切换到搜索标签
}
}
}
5.6 Sheet — 模态半屏视图
sheet 从屏幕底部滑入一个新视图,覆盖在当前页面上方。用于不需要完全离开当前上下文的操作,如添加记录、查看详情。
struct ArticleView: View {
@State private var showAddSheet = false
@State private var selectedArticle: Article? = nil
var body: some View {
VStack {
// 方式1:isPresented 布尔控制
Button("添加文章") { showAddSheet = true }
.sheet(isPresented: $showAddSheet) {
AddArticleView() // sheet 内容
}
// 方式2:item 可选值控制(有值时展示,nil 时关闭)
List(articles) { article in
Text(article.title)
.onTapGesture { selectedArticle = article }
}
.sheet(item: $selectedArticle) { article in
ArticleDetailSheet(article: article)
}
}
}
}
// Sheet 内部:用 dismiss 关闭
struct AddArticleView: View {
@Environment(\.dismiss) var dismiss
@State private var title = ""
var body: some View {
NavigationStack {
Form {
TextField("标题", text: $title)
}
.navigationTitle("新建文章")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("取消") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button("保存") {
// 保存逻辑
dismiss()
}
.disabled(title.isEmpty)
}
}
}
}
}
// presentationDetents:控制 sheet 高度(iOS 16+)
struct QuickActionView: View {
@State private var showSheet = false
var body: some View {
Button("操作面板") { showSheet = true }
.sheet(isPresented: $showSheet) {
ActionPanelView()
.presentationDetents([.height(200), .medium, .large])
// .height(200) — 固定 200pt 高
// .medium — 半屏(约 50%)
// .large — 全屏
// .fraction(0.3) — 30% 屏幕高度
.presentationDragIndicator(.visible) // 显示顶部拖动条
}
}
}
5.7 FullScreenCover — 全屏覆盖
fullScreenCover 与 sheet 类似,但会完全遮盖整个屏幕,通常用于登录、引导、相机等需要完全占据屏幕的场景。
struct AppRootView: View {
@State private var isLoggedIn = false
var body: some View {
MainContentView()
// 未登录时显示全屏登录页
.fullScreenCover(isPresented: Binding(
get: { !isLoggedIn },
set: { _ in }
)) {
LoginView(isLoggedIn: $isLoggedIn)
}
}
}
struct LoginView: View {
@Binding var isLoggedIn: Bool
@Environment(\.dismiss) var dismiss
var body: some View {
VStack(spacing: 24) {
Text("欢迎登录").font(.largeTitle).fontWeight(.bold)
Button("一键登录") {
isLoggedIn = true
dismiss()
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
}
}
}
5.8 Alert 与 ConfirmationDialog
用于展示需要用户确认的操作或系统消息。Alert 是简单对话框,ConfirmationDialog(即 Action Sheet)适合多选操作。
struct DeleteExampleView: View {
@State private var showDeleteAlert = false
@State private var showActionSheet = false
@State private var alertMessage = ""
var body: some View {
VStack(spacing: 20) {
// ── Alert:简单确认弹窗 ──
Button("删除账号", role: .destructive) {
showDeleteAlert = true
}
.alert("确认删除", isPresented: $showDeleteAlert) {
// 操作按钮(不写就只有默认的"OK")
Button("删除", role: .destructive) {
// 执行删除
alertMessage = "已删除"
}
Button("取消", role: .cancel) { }
} message: {
Text("此操作不可撤销,账号中的所有数据将被永久删除。")
}
// Alert 绑定错误类型(item 方式)
Button("触发错误") {
alertMessage = "网络连接失败,请检查网络设置"
showDeleteAlert = true
}
// ── ConfirmationDialog:Action Sheet(多个选项)──
Button("分享") {
showActionSheet = true
}
.confirmationDialog(
"选择分享方式",
isPresented: $showActionSheet,
titleVisibility: .visible // .visible / .hidden / .automatic
) {
Button("复制链接") { }
Button("发送给朋友") { }
Button("保存到相册") { }
Button("举报", role: .destructive) { }
Button("取消", role: .cancel) { }
} message: {
Text("选择将此内容分享的方式")
}
}
}
}
5.9 实战示例:带导航的主从页面
构建一个完整的新闻列表 App,综合运用 NavigationStack、TabView、Sheet 和 Alert。
// ─── 数据模型 ───
struct NewsItem: Identifiable, Hashable {
let id = UUID()
let title: String
let summary: String
let category: String
let isBookmarked: Bool
}
// ─── 新闻详情页 ───
struct NewsDetailView: View {
let news: NewsItem
@State private var isBookmarked = false
@State private var showShareSheet = false
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text(news.category)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(.blue.opacity(0.1))
.foregroundColor(.blue)
.cornerRadius(4)
Text(news.title)
.font(.title)
.fontWeight(.bold)
Divider()
Text(news.summary)
.font(.body)
.lineSpacing(6)
.foregroundColor(.secondary)
}
.padding()
}
.navigationTitle("详情")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Button {
isBookmarked.toggle()
} label: {
Image(systemName: isBookmarked ? "bookmark.fill" : "bookmark")
}
Button {
showShareSheet = true
} label: {
Image(systemName: "square.and.arrow.up")
}
}
}
.onAppear { isBookmarked = news.isBookmarked }
.sheet(isPresented: $showShareSheet) {
ShareSheetView(title: news.title)
.presentationDetents([.medium])
}
}
}
// ─── 新闻列表 ───
struct NewsListView: View {
let newsList: [NewsItem] = [
NewsItem(title: "Apple 发布 SwiftUI 5.0",
summary: "本次更新带来了全新的动画系统和更多的系统集成...",
category: "科技", isBookmarked: false),
NewsItem(title: "WWDC 2025 亮点回顾",
summary: "今年 WWDC 带来了 iOS 19、visionOS 3 等重大发布...",
category: "活动", isBookmarked: true),
]
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(newsList) { news in
NavigationLink(value: news) {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(news.category)
.font(.caption)
.foregroundColor(.blue)
Spacer()
if news.isBookmarked {
Image(systemName: "bookmark.fill")
.font(.caption)
.foregroundColor(.orange)
}
}
Text(news.title).font(.headline).lineLimit(2)
Text(news.summary).font(.caption)
.foregroundColor(.secondary).lineLimit(2)
}
.padding(.vertical, 4)
}
}
.navigationTitle("今日新闻")
.navigationDestination(for: NewsItem.self) { news in
NewsDetailView(news: news)
}
}
}
}
// ─── 完整的标签栏 App ───
struct NewsApp: View {
var body: some View {
TabView {
NewsListView()
.tabItem { Label("头条", systemImage: "newspaper") }
Text("搜索页")
.tabItem { Label("搜索", systemImage: "magnifyingglass") }
Text("我的书签")
.tabItem { Label("书签", systemImage: "bookmark") }
}
}
}
本章学习了 SwiftUI 的完整导航体系:NavigationStack 处理层级导航,navigationDestination 实现类型安全路由,NavigationPath 支持编程式路由控制,TabView 构建 App 主框架,sheet/fullScreenCover 处理模态展示,Alert/ConfirmationDialog 实现用户确认操作。