如何实现UITabBarItem位置变更时的垂直上下移动动画
问题描述
通过修改imageInsets和titlePositionAdjustment调整UITabBarItem位置时,添加UIView过渡动画后,UITabBarItem未按预期垂直移动,反而出现从左到右的横向过渡效果。
异常效果参考:
原有实现代码:
class TabBarViewController : UITabBarController, UITabBarControllerDelegate{ func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { let yChanges : CGFloat = 20 for i in 0 ... tabBarController.tabBar.items!.count - 1{ UIView.animate(withDuration: 0.2, animations: { let isSelected = (i == tabBarController.selectedIndex) let contentInset = UIEdgeInsets(top: isSelected ? -yChanges : 0, left: 0, bottom: isSelected ? yChanges : 0, right: 0) tabBarController.tabBar.items![i].imageInsets = contentInset tabBarController.tabBar.items![i].titlePositionAdjustment.vertical = (i == tabBarController.selectedIndex) ? -yChanges : 0 }) } } }
问题原因
UITabBarItem的imageInsets、titlePositionAdjustment不属于UIKit公开支持的可动画属性,把这两个属性的赋值放在UIView.animate闭包内时,系统不会生成垂直位移的帧插值动画,反而会触发UITabBar内置的选中项切换默认横向过渡逻辑,最终出现不符合预期的横向移动效果。
可行解决方案
不要直接对UITabBarItem的布局属性做动画,改为操作实际承载内容展示的UITabBar内部按钮视图的可动画属性,即可实现正常的垂直移动效果:
- 遍历
tabBar的子视图,按水平坐标排序后,得到和tabBarItem索引一一对应的按钮实例(即内部的UITabBarButton) - 直接对按钮的
transform属性做动画,该属性是UIKit明确支持隐式动画的,不会触发系统默认的横向过渡 - 动画过程中不要修改
imageInsets和titlePositionAdjustment,避免触发UITabBar默认布局刷新打断自定义动画
修正后代码
class TabBarViewController : UITabBarController, UITabBarControllerDelegate{ // 垂直移动距离 private let yOffset: CGFloat = 20 // 记录上一次选中的索引,默认初始选中第0项 private var lastSelectedIndex: Int = 0 override func viewDidLoad() { super.viewDidLoad() self.delegate = self } func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { let currentIndex = tabBarController.selectedIndex // 过滤出tabBar上的按钮,按x坐标排序保证和item索引顺序一致 let tabBarButtons = tabBarController.tabBar.subviews.filter { String(describing: type(of: $0)).contains("UITabBarButton") }.sorted { $0.frame.origin.x < $1.frame.origin.x } guard tabBarButtons.count == tabBarController.tabBar.items?.count else { return } UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) { // 恢复上一个选中按钮的位置 if self.lastSelectedIndex < tabBarButtons.count { tabBarButtons[self.lastSelectedIndex].transform = .identity } // 当前选中按钮做垂直向上偏移 if currentIndex < tabBarButtons.count { tabBarButtons[currentIndex].transform = CGAffineTransform(translationX: 0, y: -self.yOffset) } } completion: { _ in self.lastSelectedIndex = currentIndex } } }
补充说明
- 上述遍历子视图匹配UITabBarButton的方式是iOS开发常规实现方案,不涉及私有API调用,不会影响App Store审核
- 如果需要更精细的图标、文字偏移效果,可以自定义UITabBar子类重写布局逻辑,直接调整按钮内部imageView和titleLabel的位置,再配合transform动画,可控性比修改imageInsets更高
- 可以根据实际交互需求调整动画参数,比如添加弹簧阻尼效果让动效更自然
内容的提问来源于stack exchange,提问作者wingwing
相关产品推荐
相关产品推荐

