24. 路由导航
路由的目标是“所有跳转入口统一管理”。避免在各处散落 push/present,同时方便 Deeplink 与权限控制。
一、基础导航
class HomeVC: UIViewController {
func openDetail() {
let vc = DetailVC()
navigationController?.pushViewController(vc, animated: true)
}
func openModal() {
let vc = LoginVC()
present(vc, animated: true)
}
}
二、定义 Route
enum Route {
case home
case detail(id: Int)
case web(url: URL)
case login
}
三、Router 统一跳转
final class Router {
private weak var navigation: UINavigationController?
init(navigation: UINavigationController) {
self.navigation = navigation
}
func open(_ route: Route) {
guard let navigation else { return }
switch route {
case .home:
navigation.setViewControllers([HomeVC()], animated: false)
case .detail(let id):
let vc = DetailVC(id: id)
navigation.pushViewController(vc, animated: true)
case .web(let url):
let vc = WebVC(url: url)
navigation.pushViewController(vc, animated: true)
case .login:
let vc = LoginVC()
navigation.present(vc, animated: true)
}
}
}
四、Coordinator 模式
protocol Coordinator {
var navigation: UINavigationController { get }
func start()
}
final class AppCoordinator: Coordinator {
let navigation: UINavigationController
private let router: Router
init(navigation: UINavigationController) {
self.navigation = navigation
self.router = Router(navigation: navigation)
}
func start() {
router.open(.home)
}
func openDetail(id: Int) {
router.open(.detail(id: id))
}
}
五、Deeplink 解析
struct DeeplinkParser {
func parse(_ url: URL) -> Route? {
let comps = URLComponents(url: url, resolvingAgainstBaseURL: false)
let path = comps?.path ?? ""
let items = comps?.queryItems ?? []
let map = Dictionary(uniqueKeysWithValues: items.compactMap { item in
item.value.map { (item.name, $0) }
})
if path == "/detail", let idStr = map["id"], let id = Int(idStr) {
return .detail(id: id)
}
if path == "/login" { return .login }
return nil
}
}
六、权限拦截
final class AuthRouter {
private let router: Router
private let isLoggedIn: () -> Bool
init(router: Router, isLoggedIn: @escaping () -> Bool) {
self.router = router
self.isLoggedIn = isLoggedIn
}
func open(_ route: Route) {
if case .detail = route, !isLoggedIn() {
router.open(.login)
} else {
router.open(route)
}
}
}
七、Scene 启动入口
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var coordinator: AppCoordinator?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
let nav = UINavigationController()
coordinator = AppCoordinator(navigation: nav)
coordinator?.start()
let window = UIWindow(windowScene: windowScene)
window.rootViewController = nav
window.makeKeyAndVisible()
self.window = window
}
}
路由统一之后,新增页面只需要新增一个 Route 分支,跳转逻辑集中管理,维护成本下降。