// // EPTabBarController.swift // YuMi // // Created by AI on 2025-10-09. // Copyright © 2025 YuMi. All rights reserved. // import UIKit import SnapKit /// EP 系列 TabBar 控制器 /// 悬浮设计 + 液态玻璃效果,只包含 Moment 和 Mine 两个 Tab @objc class EPTabBarController: UITabBarController { // MARK: - Properties /// 全局事件管理器 private var globalEventManager: GlobalEventManager? /// 是否已登录 private var isLoggedIn: Bool = false /// 自定义悬浮 TabBar 容器 private var customTabBarView: UIView! /// 毛玻璃背景视图 private var tabBarBackgroundView: UIVisualEffectView! /// Tab 按钮数组 private var tabButtons: [UIButton] = [] // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() // 测试域名配置 #if DEBUG APIConfig.testEncryption() #endif // 隐藏原生 TabBar self.tabBar.isHidden = true // 设置 delegate 以完全控制切换行为 self.delegate = self // ✅ 启动时验证 ticket(与 OC 版本保持一致) performAutoLogin() setupCustomFloatingTabBar() setupGlobalManagers() setupInitialViewControllers() NSLog("[EPTabBarController] 悬浮 TabBar 初始化完成") } deinit { globalEventManager?.removeAllDelegates() NSLog("[EPTabBarController] 已释放") } // MARK: - Setup /// 设置自定义悬浮 TabBar private func setupCustomFloatingTabBar() { // 创建悬浮容器 customTabBarView = UIView() customTabBarView.translatesAutoresizingMaskIntoConstraints = false customTabBarView.backgroundColor = .clear view.addSubview(customTabBarView) // 液态玻璃/毛玻璃效果 let effect: UIVisualEffect if #available(iOS 26.0, *) { // iOS 26+ 使用液态玻璃(Material) effect = UIGlassEffect() } else { // iOS 13-17 使用毛玻璃 effect = UIBlurEffect(style: .systemMaterial) } tabBarBackgroundView = UIVisualEffectView(effect: effect) tabBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false tabBarBackgroundView.layer.cornerRadius = 28 tabBarBackgroundView.layer.masksToBounds = true // 添加边框 tabBarBackgroundView.layer.borderWidth = 0.5 tabBarBackgroundView.layer.borderColor = UIColor.white.withAlphaComponent(0.2).cgColor customTabBarView.addSubview(tabBarBackgroundView) // 简化的布局约束(类似 Masonry 风格) customTabBarView.snp.makeConstraints { make in make.leading.equalTo(view).offset(16) make.trailing.equalTo(view).offset(-16) make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12) make.height.equalTo(64) } tabBarBackgroundView.snp.makeConstraints { make in make.edges.equalTo(customTabBarView) } // 添加 Tab 按钮 setupTabButtons() NSLog("[EPTabBarController] 悬浮 TabBar 设置完成") } /// 设置 Tab 按钮 private func setupTabButtons() { let momentButton = createTabButton( normalImage: "tab_moment_off", selectedImage: "tab_moment_on", tag: 0 ) let mineButton = createTabButton( normalImage: "tab_mine_off", selectedImage: "tab_mine_on", tag: 1 ) tabButtons = [momentButton, mineButton] let stackView = UIStackView(arrangedSubviews: tabButtons) stackView.axis = .horizontal stackView.distribution = .fillEqually stackView.spacing = 20 stackView.translatesAutoresizingMaskIntoConstraints = false tabBarBackgroundView.contentView.addSubview(stackView) stackView.snp.makeConstraints { make in make.top.equalTo(tabBarBackgroundView).offset(8) make.leading.equalTo(tabBarBackgroundView).offset(20) make.trailing.equalTo(tabBarBackgroundView).offset(-20) make.bottom.equalTo(tabBarBackgroundView).offset(-8) } // 默认选中第一个 updateTabButtonStates(selectedIndex: 0) } /// 创建 Tab 按钮 private func createTabButton(normalImage: String, selectedImage: String, tag: Int) -> UIButton { let button = UIButton(type: .custom) button.tag = tag button.adjustsImageWhenHighlighted = false // 禁用高亮效果,避免闪烁 // 尝试设置自定义图片,如果不存在则使用 SF Symbols if let normalImg = UIImage(named: normalImage), let selectedImg = UIImage(named: selectedImage) { // 正确设置:分别为 normal 和 selected 状态设置图片 button.setImage(normalImg, for: .normal) button.setImage(selectedImg, for: .selected) } else { // 使用 SF Symbols 作为备用 let fallbackIcons = ["sparkles", "person.circle"] let iconName = fallbackIcons[tag] let imageConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .medium) let normalIcon = UIImage(systemName: iconName, withConfiguration: imageConfig) button.setImage(normalIcon, for: .normal) button.setImage(normalIcon, for: .selected) button.tintColor = .white.withAlphaComponent(0.6) } // 图片渲染模式 button.imageView?.contentMode = .scaleAspectFit // 移除标题 button.setTitle(nil, for: .normal) button.setTitle(nil, for: .selected) // 设置图片大小约束 button.imageView?.snp.makeConstraints { make in make.size.equalTo(28) } button.addTarget(self, action: #selector(tabButtonTapped(_:)), for: .touchUpInside) return button } /// Tab 按钮点击事件 @objc private func tabButtonTapped(_ sender: UIButton) { let newIndex = sender.tag // 如果点击的是当前已选中的 tab,不做任何操作 if newIndex == selectedIndex { return } // 先更新按钮状态 updateTabButtonStates(selectedIndex: newIndex) // 禁用 UITabBarController 的默认切换动画,避免闪烁 UIView.performWithoutAnimation { selectedIndex = newIndex } let tabNames = [YMLocalizedString("tab.moment"), YMLocalizedString("tab.mine")] NSLog("[EPTabBarController] 选中 Tab: \(tabNames[newIndex])") } /// 更新 Tab 按钮状态 private func updateTabButtonStates(selectedIndex: Int) { // 禁用按钮交互,避免快速点击 tabButtons.forEach { $0.isUserInteractionEnabled = false } for (index, button) in tabButtons.enumerated() { let isSelected = (index == selectedIndex) // 直接设置 isSelected 属性即可,图片会自动切换 button.isSelected = isSelected // SF Symbols 的情况需要手动更新 tintColor if button.currentImage?.isSymbolImage == true { button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6) } // 选中状态缩放动画 UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut], animations: { button.transform = isSelected ? CGAffineTransform(scaleX: 1.1, y: 1.1) : .identity }) } // 延迟恢复按钮交互 DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { self.tabButtons.forEach { $0.isUserInteractionEnabled = true } } } /// 设置全局管理器 private func setupGlobalManagers() { globalEventManager = GlobalEventManager.shared() globalEventManager?.setupSDKDelegates() // TODO: v0.2 版本暂时禁用房间最小化视图(无房间功能) // 后续版本可通过 Build Configuration 或版本号判断是否启用 /* if let containerView = view { globalEventManager?.setupRoomMiniView(on: containerView) } */ // 注册社交分享回调 globalEventManager?.registerSocialShareCallback() NSLog("[EPTabBarController] 全局管理器设置完成(v0.2 - 无 MiniRoom)") } /// 设置初始 ViewController(未登录状态) private func setupInitialViewControllers() { // TODO: 暂时使用空白页面占位 let blankVC1 = UIViewController() blankVC1.view.backgroundColor = .white blankVC1.tabBarItem = createTabBarItem( title: YMLocalizedString("tab.moment"), normalImage: "tab_moment_normal", selectedImage: "tab_moment_selected" ) let blankVC2 = UIViewController() blankVC2.view.backgroundColor = .white blankVC2.tabBarItem = createTabBarItem( title: YMLocalizedString("tab.mine"), normalImage: "tab_mine_normal", selectedImage: "tab_mine_selected" ) viewControllers = [blankVC1, blankVC2] selectedIndex = 0 NSLog("[EPTabBarController] 初始 ViewControllers 设置完成") } /// 创建 TabBarItem /// - Parameters: /// - title: 标题 /// - normalImage: 未选中图标名称 /// - selectedImage: 选中图标名称 /// - Returns: UITabBarItem private func createTabBarItem(title: String, normalImage: String, selectedImage: String) -> UITabBarItem { let item = UITabBarItem( title: title, image: UIImage(named: normalImage)?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: selectedImage)?.withRenderingMode(.alwaysOriginal) ) return item } // MARK: - Public Methods /// 登录成功后刷新 TabBar /// - Parameter isLogin: 是否已登录 func refreshTabBar(isLogin: Bool) { isLoggedIn = isLogin if isLogin { setupLoggedInViewControllers() } else { setupInitialViewControllers() } NSLog("[EPTabBarController] TabBar 已刷新,登录状态: \(isLogin)") } /// 设置登录后的 ViewControllers private func setupLoggedInViewControllers() { // 只在 viewControllers 为空或不是正确类型时才创建 if viewControllers?.count != 2 || !(viewControllers?[0] is UINavigationController) || !(viewControllers?[1] is UINavigationController) { // 创建动态页 let momentVC = EPMomentViewController() momentVC.title = YMLocalizedString("tab.moment") let momentNav = createTransparentNavigationController( rootViewController: momentVC, tabTitle: YMLocalizedString("tab.moment"), normalImage: "tab_moment_normal", selectedImage: "tab_moment_selected" ) // 创建我的页 let mineVC = EPMineViewController() mineVC.title = YMLocalizedString("tab.mine") let mineNav = createTransparentNavigationController( rootViewController: mineVC, tabTitle: YMLocalizedString("tab.mine"), normalImage: "tab_mine_normal", selectedImage: "tab_mine_selected" ) viewControllers = [momentNav, mineNav] NSLog("[EPTabBarController] 登录后 ViewControllers 创建完成 - Moment & Mine") } selectedIndex = 0 } /// 创建透明导航控制器(统一配置) /// - Parameters: /// - rootViewController: 根视图控制器 /// - tabTitle: TabBar 标题 /// - normalImage: 未选中图标 /// - selectedImage: 选中图标 /// - Returns: 配置好的 UINavigationController private func createTransparentNavigationController( rootViewController: UIViewController, tabTitle: String, normalImage: String, selectedImage: String ) -> UINavigationController { let nav = UINavigationController(rootViewController: rootViewController) nav.navigationBar.isTranslucent = true nav.navigationBar.setBackgroundImage(UIImage(), for: .default) nav.navigationBar.shadowImage = UIImage() nav.view.backgroundColor = .clear nav.tabBarItem = createTabBarItem( title: tabTitle, normalImage: normalImage, selectedImage: selectedImage ) // 设置 delegate 以监听页面切换 nav.delegate = self return nav } // MARK: - TabBar Visibility Control /// 显示悬浮 TabBar private func showCustomTabBar(animated: Bool = true) { guard customTabBarView.isHidden else { return } if animated { customTabBarView.isHidden = false customTabBarView.alpha = 0 customTabBarView.transform = CGAffineTransform(translationX: 0, y: 20) UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) { self.customTabBarView.alpha = 1 self.customTabBarView.transform = .identity } } else { customTabBarView.isHidden = false customTabBarView.alpha = 1 } } /// 隐藏悬浮 TabBar private func hideCustomTabBar(animated: Bool = true) { guard !customTabBarView.isHidden else { return } if animated { UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn, animations: { self.customTabBarView.alpha = 0 self.customTabBarView.transform = CGAffineTransform(translationX: 0, y: 20) }) { _ in self.customTabBarView.isHidden = true self.customTabBarView.transform = .identity } } else { customTabBarView.isHidden = true customTabBarView.alpha = 0 } } } // MARK: - UITabBarControllerDelegate extension EPTabBarController: UITabBarControllerDelegate { override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { NSLog("[EPTabBarController] 选中 Tab: \(item.title ?? "Unknown")") } /// 禁用系统默认的切换动画 func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { // 返回 nil 表示不使用动画 return nil } /// 完全控制是否允许切换 func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { // 允许切换,但通过返回 nil 的 animationController 来禁用动画 return true } } // MARK: - UINavigationControllerDelegate extension EPTabBarController: UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { // 判断是否是根页面(一级页面) let isRootViewController = navigationController.viewControllers.count == 1 if isRootViewController { // 一级页面:显示 TabBar showCustomTabBar(animated: animated) NSLog("[EPTabBarController] 显示 TabBar - 根页面") } else { // 二级及以上页面:隐藏 TabBar hideCustomTabBar(animated: animated) NSLog("[EPTabBarController] 隐藏 TabBar - 子页面 (层级: \(navigationController.viewControllers.count))") } } } // MARK: - Auto Login & Ticket Validation extension EPTabBarController { /// 自动登录:验证 ticket 有效性(与 OC MainPresenter.autoLogin 保持一致) private func performAutoLogin() { // 1. 检查账号信息 guard let accountModel = AccountInfoStorage.instance().getCurrentAccountInfo() else { NSLog("[EPTabBarController] ⚠️ 账号信息不存在,跳转到登录页") handleTokenInvalid() return } // 2. 检查 uid 和 access_token let uid = accountModel.uid let accessToken = accountModel.access_token guard !uid.isEmpty, !accessToken.isEmpty else { NSLog("[EPTabBarController] ⚠️ uid 或 access_token 为空,跳转到登录页") handleTokenInvalid() return } // 3. 检查 ticket 是否已存在(内存缓存) let existingTicket = AccountInfoStorage.instance().getTicket() ?? "" if !existingTicket.isEmpty { NSLog("[EPTabBarController] ✅ Ticket 已存在,自动登录成功") return } // 4. Ticket 不存在,请求新的 ticket NSLog("[EPTabBarController] 🔄 Ticket 不存在,正在请求...") let loginService = EPLoginService() loginService.requestTicket(accessToken: accessToken) { ticket in NSLog("[EPTabBarController] ✅ Ticket 请求成功: \(ticket)") AccountInfoStorage.instance().saveTicket(ticket) } failure: { [weak self] code, msg in NSLog("[EPTabBarController] ❌ Ticket 请求失败 (\(code)): \(msg)") // ⚠️ Ticket 失败,强制退出登录(与 OC MainPresenter 保持一致) DispatchQueue.main.async { self?.handleTokenInvalid() } } } /// 处理 Token 失效:清空数据并跳转到登录页 private func handleTokenInvalid() { NSLog("[EPTabBarController] ⚠️ Token 失效,清空账号数据...") // 1. 清空账号信息 AccountInfoStorage.instance().saveAccountInfo(nil) AccountInfoStorage.instance().saveTicket("") // 2. 跳转到登录页 DispatchQueue.main.async { let loginVC = EPLoginViewController() let nav = BaseNavigationController(rootViewController: loginVC) nav.modalPresentationStyle = .fullScreen // 获取 keyWindow(iOS 13+ 兼容) if #available(iOS 13.0, *) { for scene in UIApplication.shared.connectedScenes { if let windowScene = scene as? UIWindowScene, windowScene.activationState == .foregroundActive, let window = windowScene.windows.first(where: { $0.isKeyWindow }) { window.rootViewController = nav window.makeKeyAndVisible() break } } } else { if let window = UIApplication.shared.keyWindow { window.rootViewController = nav window.makeKeyAndVisible() } } NSLog("[EPTabBarController] ✅ 已跳转到登录页") } } } // MARK: - OC Compatibility extension EPTabBarController { /// OC 兼容:创建实例的工厂方法 @objc static func create() -> EPTabBarController { return EPTabBarController() } /// OC 兼容:刷新 TabBar 方法 @objc func refreshTabBarWithIsLogin(_ isLogin: Bool) { refreshTabBar(isLogin: isLogin) } }