欢迎来到本系列教程的第四十六篇。在前四十五篇文章中,你已经学习了从Swift基础到数据可视化的全方位iOS开发技能。现在,你能够构建出功能完善、数据丰富的应用了。但是,当应用包含数十个页面时,如何管理复杂的导航逻辑?如何实现深度链接?如何处理状态恢复?
导航架构是大型应用的核心基础设施。SwiftUI提供了NavigationStack、NavigationPath等工具,结合自定义路由系统,可以构建可维护、可测试的导航架构。
在这一篇中,你将学到:
-
NavigationStack基础
-
声明式导航
-
NavigationPath状态管理
-
导航样式与工具栏
-
-
枚举路由系统
-
类型安全的导航
-
参数传递
-
嵌套导航
-
-
协调器模式
-
分离导航逻辑
-
依赖注入
-
可测试性
-
-
深度链接
-
URL路由解析
-
推送通知导航
-
状态恢复
-
-
实战项目:构建完整的应用路由系统
一、NavigationStack基础
1.1 基础导航
swift
import SwiftUI
// MARK: - 基础NavigationStack
struct BasicNavigationView: View {
var body: some View {
NavigationStack {
List {
NavigationLink("用户详情", destination: UserDetailView(userId: 1))
NavigationLink("设置页面", destination: SettingsView())
NavigationLink("关于页面", destination: AboutView())
}
.navigationTitle("首页")
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("编辑") {
// 编辑动作
}
}
}
}
}
}
struct UserDetailView: View {
let userId: Int
var body: some View {
VStack {
Text("用户ID: \(userId)")
NavigationLink("查看订单", destination: OrderListView(userId: userId))
}
.navigationTitle("用户详情")
.navigationBarTitleDisplayMode(.inline)
}
}
1.2 NavigationPath状态管理
swift
// MARK: - NavigationPath导航
struct NavigationPathView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List {
Button("跳转到A -> B -> C") {
path.append("A")
path.append("B")
path.append("C")
}
Button("随机导航") {
let destinations = ["A", "B", "C", "D", "E"]
path.append(destinations.randomElement()!)
}
}
.navigationTitle("导航路径")
.navigationDestination(for: String.self) { value in
DestinationView(value: value, path: $path)
}
}
}
}
struct DestinationView: View {
let value: String
@Binding var path: NavigationPath
var body: some View {
VStack(spacing: 20) {
Text("当前页面: \(value)")
.font(.largeTitle)
HStack {
Button("返回上一页") {
path.removeLast()
}
.buttonStyle(.bordered)
Button("返回首页") {
path.removeLast(path.count)
}
.buttonStyle(.bordered)
Button("添加下一页") {
let next = String(UnicodeScalar(value.unicodeScalars.first!.value + 1)!)
if next <= "E" {
path.append(next)
}
}
.buttonStyle(.borderedProminent)
}
}
.navigationTitle("页面 \(value)")
}
}
1.3 导航样式与工具栏
swift
// MARK: - 自定义导航样式
struct CustomNavigationView: View {
var body: some View {
NavigationStack {
List(0..<10) { i in
NavigationLink("项目 \(i)", value: i)
}
.navigationTitle("自定义导航")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.toolbarBackground(.blue.opacity(0.1), for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)
.navigationDestination(for: Int.self) { item in
DetailView(item: item)
}
}
}
}
struct DetailView: View {
let item: Int
@Environment(\.dismiss) var dismiss
var body: some View {
VStack(spacing: 20) {
Text("详情页面 \(item)")
.font(.largeTitle)
Button("关闭") {
dismiss()
}
.buttonStyle(.borderedProminent)
}
.navigationTitle("详情")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("取消") {
dismiss()
}
}
ToolbarItem(placement: .topBarTrailing) {
Button("保存") {
// 保存动作
}
}
}
}
}
二、枚举路由系统
2.1 类型安全的导航
swift
// MARK: - 路由枚举定义
enum AppRoute: Hashable {
case home
case userDetail(id: Int)
case settings
case about
case orderList(userId: Int)
case orderDetail(orderId: Int)
case productDetail(productId: Int, fromCart: Bool)
}
// MARK: - 路由处理器
struct AppRouter: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
HomeView(path: $path)
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .home:
HomeView(path: $path)
case .userDetail(let id):
UserDetailRouteView(userId: id, path: $path)
case .settings:
SettingsRouteView(path: $path)
case .about:
AboutRouteView(path: $path)
case .orderList(let userId):
OrderListRouteView(userId: userId, path: $path)
case .orderDetail(let orderId):
OrderDetailRouteView(orderId: orderId, path: $path)
case .productDetail(let productId, let fromCart):
ProductDetailRouteView(productId: productId, fromCart: fromCart, path: $path)
}
}
}
}
}
// MARK: - 首页视图
struct HomeView: View {
@Binding var path: NavigationPath
var body: some View {
List {
Button("用户详情") {
path.append(AppRoute.userDetail(id: 123))
}
Button("设置") {
path.append(AppRoute.settings)
}
Button("订单列表") {
path.append(AppRoute.orderList(userId: 123))
}
Button("关于") {
path.append(AppRoute.about)
}
}
.navigationTitle("首页")
}
}
struct UserDetailRouteView: View {
let userId: Int
@Binding var path: NavigationPath
var body: some View {
VStack(spacing: 20) {
Text("用户ID: \(userId)")
Button("查看订单") {
path.append(AppRoute.orderList(userId: userId))
}
Button("查看商品") {
path.append(AppRoute.productDetail(productId: 456, fromCart: false))
}
}
.navigationTitle("用户详情")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("返回") {
path.removeLast()
}
}
}
}
}
// 其他路由视图类似...
struct SettingsRouteView: View {
@Binding var path: NavigationPath
var body: some View {
Text("设置页面")
.navigationTitle("设置")
}
}
struct AboutRouteView: View {
@Binding var path: NavigationPath
var body: some View {
Text("关于页面")
.navigationTitle("关于")
}
}
struct OrderListRouteView: View {
let userId: Int
@Binding var path: NavigationPath
var body: some View {
List {
Button("订单 #1001") {
path.append(AppRoute.orderDetail(orderId: 1001))
}
Button("订单 #1002") {
path.append(AppRoute.orderDetail(orderId: 1002))
}
}
.navigationTitle("订单列表")
}
}
struct OrderDetailRouteView: View {
let orderId: Int
@Binding var path: NavigationPath
var body: some View {
Text("订单详情: \(orderId)")
.navigationTitle("订单详情")
}
}
struct ProductDetailRouteView: View {
let productId: Int
let fromCart: Bool
@Binding var path: NavigationPath
var body: some View {
VStack {
Text("商品ID: \(productId)")
Text("来源: \(fromCart ? "购物车" : "普通浏览")")
}
.navigationTitle("商品详情")
}
}
2.2 嵌套导航
swift
// MARK: - 嵌套路由
struct NestedAppRoute: Hashable {
enum Tab: Hashable {
case home
case profile
case settings
}
var tab: Tab
var path: [AppRoute]
}
struct NestedRouter: View {
@State private var selectedTab: NestedAppRoute.Tab = .home
@State private var homePath = NavigationPath()
@State private var profilePath = NavigationPath()
@State private var settingsPath = NavigationPath()
var body: some View {
TabView(selection: $selectedTab) {
NavigationStack(path: $homePath) {
HomeTabView(path: $homePath)
.navigationDestination(for: AppRoute.self) { route in
handleRoute(route, path: $homePath)
}
}
.tabItem {
Label("首页", systemImage: "house")
}
.tag(NestedAppRoute.Tab.home)
NavigationStack(path: $profilePath) {
ProfileTabView(path: $profilePath)
.navigationDestination(for: AppRoute.self) { route in
handleRoute(route, path: $profilePath)
}
}
.tabItem {
Label("个人", systemImage: "person")
}
.tag(NestedAppRoute.Tab.profile)
NavigationStack(path: $settingsPath) {
SettingsTabView(path: $settingsPath)
.navigationDestination(for: AppRoute.self) { route in
handleRoute(route, path: $settingsPath)
}
}
.tabItem {
Label("设置", systemImage: "gear")
}
.tag(NestedAppRoute.Tab.settings)
}
}
@ViewBuilder
private func handleRoute(_ route: AppRoute, path: Binding<NavigationPath>) -> some View {
switch route {
case .userDetail(let id):
UserDetailRouteView(userId: id, path: path)
case .orderList(let userId):
OrderListRouteView(userId: userId, path: path)
default:
EmptyView()
}
}
}
struct HomeTabView: View {
@Binding var path: NavigationPath
var body: some View {
List {
Button("用户详情") {
path.append(AppRoute.userDetail(id: 1))
}
}
.navigationTitle("首页")
}
}
struct ProfileTabView: View {
@Binding var path: NavigationPath
var body: some View {
List {
Button("订单列表") {
path.append(AppRoute.orderList(userId: 1))
}
}
.navigationTitle("个人")
}
}
struct SettingsTabView: View {
@Binding var path: NavigationPath
var body: some View {
Text("设置页面")
.navigationTitle("设置")
}
}
三、协调器模式
3.1 协调器基础
swift
// MARK: - 协调器协议
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController? { get set }
func start()
}
// MARK: - SwiftUI协调器
class AppCoordinator: ObservableObject {
@Published var path = NavigationPath()
private var dependencies: Dependencies
struct Dependencies {
let userService: UserServiceProtocol
let orderService: OrderServiceProtocol
}
init(dependencies: Dependencies) {
self.dependencies = dependencies
}
func navigate(to route: AppRoute) {
path.append(route)
}
func pop() {
path.removeLast()
}
func popToRoot() {
path.removeLast(path.count)
}
@ViewBuilder
func view(for route: AppRoute) -> some View {
switch route {
case .home:
HomeCoordinatorView(coordinator: self)
case .userDetail(let id):
UserDetailCoordinatorView(coordinator: self, userId: id)
case .orderList(let userId):
OrderListCoordinatorView(coordinator: self, userId: userId)
default:
EmptyView()
}
}
}
// MARK: - 协调器视图
struct AppCoordinatorView: View {
@StateObject var coordinator: AppCoordinator
var body: some View {
NavigationStack(path: $coordinator.path) {
coordinator.view(for: .home)
.navigationDestination(for: AppRoute.self) { route in
coordinator.view(for: route)
}
}
}
}
struct HomeCoordinatorView: View {
let coordinator: AppCoordinator
var body: some View {
List {
Button("用户详情") {
coordinator.navigate(to: .userDetail(id: 1))
}
Button("订单列表") {
coordinator.navigate(to: .orderList(userId: 1))
}
}
.navigationTitle("首页")
}
}
struct UserDetailCoordinatorView: View {
let coordinator: AppCoordinator
let userId: Int
var body: some View {
VStack {
Text("用户ID: \(userId)")
Button("查看订单") {
coordinator.navigate(to: .orderList(userId: userId))
}
}
.navigationTitle("用户详情")
}
}
struct OrderListCoordinatorView: View {
let coordinator: AppCoordinator
let userId: Int
var body: some View {
List {
Text("订单列表 for user \(userId)")
}
.navigationTitle("订单列表")
}
}
3.2 可测试性
swift
// MARK: - 协议抽象
protocol Routing {
func navigate(to route: AppRoute)
func pop()
func popToRoot()
}
class MockCoordinator: Routing {
var navigatedRoutes: [AppRoute] = []
var popCalled = false
var popToRootCalled = false
func navigate(to route: AppRoute) {
navigatedRoutes.append(route)
}
func pop() {
popCalled = true
}
func popToRoot() {
popToRootCalled = true
}
}
// MARK: - 可测试的视图
struct TestableHomeView: View {
let router: Routing
var body: some View {
List {
Button("用户详情") {
router.navigate(to: .userDetail(id: 1))
}
Button("订单列表") {
router.navigate(to: .orderList(userId: 1))
}
}
.navigationTitle("首页")
}
}
四、深度链接
4.1 URL路由解析
swift
// MARK: - URL路由解析器
struct URLRouter {
static let scheme = "myapp"
static func parse(url: URL) -> AppRoute? {
guard url.scheme == scheme else { return nil }
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let path = url.pathComponents
switch url.host {
case "user":
if path.count > 1, let id = Int(path[1]) {
return .userDetail(id: id)
}
case "order":
if path.count > 1, let id = Int(path[1]) {
return .orderDetail(orderId: id)
}
case "product":
if path.count > 1, let id = Int(path[1]) {
let fromCart = components?.queryItems?.first(where: { $0.name == "fromCart" })?.value == "true"
return .productDetail(productId: id, fromCart: fromCart)
}
case "settings":
return .settings
case "about":
return .about
default:
break
}
return nil
}
static func url(for route: AppRoute) -> URL {
var components = URLComponents()
components.scheme = scheme
switch route {
case .userDetail(let id):
components.host = "user"
return components.url!.appendingPathComponent("\(id)")
case .orderDetail(let id):
components.host = "order"
return components.url!.appendingPathComponent("\(id)")
case .productDetail(let id, let fromCart):
components.host = "product"
components.queryItems = [URLQueryItem(name: "fromCart", value: fromCart ? "true" : "false")]
return components.url!.appendingPathComponent("\(id)")
case .settings:
components.host = "settings"
return components.url!
case .about:
components.host = "about"
return components.url!
default:
return components.url!
}
}
}
// MARK: - 深度链接处理
class DeepLinkManager: ObservableObject {
@Published var pendingRoute: AppRoute?
func handleURL(_ url: URL) -> Bool {
if let route = URLRouter.parse(url: url) {
pendingRoute = route
return true
}
return false
}
func consumePendingRoute() -> AppRoute? {
let route = pendingRoute
pendingRoute = nil
return route
}
}
// MARK: - 支持深度链接的应用
struct DeepLinkAppView: View {
@StateObject private var coordinator = AppCoordinator(dependencies: AppCoordinator.Dependencies(
userService: UserService(),
orderService: OrderService()
))
@StateObject private var deepLinkManager = DeepLinkManager()
var body: some View {
AppCoordinatorView(coordinator: coordinator)
.onOpenURL { url in
if let route = URLRouter.parse(url: url) {
coordinator.navigate(to: route)
}
}
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
guard let url = userActivity.webpageURL,
let route = URLRouter.parse(url: url) else { return }
coordinator.navigate(to: route)
}
}
}
// 模拟服务
protocol UserServiceProtocol {}
protocol OrderServiceProtocol {}
struct UserService: UserServiceProtocol {}
struct OrderService: OrderServiceProtocol {}
4.2 推送通知导航
swift
// MARK: - 推送通知路由
struct NotificationRouter {
static func handleNotification(_ userInfo: [AnyHashable: Any]) -> AppRoute? {
guard let type = userInfo["type"] as? String else { return nil }
switch type {
case "user":
if let userId = userInfo["userId"] as? Int {
return .userDetail(id: userId)
}
case "order":
if let orderId = userInfo["orderId"] as? Int {
return .orderDetail(orderId: orderId)
}
case "product":
if let productId = userInfo["productId"] as? Int {
let fromCart = userInfo["fromCart"] as? Bool ?? false
return .productDetail(productId: productId, fromCart: fromCart)
}
default:
break
}
return nil
}
}
// MARK: - 通知处理扩展
extension Notification.Name {
static let openRoute = Notification.Name("openRoute")
}
// 在AppDelegate中处理推送
class AppNotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let route = NotificationRouter.handleNotification(userInfo) {
NotificationCenter.default.post(name: .openRoute, object: route)
}
completionHandler()
}
}
五、状态恢复
5.1 场景状态保存
swift
// MARK: - 可保存的导航状态
struct NavigationState: Codable {
var routes: [String]
}
class StatefulCoordinator: ObservableObject {