打造炫酷浮动式 TabBar:让 iOS 应用导航更有格调!

打造炫酷浮动式 TabBar:让 iOS 应用导航更有格调!

在 iOS 应用开发中,TabBar 作为核心导航组件,其用户体验直接影响着整个应用的使用感受。传统的 TabBar 虽然功能完善,但在视觉表现上往往缺乏新意。今天,我将带你实现一个极具视觉冲击力的浮动式 TabBar,让你的应用在众多竞品中脱颖而出!

🌟 效果预览

这个自定义的浮动 TabBar 具备以下特色:

  • 毛玻璃背景效果 - 采用系统材质背景,通透而不失质感
  • 优雅的圆角设计 - 25pt 圆角,柔和的视觉边界
  • 精致的阴影效果 - 多层次阴影,营造悬浮立体感
  • 流畅的动画交互 - 点击时的缩放动画,反馈明确
  • 可自定义的徽章系统 - 红点提醒,用户体验更完善

🛠️ 核心实现

浮动 TabBar 完整实现

让我们深入探讨 FloatingTabBar 的核心代码:

swift 复制代码
import UIKit
import SnapKit

/// 浮动标签栏 - 继承自UITabBar的自定义标签栏
/// 特点:
/// - 支持毛玻璃效果和圆角
/// - 支持自定义动画和徽章
class FloatingTabBar: UITabBar {
    
    // MARK: - Properties
    private var customTabItems: [FloatingTabBarItem] = []
    private var stackView: UIStackView!
    private var blurEffectView: UIVisualEffectView!
    private var customSelectedIndex: Int = 0
    
    // 代理
    weak var floatingTabBarDelegate: FloatingTabBarDelegate?
    
    // 自定义属性
    var cornerRadius: CGFloat = 25 {
        didSet {
            blurEffectView.layer.cornerRadius = cornerRadius
        }
    }
    
    var blurAlpha: CGFloat = 0.9 {
        didSet {
            blurEffectView.alpha = blurAlpha
        }
    }
    
    var shadowColor: UIColor = .black {
        didSet {
            layer.shadowColor = shadowColor.cgColor
        }
    }
    
    var shadowOffset: CGSize = CGSize(width: 0, height: 4) {
        didSet {
            layer.shadowOffset = shadowOffset
        }
    }
    
    var shadowRadius: CGFloat = 12 {
        didSet {
            layer.shadowRadius = shadowRadius
        }
    }
    
    var shadowOpacity: Float = 0.15 {
        didSet {
            layer.shadowOpacity = shadowOpacity
        }
    }
    
    // MARK: - Override UITabBar Methods
    override var items: [UITabBarItem]? {
        get {
            return []
        }
        set {}
    }
    
    // MARK: - Initialization
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupUI()
    }
    
    // MARK: - UI Setup
    private func setupUI() {
        // 设置背景
        backgroundColor = .clear
        isUserInteractionEnabled = true // 确保整个TabBar可以交互
        
        // 隐藏系统默认的tabBar背景
        shadowImage = UIImage()
        backgroundImage = UIImage()
        backgroundColor = .clear
        
        // 创建毛玻璃效果
        let blurEffect = UIBlurEffect(style: .systemMaterialDark)
        blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.layer.cornerRadius = 25
        blurEffectView.layer.masksToBounds = true
        blurEffectView.alpha = 0.9
        blurEffectView.isUserInteractionEnabled = false // 毛玻璃效果不接收点击
        addSubview(blurEffectView)
        
        // 创建StackView
        stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.distribution = .fillEqually
        stackView.alignment = .center
        stackView.spacing = 0
        stackView.isUserInteractionEnabled = true // StackView可以接收点击
        addSubview(stackView)
        
        // 设置约束
        blurEffectView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        stackView.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12))
        }
        
        // 设置阴影
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0, height: 4)
        layer.shadowRadius = 12
        layer.shadowOpacity = 0.15
    }
    
    // MARK: - Public Methods
    func configure(with items: [FloatingTabBarItem]) {
        // 清除现有项
        customTabItems.forEach { $0.removeFromSuperview() }
        customTabItems.removeAll()
        stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
        
        customTabItems = items
        
        for (index, item) in items.enumerated() {
            item.tag = index
            item.addTarget(self, action: #selector(tabButtonTapped(_:)), for: .touchUpInside)
            stackView.addArrangedSubview(item)
        }
        
        // 默认选中第一个
        if !items.isEmpty {
            selectItem(at: 0, animated: false)
        }
    }
    
    func selectItem(at index: Int, animated: Bool = true) {
        guard index >= 0 && index < customTabItems.count else { return }
        
        let previousIndex = customSelectedIndex
        customSelectedIndex = index
        
        // 更新选中状态
        for (i, item) in customTabItems.enumerated() {
            item.isSelected = (i == index)
            
            if animated {
                UIView.animate(withDuration: 0.3,
                               delay: 0,
                               usingSpringWithDamping: 0.8,
                               initialSpringVelocity: 0,
                               options: .curveEaseInOut,
                               animations: {
                })
            }
        }
        
        // 通知代理选中项发生变化
        if previousIndex != index {
            floatingTabBarDelegate?.floatingTabBar(self, didSelectItemAt: index)
        }
    }
    
    @objc private func tabButtonTapped(_ sender: UIButton) {
        let index = sender.tag
        selectItem(at: index)
    }
    
    func setBadge(visible: Bool, at index: Int) {
        guard index >= 0 && index < customTabItems.count else { return }
        let item = customTabItems[index]
        item.showBadge = visible
    }
}

// MARK: - FloatingTabBarDelegate Protocol
protocol FloatingTabBarDelegate: AnyObject {
    /// 当TabBar项被选中时调用
    /// - Parameters:
    ///   - tabBar: 触发事件的TabBar
    ///   - index: 被选中的项的下标
    func floatingTabBar(_ tabBar: FloatingTabBar, didSelectItemAt index: Int)
}

自定义 TabBar 项目实现

每个 TabBar 项目都是一个精心设计的按钮:

swift 复制代码
// MARK: - FloatingTabBarItem
class FloatingTabBarItem: UIButton {
    
    var normalImage: UIImage? {
        didSet {
            // 设置图片时自动使用原始渲染模式
            setImage(normalImage?.withRenderingMode(.alwaysOriginal), for: .normal)
            updateAppearance()
        }
    }
    
    var selectedImage: UIImage? {
        didSet {
            // 设置图片时自动使用原始渲染模式
            setImage(selectedImage?.withRenderingMode(.alwaysOriginal) ?? normalImage?.withRenderingMode(.alwaysOriginal), for: .selected)
            updateAppearance()
        }
    }
    
    var showBadge: Bool = false {
        didSet {
            updateBadge()
        }
    }
    
    var normalTintColor: UIColor = .clear {
        didSet {
            updateAppearance()
        }
    }
    
    var selectedTintColor: UIColor = .clear {
        didSet {
            updateAppearance()
        }
    }
    
    
    private var badgeView: UIView?
    
    init(normalImage: UIImage?, selectedImage: UIImage?) {
        self.normalImage = normalImage
        self.selectedImage = selectedImage
        super.init(frame: .zero)
        setupItem()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupItem()
    }
    
    private func setupItem() {
        backgroundColor = .clear
        
        // 关键:禁用按钮的tintColor,防止图片被染色
        tintColor = .clear
        
        // 设置按钮状态图片 - 使用原始渲染模式避免被染色
        setImage(normalImage?.withRenderingMode(.alwaysOriginal), for: .normal)
        setImage(selectedImage?.withRenderingMode(.alwaysOriginal) ?? normalImage?.withRenderingMode(.alwaysOriginal), for: .selected)
        
        // 设置按钮状态颜色
        setTitleColor(normalTintColor, for: .normal)
        setTitleColor(selectedTintColor, for: .selected)
        
        // 初始状态
        isSelected = false
        updateAppearance()
    }
    
    override var isSelected: Bool {
        didSet {
            if oldValue != isSelected {
                updateAppearance()
            }
        }
    }
    
    func updateAppearance() {
        // 重新设置图片(确保使用原始渲染模式)
        setImage(normalImage?.withRenderingMode(.alwaysOriginal), for: .normal)
        setImage(selectedImage?.withRenderingMode(.alwaysOriginal) ?? normalImage?.withRenderingMode(.alwaysOriginal), for: .selected)
        
        // 动画效果
        UIView.animate(withDuration: 0.3,
                       delay: 0,
                       usingSpringWithDamping: 0.7,
                       initialSpringVelocity: 0,
                       options: .curveEaseInOut,
                       animations: {
            self.transform = self.isSelected ? CGAffineTransform(scaleX: 1.15, y: 1.15) : .identity
        })
    }
    
    private func updateBadge() {
        if showBadge {
            if badgeView == nil {
                let badge = UIView()
                badge.backgroundColor = .red
                badge.layer.cornerRadius = 4
                addSubview(badge)
                badge.snp.makeConstraints { make in
                    make.top.equalToSuperview().offset(2)
                    make.trailing.equalToSuperview().offset(-2)
                    make.width.height.equalTo(8)
                }
                badgeView = badge
                
                // 添加动画
                badge.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
                UIView.animate(withDuration: 0.3,
                               delay: 0,
                               usingSpringWithDamping: 0.7,
                               initialSpringVelocity: 0,
                               options: .curveEaseInOut,
                               animations: {
                    badge.transform = .identity
                })
            }
        } else {
            badgeView?.removeFromSuperview()
            badgeView = nil
        }
    }
}

🚀 集成到主控制器

TabBarPage 中,我们通过 KVC 巧妙地替换系统 TabBar:

swift 复制代码
// MARK: - 主TabBar页面控制器
class TabBarPage: UITabBarController {
    
    // MARK: - 私有属性
    private var customTabBarItems: [FloatingTabBarItem] = []
    private let floatingTabBar = FloatingTabBar()
    
    // MARK: - 生命周期方法
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupCustomTabBar()
        setupViewControllers()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        updateTabBarFrame()
    }
    
    // MARK: - 初始化设置
    /// 设置自定义TabBar
    private func setupCustomTabBar() {
        // 使用KVC替换系统tabBar
        setValue(floatingTabBar, forKey: "tabBar")
        configureTabBarAppearance()
    }
    
    /// 配置TabBar外观
    private func configureTabBarAppearance() {
        floatingTabBar.cornerRadius = 25
        floatingTabBar.blurAlpha = 0.95
        floatingTabBar.shadowColor = .black
        floatingTabBar.shadowOffset = CGSize(width: 0, height: 4)
        floatingTabBar.shadowRadius = 12
        floatingTabBar.shadowOpacity = 0.2
        floatingTabBar.floatingTabBarDelegate = self
    }
    
    /// 更新TabBar的frame
    private func updateTabBarFrame() {
        floatingTabBar.frame = CGRect(
            x: (view.bounds.width - 280) / 2,
            y: view.bounds.height - 100,
            width: 280,
            height: 60
        )
    }
    
    // MARK: - 页面配置
    /// 设置视图控制器
    private func setupViewControllers() {
        // 配置视图控制器和TabBar项目
        let viewControllers = [UIViewController]() // 省略具体实现
        self.viewControllers = viewControllers
        
        floatingTabBar.configure(with: customTabBarItems)
        delegate = self
    }
}

extension TabBarPage: FloatingTabBarDelegate{
    func floatingTabBar(_ tabBar: FloatingTabBar, didSelectItemAt index: Int) {
        selectedIndex = index
    }
}

💡 技术亮点解析

1. 毛玻璃效果的艺术

swift 复制代码
let blurEffect = UIBlurEffect(style: .systemMaterialDark)
blurEffectView = UIVisualEffectView(effect: blurEffect)

使用系统材质背景,既保证了视觉效果的一致性,又确保了性能优化。

2. 完美的阴影层次

swift 复制代码
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 4)
layer.shadowRadius = 12
layer.shadowOpacity = 0.15

通过精心调整的阴影参数,创造出自然的悬浮感。

3. 流畅的动画体验

swift 复制代码
UIView.animate(withDuration: 0.3,
               delay: 0,
               usingSpringWithDamping: 0.7,
               initialSpringVelocity: 0,
               options: .curveEaseInOut,
               animations: {
    self.transform = self.isSelected ? 
        CGAffineTransform(scaleX: 1.15, y: 1.15) : .identity
})

弹簧动画让交互更加生动自然。

4. 灵活的配置系统

通过可配置的属性,可以轻松调整 TabBar 的外观:

  • cornerRadius - 圆角大小
  • blurAlpha - 毛玻璃透明度
  • 阴影相关属性 - 控制立体感

🎯 扩展思路

这个浮动 TabBar 还有很多可以扩展的方向:

  • 动态背景色:根据页面内容动态调整背景效果
  • 复杂徽章:支持数字徽章和自定义样式的徽章
  • 长按菜单:为每个 TabBar 项目添加 3D Touch 式菜单
  • 拖拽排序:允许用户自定义 TabBar 项目的顺序

结语

这个浮动式 TabBar 不仅提升了应用的视觉吸引力,更重要的是提供了更加流畅和愉悦的用户体验。通过完整的代码实现,你可以看到自定义 UI 组件在提升产品质感方面的重要作用。

有时候,细节处的精心设计,正是让产品与众不同的关键所在。


希望这篇文章对你的开发工作有所启发!完整代码已在上文提供,你可以直接集成到项目中。如果你有更好的想法或改进建议,欢迎在评论区交流讨论。

相关推荐
AAA阿giao3 小时前
Promise:让 JavaScript 异步任务“同步化”的利器
前端·javascript·promise
光影少年3 小时前
vite7更新了哪些内容
前端
六月的可乐3 小时前
前端自定义右键菜单与图片复制(兼容H5)
前端
浮游本尊4 小时前
React 18.x 学习计划 - 第八天:React测试
前端·学习·react.js
麦麦在写代码4 小时前
前端学习1
前端·学习
sg_knight4 小时前
微信小程序中 WebView 组件的使用与应用场景
前端·javascript·微信·微信小程序·小程序·web·weapp
2501_940094025 小时前
索尼PSP游戏资源下载 推荐中文汉化ios格式合集分享开源掌机模拟器都支持
游戏·ios·cocoa
凯子坚持 c5 小时前
生产级 Rust Web 应用架构:使用 Axum 实现模块化设计与健壮的错误处理
前端·架构·rust