// // MainTabBarController.swift // suixinkan // import UIKit /// 主 Tab 容器,展示四个一级入口及中间扫码按钮。 final class MainTabBarController: UITabBarController { private let badgeViewModel = MainTabBadgeViewModel() private var tabNavigationControllers: [AppTab: TabNavigationController] = [:] private var isSyncingTabSelection = false private let scanHandler = MainTabScanHandler(api: NetworkServices.shared.orderAPI) /// 测试时可注入扫码点击处理。 var onScanTabSelected: (() -> Void)? private enum TabSlot: CaseIterable { case home case orders case scan case statistics case profile var appTab: AppTab? { switch self { case .home: return .home case .orders: return .orders case .scan: return nil case .statistics: return .statistics case .profile: return .profile } } static func index(for tab: AppTab) -> Int? { allCases.firstIndex { $0.appTab == tab } } static var scanIndex: Int? { allCases.firstIndex(of: .scan) } } override func viewDidLoad() { super.viewDidLoad() delegate = self configureTabBarAppearance() configureTabs() bindBadgeViewModel() bindScanHandler() badgeViewModel.refreshPendingWriteOffCount() } /// 选中指定主 Tab。 func selectTab(_ tab: AppTab) { guard let index = TabSlot.index(for: tab) else { return } guard selectedIndex != index else { return } isSyncingTabSelection = true selectedIndex = index isSyncingTabSelection = false if tab == .orders { badgeViewModel.refreshPendingWriteOffCount() } } /// 响应推送点击,切换到目标 Tab 并打开对应业务页面。 func openPushDestination(_ destination: PushDestination) { let targetTab: AppTab = switch destination { case .orders, .verificationOrders: .orders case .payment, .task, .queue, .messageCenter: .home } selectTab(targetTab) guard let navigationController = tabNavigationControllers[targetTab] else { return } _ = navigationController.popToRootViewController(animated: false) let controller: UIViewController? switch destination { case .payment: controller = PaymentCollectionDetailsViewController() case .task: controller = TaskAddViewController() case .queue: controller = ScenicQueueViewController() case .messageCenter: controller = MessageCenterViewController() case .orders, .verificationOrders: controller = nil } if let controller { navigationController.pushViewController(controller, animated: false) } } private func configureTabBarAppearance() { tabBar.tintColor = AppColor.primary tabBar.unselectedItemTintColor = AppColor.textTabInactive tabBar.backgroundColor = AppColor.cardBackground tabBar.shadowImage = UIImage() let normalAttributes: [NSAttributedString.Key: Any] = [ .foregroundColor: AppColor.textTabInactive, .font: UIFont.app(.caption), ] let selectedAttributes: [NSAttributedString.Key: Any] = [ .foregroundColor: AppColor.primary, .font: UIFont.app(.captionMedium), ] let appearance = UITabBarAppearance() appearance.configureWithOpaqueBackground() appearance.backgroundColor = AppColor.cardBackground appearance.shadowColor = AppColor.tabBarShadow appearance.stackedLayoutAppearance.normal.titleTextAttributes = normalAttributes appearance.stackedLayoutAppearance.selected.titleTextAttributes = selectedAttributes tabBar.standardAppearance = appearance tabBar.scrollEdgeAppearance = appearance } private func configureTabs() { tabNavigationControllers.removeAll() viewControllers = TabSlot.allCases.map { slot in if let tab = slot.appTab { let navigationController = TabNavigationController(tab: tab) navigationController.tabBarItem = tab.makeTabBarItem() tabNavigationControllers[tab] = navigationController return navigationController } let scannerPlaceholder = UIViewController() scannerPlaceholder.tabBarItem = MainScanTabItem.makeTabBarItem() return scannerPlaceholder } } private func bindBadgeViewModel() { badgeViewModel.onChange = { [weak self] in self?.updateOrderBadge() } } private func updateOrderBadge() { let badgeValue: String? if let count = badgeViewModel.pendingWriteOffCount, count > 0 { badgeValue = "\(count)" } else { badgeValue = nil } tabNavigationControllers[.orders]?.tabBarItem.badgeValue = badgeValue } private func bindScanHandler() { scanHandler.onShowMessage = { [weak self] message in self?.showScanToast(message) } scanHandler.onShowSignIn = { [weak self] response in guard let self else { return } OrderSignInAlertController.present(from: self, response: response) } scanHandler.onShowWriteOff = { [weak self] response in guard let self else { return } WriteOffResultAlertController.present(from: self, response: response) } scanHandler.onShowMultiVerifySheet = { [weak self] spots, selected in self?.presentMultiVerifySheet(spots: spots, selected: selected) } scanHandler.onNavigateBindAcquirer = { [weak self] saleUserId in self?.presentBindAcquirerAfterScan(saleUserId: saleUserId) } } private func presentBindAcquirerAfterScan(saleUserId: Int) { dismiss(animated: true) { [weak self] in guard let self else { return } let controller = BindAcquirerViewController(saleUserId: saleUserId) self.topNavigationController()?.pushViewController(controller, animated: true) } } private func topNavigationController() -> UINavigationController? { if let selected = selectedViewController as? UINavigationController { return selected } return selectedViewController?.navigationController } private func showScanToast(_ message: String) { let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert) present(alert, animated: true) DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { alert.dismiss(animated: true) } } private func presentMultiVerifySheet(spots: [MultiTravelScenicSpotEntity], selected: MultiTravelScenicSpotEntity?) { let controller = MultiVerifyScenicSpotViewController(spots: spots, selected: selected) controller.onConfirm = { [weak self] spot in guard let self, let pending = self.scanHandler.pendingMultiVerify else { return } Task { await self.scanHandler.confirmMultiVerify(pending: pending, spot: spot) } } let nav = UINavigationController(rootViewController: controller) nav.modalPresentationStyle = .pageSheet present(nav, animated: true) } private func presentGlobalScanner() { guard presentedViewController == nil else { return } let scanner = QRCodeScannerViewController() scanner.onScanResult = { [weak self] result in guard let self else { return } Task { await self.scanHandler.handleScanResult(result) } } let navigationController = UINavigationController(rootViewController: scanner) navigationController.modalPresentationStyle = .fullScreen present(navigationController, animated: true) } } extension MainTabBarController: UITabBarControllerDelegate { func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { guard viewControllers?.firstIndex(of: viewController) == TabSlot.scanIndex else { return true } if let onScanTabSelected { onScanTabSelected() } else { presentGlobalScanner() } return false } func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { guard !isSyncingTabSelection, let navigationController = viewController as? TabNavigationController else { return } if navigationController.appTab == .orders { badgeViewModel.refreshPendingWriteOffCount() } } }