// // NavigationRouter.swift // suixinkan // // Created by Codex on 2026/6/18. // import Foundation import UIKit /// 应用内可导航目标,承载各 Tab 内部的路由目的地。 enum AppRoute: Hashable { case placeholder(title: String) case home(HomeRoute) case profile(ProfileRoute) case orders(OrdersRoute) /// 返回该路由通过 push 后是否隐藏底部 TabBar。 var hidesTabBarWhenPushed: Bool { true } } @MainActor /// UIKit 路由执行策略,描述全局入口应如何选择导航栈。 enum AppNavigationPolicy: Equatable { case currentStack case tabRoot(AppTab) case preserveTab(AppTab) } @MainActor /// 主 Tab 导航宿主协议,由 `MainTabBarController` 提供 UIKit 栈能力。 protocol AppNavigationHost: AnyObject { /// 选中指定主 Tab。 func selectTab(_ tab: AppTab) /// 返回指定 Tab 的导航控制器。 func navigationController(for tab: AppTab) -> TabNavigationController? /// 返回当前选中 Tab 的导航控制器。 func selectedNavigationController() -> TabNavigationController? /// 重置全部 Tab 的导航栈。 func resetAllStacks() /// 将指定订单入口应用到订单根页面。 func applyOrdersEntry(_ entry: OrdersEntry, scannedCode: String?) } @MainActor /// UIKit 主导航器,以 `UINavigationController.viewControllers` 作为唯一导航事实源。 final class AppNavigator { private weak var host: AppNavigationHost? private var pendingActions: [(@MainActor (AppNavigationHost) -> Void)] = [] /// 绑定主 Tab 宿主,并按 FIFO 执行挂载前暂存的全局导航请求。 func attach(host: AppNavigationHost) { self.host = host let actions = pendingActions pendingActions.removeAll() actions.forEach { $0(host) } } /// 解绑主 Tab 宿主并清理尚未执行的全局导航请求。 func detach(host: AppNavigationHost? = nil) { if let host, self.host !== host { return } self.host = nil pendingActions.removeAll() } /// 切换到指定主 Tab,保留该 Tab 已有导航栈。 func selectTab(_ tab: AppTab) { performOrEnqueue { host in host.selectTab(tab) } } /// 在当前或指定来源的导航栈中 push 应用路由。 func push(_ route: AppRoute, from source: UIViewController?, animated: Bool = true) { if let navigationController = source?.navigationController as? TabNavigationController { navigationController.push(route: route, animated: animated) return } performOrEnqueue { host in guard let navigationController = source?.navigationController as? TabNavigationController ?? host.selectedNavigationController() else { return } navigationController.push(route: route, animated: animated) } } /// 打开首页路由。 func openHome(_ route: HomeRoute, policy: AppNavigationPolicy = .currentStack, animated: Bool = true) { open(.home(route), defaultTab: .home, policy: policy, animated: animated) } /// 打开个人中心路由。 func openProfile(_ route: ProfileRoute, policy: AppNavigationPolicy = .currentStack, animated: Bool = true) { open(.profile(route), defaultTab: .profile, policy: policy, animated: animated) } /// 切换到订单 Tab 并应用订单内部入口,可携带全局扫码结果。 func openOrdersEntry(_ entry: OrdersEntry, scannedCode: String? = nil) { performOrEnqueue { host in host.selectTab(.orders) host.navigationController(for: .orders)?.popToRootViewController(animated: false) host.applyOrdersEntry(entry, scannedCode: scannedCode) } } /// 重置全部 Tab 导航栈并清空暂存请求。 func resetAllStacks() { pendingActions.removeAll() host?.resetAllStacks() } /// 执行通用路由打开逻辑。 private func open( _ route: AppRoute, defaultTab: AppTab, policy: AppNavigationPolicy, animated: Bool ) { performOrEnqueue { host in let targetTab: AppTab switch policy { case .currentStack: guard let navigationController = host.selectedNavigationController() else { return } navigationController.push(route: route, animated: animated) return case .tabRoot(let tab): targetTab = tab host.selectTab(tab) guard let navigationController = host.navigationController(for: tab) else { return } navigationController.popToRootViewController(animated: false) navigationController.push(route: route, animated: animated) case .preserveTab(let tab): targetTab = tab host.selectTab(tab) host.navigationController(for: tab)?.push(route: route, animated: animated) } if targetTab != defaultTab { assertionFailure("Route \(route) opened on unexpected tab \(targetTab)") } } } /// 主 Tab 已挂载时立即执行,否则暂存至登录后执行。 private func performOrEnqueue(_ action: @escaping @MainActor (AppNavigationHost) -> Void) { if let host { action(host) } else { pendingActions.append(action) } } }