Files
real-e-party-iOS/YuMi/E-P/TabBar/EPTabBarController.swift.backup
2025-10-17 14:52:29 +08:00

535 lines
19 KiB
Plaintext
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.

//
// 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 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()
setupInitialViewControllers()
NSLog("[EPTabBarController] 悬浮 TabBar 初始化完成")
}
deinit {
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 }
}
}
/// 设置初始 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
// 获取 keyWindowiOS 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)
}
}