精选文章

24. 路由导航

2020-12-18 · 路由

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))
    }
}
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 分支,跳转逻辑集中管理,维护成本下降。

JJ

作者简介

专注于内容创作、产品策略与设计实践。欢迎交流合作。

上一篇 下一篇