Files
suixinkan_ios_uikit/suixinkan_ios/Features/Main/ViewControllers/MainTabBarController.swift
汉秋 43179abf2c Fix TabBar icon assets and simplify Podfile post_install.
Rename tab icons to snake_case with @2x/@3x scales, register CYL plus button at app launch, and remove redundant Podfile hooks already covered by the Xcode project.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 18:49:37 +08:00

201 lines
7.2 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MainTabBarController.swift
// suixinkan
//
import CYLTabBarController
import UIKit
@MainActor
/// Tab 使 CYLTabBarController
final class MainTabBarController: CYLTabBarController {
private let services: AppServices
private let badgeViewModel = MainTabBadgeViewModel()
private var tabNavigationControllers: [AppTab: TabNavigationController] = [:]
private var isSyncingTabSelection = false
///
init(services: AppServices) {
self.services = services
super.init(nibName: nil, bundle: nil)
MainScanPlusButton.onScanTap = { [weak self] in
self?.presentGlobalScanner()
}
// delegate = self
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// UI
override func viewDidLoad() {
super.viewDidLoad()
configureTabBarAppearance()
configureTabs()
services.appNavigator.attach(host: self)
bindBadgeViewModel()
bindAccountContext()
Task { await refreshOrderBadge() }
#if DEBUG
Task { await AppUITestRouteDriver.applyIfNeeded(services: services) }
#endif
}
///
deinit {
Task { @MainActor in
MainScanPlusButton.onScanTap = nil
}
}
/// TabBar CustomMainTabBar
private func configureTabBarAppearance() {
tabBar.tintColor = AppDesign.primary
tabBar.backgroundColor = .white
tabBar.shadowImage = UIImage()
let normalAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor(hex: 0x7D8DA3),
.font: UIFont.systemFont(ofSize: 12, weight: .regular)
]
let selectedAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: AppDesign.primary,
.font: UIFont.systemFont(ofSize: 12, weight: .medium)
]
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .white
appearance.shadowColor = .clear
appearance.stackedLayoutAppearance.normal.titleTextAttributes = normalAttributes
appearance.stackedLayoutAppearance.selected.titleTextAttributes = selectedAttributes
tabBar.standardAppearance = appearance
tabBar.scrollEdgeAppearance = appearance
}
/// Tab CYLTabBarController Tab
private func configureTabs() {
let tabs = AppTab.allCases
tabBarItemsAttributes = tabs.map { makeTabBarItemAttributes(for: $0) }
viewControllers = tabs.map { tab in
let navigationController = TabNavigationController(tab: tab, services: services)
tabNavigationControllers[tab] = navigationController
return navigationController
}
}
/// CYLTabBarController TabBarItem
private func makeTabBarItemAttributes(for tab: AppTab) -> [String: String] {
[
CYLTabBarItemTitle: tab.title,
CYLTabBarItemImage: tab.unselectedImageName,
CYLTabBarItemSelectedImage: tab.selectedImageName
]
}
/// ViewModel
private func bindBadgeViewModel() {
badgeViewModel.onChange = { [weak self] in
self?.updateOrderBadge()
}
}
///
private func bindAccountContext() {
services.accountContext.onChange = { [weak self] in
Task { await self?.refreshOrderBadge() }
}
}
/// Tab
private func updateOrderBadge() {
let badgeValue: String?
if let count = badgeViewModel.pendingWriteOffCount, count > 0 {
badgeValue = "\(count)"
} else {
badgeValue = nil
}
tabNavigationControllers[.orders]?.tabBarItem.badgeValue = badgeValue
}
/// Tab
private func refreshOrderBadge() async {
await badgeViewModel.refreshPendingWriteOffCount(
api: services.ordersAPI,
scenicId: services.accountContext.currentScenic?.id,
storeId: services.accountContext.currentStore?.id
)
}
/// TabBar
private func presentGlobalScanner() {
let scanner = OrderCodeScannerViewController()
scanner.onScanResult = { [weak self, weak scanner] result in
scanner?.dismiss(animated: true)
guard let self else { return }
switch result {
case .success(let code):
self.services.appNavigator.openOrdersEntry(.verificationOrders, scannedCode: code)
case .failure(let error):
self.services.toastCenter.show(error.localizedDescription)
}
}
let navigationController = UINavigationController(rootViewController: scanner)
navigationController.modalPresentationStyle = .fullScreen
present(navigationController, animated: true)
}
/// TabBar
override func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard !isSyncingTabSelection,
let navigationController = viewController as? TabNavigationController else { return }
if navigationController.appTab == .orders {
Task { await refreshOrderBadge() }
}
}
}
extension MainTabBarController: AppNavigationHost {
/// Tab
func selectTab(_ tab: AppTab) {
guard let index = AppTab.allCases.firstIndex(of: tab) else { return }
guard selectedIndex != index else { return }
isSyncingTabSelection = true
selectedIndex = index
isSyncingTabSelection = false
if tab == .orders {
Task { await refreshOrderBadge() }
}
}
/// Tab
func navigationController(for tab: AppTab) -> TabNavigationController? {
tabNavigationControllers[tab]
}
/// Tab
func selectedNavigationController() -> TabNavigationController? {
selectedViewController as? TabNavigationController
}
/// Tab
func resetAllStacks() {
for navigationController in tabNavigationControllers.values {
navigationController.popToRootViewController(animated: false)
}
selectTab(.home)
}
///
func applyOrdersEntry(_ entry: OrdersEntry, scannedCode: String?) {
guard let ordersController = tabNavigationControllers[.orders]?.viewControllers.first as? OrdersViewController else {
return
}
ordersController.applyOrdersEntry(entry, scannedCode: scannedCode)
}
}