打造炫酷浮动式 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 组件在提升产品质感方面的重要作用。
有时候,细节处的精心设计,正是让产品与众不同的关键所在。
希望这篇文章对你的开发工作有所启发!完整代码已在上文提供,你可以直接集成到项目中。如果你有更好的想法或改进建议,欢迎在评论区交流讨论。