iOS 自定义TabBar的正确姿势

iOS 自定义TabBar的正确姿势

看默认风格的 TabBar 久了未免会觉得有些审美疲劳,于是就想自定义 TabBar,加一些小动画。自定义 TabBar 并不困难,无非就是写一个 UITabBarController 的子类,然后在 storyboard 中设置一下嘛。事实上,我之前也写过这样的一个小 demo,放在了 gitlab 上:TabBarAnimation 。这次想在自己的小项目上应用一下,美滋滋地直接把代码拷贝过来,却出现了不少问题。经过一番周折,终于发现了自定义 TabBar 的正确姿势。

在 Demo 中的实现思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CSTabBarController: UITabBarController {
var imageViews = [UIImageView]()
let mainView = UIView()

override func viewDidLoad() {
super.viewDidLoad()
mainView.frame = self.tabBar.bounds
self.view.addSubview(mainView)
//balabal......
self.tabBar.removeFromSuperview()
}

func onClickTabBarButton(sender:UIButton) {
//balabala......
}
}

思路很简单。既然是自定义 TabBar,而且没办法在原来的 BarItem 上修改,那我就把原来的 TabBar 移除,然后在 view 上新建一个 view 冒充 TabBar 嘛!只要把原来 Item 的位置上放上按钮,就足以以假乱真了。而且在 demo 中,这个方法的确奏效。

出现的问题 & 前期解决方案

在 demo 中,我只做了 TabBar 的小动画,比较简单,所以没有暴露出来这些隐藏的问题。而放在一个实际的项目中,这些问题就变得不可容忍了,主要的问题有:

  • TabBar 丢失了半透明、模糊效果。
  • 顶部缺少了一条浅灰色的分界线。
  • 即使设置了 hideBottomBarOnPush,TabBar 也不会隐藏。

作为一个优秀的开发者(大雾),这些小小的问题怎么能难倒我呢?既然丢失了模糊效果,那我就自己加一个 blurEffect layer 上去。缺少分界线,就自己画一条上去。不能隐藏这个问题比较麻烦,那就在每一个 viewWillAppear 中自己手动设置 isHide 嘛。虽然烦琐了点,但又不是不能用。

手动做完这些之后,本以为就没什么问题了。但显然我还是太天真了。前两个问题还好,第三个手动隐藏缺暴露了更多的问题,而且难以容忍:

  • 隐藏和显示是突然出现的,而系统默认的是有一个向左滑动的动画效果,显得太过突兀。
  • 进入下一级页面时(添加上去的自定义 view 刚刚隐藏),默认版本的 TabBar 突然出现了一下,即使已经把默认的 TabBar 从 superView 中移除了。虽然很细节,但如果用户仔细观察还是可以发现。
  • 默认状态下,右滑回到上一级的过程中,TabBar 是跟随上一级的视图一起滑动的,而在这里就直接出现了。
  • 如果在上面的状态下,用户一边滑动一边突然点按 TabBar 上的按钮,切换到另外的视图,那么 TabBar 就消失了。除非关闭程序再进入,否则就没有办法切换试图。虽然可能很少有用户这么做,但这对体验的影响非常大。

正确的姿势

其实解决方案非常简单,就是不要直接添加到 view 中,而是添加到 TabBar 中。这样无论是模糊效果、分界线还是自动隐藏,都与默认的逻辑一样了。这里特别需要注意的是,如果添加完了没有任何效果,检查一下是不是在设置 frame 的时候,不小心设置成了 TabBar 的 frame。因为是添加到 TabBar 上面,所以应该设置为 TabBar 的 bounds(而如果项之前那样添加到 view 上,就需要用 frame)。单这样还不够,会出现和默认的 TabBar 重叠在一起的情况。所以需要在 viewWillAppear 中移除所有的子控件,再重新添加。

完整的代码(点击后有弹性放大效果、无提示文字):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class CSTabBarController: UITabBarController {

var imageViews = [UIImageView]()
let mainView = UIView()

override func viewDidLoad() {
super.viewDidLoad()
mainView.frame = self.tabBar.bounds

mainView.backgroundColor = UIColor.clear

let itemWidth = mainView.frame.width / CGFloat(viewControllers!.count)

for i in 0..<viewControllers!.count {

let button = UIButton(frame: CGRect(x: itemWidth * CGFloat(i), y: 0, width: itemWidth, height: mainView.frame.height))
button.backgroundColor = UIColor.clear
button.tag = i
button.addTarget(self, action: #selector(CSTabBarController.onClickTabBarButton(sender:)), for: .touchUpInside)
mainView.addSubview(button)

var imageView = UIImageView()
switch i {
case 0:
imageView = UIImageView(image: UIImage(named: "home")?.withRenderingMode(.alwaysTemplate))
case 1:
imageView = UIImageView(image: UIImage(named: "schedule")?.withRenderingMode(.alwaysTemplate))
case 2:
imageView = UIImageView(image: UIImage(named: "setting")?.withRenderingMode(.alwaysTemplate))
default:
break
}
if i > 0 {
imageView.tintColor = UIColor.gray
} else {
imageView.tintColor = UIColor.flatGreen
}

imageView.frame = CGRect(x: button.frame.midX - 11, y: button.frame.midY - 11, width: 22, height: 22)
mainView.addSubview(imageView)
imageViews.append(imageView)
}
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let _ = tabBar.subviews.map({$0.removeFromSuperview()})
tabBar.addSubview(mainView)
}

func onClickTabBarButton(sender:UIButton) {
if self.selectedIndex == sender.tag {
return
}

for imageView in imageViews {
imageView.tintColor = UIColor.gray
}

self.selectedIndex = sender.tag

UIView.animate(withDuration: 1, animations: {
self.imageViews[sender.tag].tintColor = UIColor.flatGreen
})

let bigger = CABasicAnimation(keyPath: "transform.scale")
bigger.fromValue = 1
bigger.toValue = 1.3
bigger.duration = 0.1

let zoom = CASpringAnimation(keyPath: "transform.scale")
zoom.fromValue = 1.3
zoom.toValue = 1
zoom.duration = 0.5
zoom.damping = 5

imageViews[sender.tag].layer.add(bigger, forKey: nil)
zoom.beginTime = CACurrentMediaTime() + 0.1
imageViews[sender.tag].layer.add(zoom, forKey: nil)

}
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×