Add CYLTabBarController with a global scan entry, promote MainTabBarController to the window root after login, replace RouterPath-based navigation with AppNavigator, and fix Profile diffable cell reconfiguration crashes. Co-authored-by: Cursor <cursoragent@cursor.com>
162 lines
5.4 KiB
Swift
162 lines
5.4 KiB
Swift
//
|
||
// 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)
|
||
}
|
||
}
|
||
}
|