建筑师之家IOS / NOTES
← 全部笔记

架构

导航是一条独立的流程

页面不要自己 new 下一个业务页面。路由集中之后,深链和按钮走同一条路。

ViewController 里写 navigationController?.pushViewController(DetailViewController(id: id), animated: true),功能在小的时候很直接。等详情页还要在搜索、通知、小组件三条路进来,每一处都会复制一遍构造和入栈。漏掉的那条路参数就不一致。

路由模型

把“去哪里”写成数据,而不是写成对某个控制器的调用。路由器是唯一知道控制器类型的地方。页面只发出路由。

enum Route: Equatable {
    case feed
    case item(id: String)
    case settings
}

protocol Routing: AnyObject {
    func show(_ route: Route)
}

final class AppRouter: Routing {
    private let navigation: UINavigationController
    init(navigation: UINavigationController) { self.navigation = navigation }

    func show(_ route: Route) {
        switch route {
        case .feed:
            navigation.setViewControllers([FeedViewController(router: self)], animated: false)
        case .item(let id):
            navigation.pushViewController(ItemViewController(id: id, router: self), animated: true)
        case .settings:
            navigation.pushViewController(SettingsViewController(router: self), animated: true)
        }
    }
}

深链解析成 Route,和按钮调用的是同一个 show。参数校验放在解析处:id 为空就不要造出一个空的详情页。

不要把导航塞进通用状态机

FeedState 应该知道列表加载完了,不应该知道 UINavigationController。如果状态类型开始 import UIKit 来做 push,它就没法在没有界面的测试里跑,也会把这个功能焊死在 UIKit 上。

Coordinator 或 Router 可以按功能拆:Feed 流程管列表和详情,账号流程管登录。它们之间通过路由或委托交接,而不是拿到对方的控制器去改栈。一个流程结束,用完成回调把结果交回去,调用方决定下一程。

SwiftUI 里同样的划分是 NavigationPath 或一组枚举,由外层导航持有。子视图通过回调或环境中的路由器发路由,不自己拿 path.append 一个具体的内部页面类型,否则模块边界又破了。