之前的一个项目用到了自定义的TabBar,在这里做一下笔记。
大概效果是这样的:
中间一个凸出的圆形按钮,很像咸鱼和喜马拉雅的tabBar。
自定义tabBar通常的办法就是继承系统的UITabBarController,然后使用KVO的方式替换系统的tabBar实现。
self.setValue(self.tabbar, forKey: "tabBar")
1.自定义tabBar
接下来就是实现self.tabbar,同样继承UITabBar,重写layoutSubviews()方法,在这个里面重新布局item。这里有一个很重要的点,中间的圆形按钮不是barItem,它就是个button,所以要单独添加。
为什么要重写layoutSubViews方法而不是在init里面写布局?
因为init方法执行的时候,这些item还没有添加到tabBar上。
重写layoutSubviews:
override func layoutSubviews() {
super.layoutSubviews()
//重新布局barItem
for tabBarItem in self.subviews {
//布局centerButton
if String(describing:tabBarItem.self).contains("XDTabBarCenterButton") {
tabBarItem.center = CGPoint.init(x: self.center.x, y: self.centerButton.bounds.height/2 - 18)
}
//布局默认tabBarButton
debugPrint("tabBarClass:" + String(describing:tabBarItem.self))
if String(describing:tabBarItem.self).contains("UITabBarButton") {
var frame = tabBarItem.frame
frame.size.width = (self.bounds.width - self.centerButton.bounds.width)/CGFloat((self.items?.count)!)
if currentItemIndex < 2{//如果
frame.origin.x = CGFloat(currentItemIndex) * frame.size.width
}else{
frame.origin.x = CGFloat(currentItemIndex) * frame.size.width + self.centerButton.bounds.width
}
tabBarItem.frame = frame
currentItemIndex += 1
if currentItemIndex == self.items?.count{
currentItemIndex = 0
}
}
}
}
如果有更改tabBar背景图片的需要,建议这么写:
func addBg(){
//添加背景图片
let bg = UIImageView.init(frame: self.bounds)
bg.image = UIImage.init(named: "bg_tab")
self.addSubview(bg)
self.sendSubviewToBack(bg)
self.shadowImage = UIImage.init()
self.backgroundImage = UIImage.init()
}
为什么不能直接修改self.backgroundImage?
直接修改这个属性,在刘海屏上面会出现分割线。
点击中间的圆形按钮超出tabBar的部分,没有响应,怎么解决?
要解决这个问题,需要重写hitTest方法,让button响应这个事件,注释已经很相信,至于原理,下一篇再作记录:
//重写hitTest方法,让突出的部分也有点击效果
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
//这一个判断是关键,不判断的话push到其他页面,点击中间按钮的位置也是会有反应的,这样就不好了
//self.isHidden == NO 说明当前页面是有tabbar的,那么肯定是在导航控制器的根控制器页面
//在导航控制器根控制器页面,那么我们就需要判断手指点击的位置是否在中间按钮身上
//是的话让中间按钮自己处理点击事件,不是的话让系统去处理点击事件就可以了
if self.isHidden == false {
//将当前tabbar的触摸点转换坐标系,转换到中间按钮的身上,生成一个新的点
let newP = self.convert(point, to: self.centerButton)
//判断如果这个新的点是在中间按钮身上,那么处理点击事件最合适的view就是中间按钮
if self.centerButton.point(inside: newP, with: event){
return self.centerButton
}else{//如果点不在发布中间身上,直接让系统处理就可以了
return super.hitTest(point, with: event)
}
}else{//tabbar隐藏了,那么说明已经push到其他的页面了,这个时候还是让系统去判断最合适的view处理就好了
return super.hitTest(point, with: event)
}
}
完整的自定义tabBar的代码是这样子的:
import UIKit
protocol XDTabBarDelegate:UITabBarDelegate {
func XDTabBarCenterButtonClicked()
}
class XDTabBar: UITabBar,UITabBarDelegate {
var xdTabBarDelegate:XDTabBarDelegate?
fileprivate var screenWidth = UIScreen.main.bounds.width
fileprivate var screenHeight = UIScreen.main.bounds.height
/**
中间播放按钮宽度
*/
fileprivate var centerButtonWidth:CGFloat = 66
/**
中间播放按钮高度
*/
fileprivate var centerButtonHeight:CGFloat = 67
//中间button
fileprivate var centerButton:XDTabBarCenterButton = {
let button = XDTabBarCenterButton.init(frame: CGRect.init(x: 0, y: 0, width: 66, height: 67))
button.addTarget(self, action: #selector(centerButtonClicked), for: .touchUpInside)
return button
}()
fileprivate var currentItemIndex = 0
override init(frame: CGRect) {
super.init(frame: frame)
addBg()
addCenterButton()
}
//添加中间按钮
func addCenterButton(){
self.addSubview(self.centerButton)
}
func addBg(){
//添加背景图片
let bg = UIImageView.init(frame: self.bounds)
bg.image = UIImage.init(named: "bg_tab")
self.addSubview(bg)
self.sendSubviewToBack(bg)
self.shadowImage = UIImage.init()
self.backgroundImage = UIImage.init()
}
override func layoutSubviews() {
super.layoutSubviews()
//重新布局barItem
for tabBarItem in self.subviews {
//布局centerButton
if String(describing:tabBarItem.self).contains("XDTabBarCenterButton") {
tabBarItem.center = CGPoint.init(x: self.center.x, y: self.centerButton.bounds.height/2 - 18)
}
//布局默认tabBarButton
debugPrint("tabBarClass:" + String(describing:tabBarItem.self))
if String(describing:tabBarItem.self).contains("UITabBarButton") {
var frame = tabBarItem.frame
frame.size.width = (self.bounds.width - self.centerButton.bounds.width)/CGFloat((self.items?.count)!)
if currentItemIndex < 2{//如果
frame.origin.x = CGFloat(currentItemIndex) * frame.size.width
}else{
frame.origin.x = CGFloat(currentItemIndex) * frame.size.width + self.centerButton.bounds.width
}
tabBarItem.frame = frame
currentItemIndex += 1
if currentItemIndex == self.items?.count{
currentItemIndex = 0
}
}
}
}
//重写hitTest方法,让突出的部分也有点击效果
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
//这一个判断是关键,不判断的话push到其他页面,点击中间按钮的位置也是会有反应的,这样就不好了
//self.isHidden == NO 说明当前页面是有tabbar的,那么肯定是在导航控制器的根控制器页面
//在导航控制器根控制器页面,那么我们就需要判断手指点击的位置是否在中间按钮身上
//是的话让中间按钮自己处理点击事件,不是的话让系统去处理点击事件就可以了
if self.isHidden == false {
//将当前tabbar的触摸点转换坐标系,转换到中间按钮的身上,生成一个新的点
let newP = self.convert(point, to: self.centerButton)
//判断如果这个新的点是在中间按钮身上,那么处理点击事件最合适的view就是中间按钮
if self.centerButton.point(inside: newP, with: event){
return self.centerButton
}else{//如果点不在发布中间身上,直接让系统处理就可以了
return super.hitTest(point, with: event)
}
}else{//tabbar隐藏了,那么说明已经push到其他的页面了,这个时候还是让系统去判断最合适的view处理就好了
return super.hitTest(point, with: event)
}
}
@objc func centerButtonClicked(){
debugPrint("中间按钮点击了")
if self.xdTabBarDelegate != nil {
self.xdTabBarDelegate?.XDTabBarCenterButtonClicked()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2.自定义tabBarController
这个自定义的tabBarController继承自系统的UITabBarController,为了让外部使用最接近系统的tabBarController,可以提供一个类似于系统的添加vc的方法:
func addChildController(childController:UIViewController,title:String,normalImage:String,selectedImage:String){
childController.tabBarItem.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -3)
childController.tabBarItem.title = title
childController.tabBarItem.image = UIImage.init(named: normalImage)?.withRenderingMode(.alwaysOriginal)
childController.tabBarItem.selectedImage = UIImage.init(named: selectedImage)?.withRenderingMode(.alwaysOriginal)
childController.tabBarItem.setTitleTextAttributes([NSAttributedString.Key.font:UIFont.init(name: XDFont.pingFangSCMedium.rawValue, size: 10)!,NSAttributedString.Key.foregroundColor:UIColor.colorWithHexString(hex: "#333334")], for: .selected)
childController.tabBarItem.setTitleTextAttributes([NSAttributedString.Key.font:UIFont.init(name: XDFont.pingFangSCMedium.rawValue, size: 10)!,NSAttributedString.Key.foregroundColor:UIColor.colorWithHexString(hex: "#BDBDBD")], for: .normal)
childController.tabBarItem.tag = tabBarItemTag
tabBarItemTag += 1
tabBarItems.append(childController.tabBarItem)
self.addChild(childController)
}
剩下的就是一些配置项,完整代码如下:
import UIKit
@objc protocol XDTabBarControllerDelegate:NSObjectProtocol {
@objc optional func XDTabBarControllerDidSelectedItemAtIndex(tabBarController:XDTabBarController,index:Int)//选择item
@objc optional func XDTabBarControllerDidSelectedCenterButton(tabBarController:XDTabBarController)//点击中间按钮
}
class XDTabBarController: UITabBarController,XDTabBarDelegate {
var xdTabBarControllerDelegate:XDTabBarControllerDelegate?{
didSet{
let de = xdTabBarControllerDelegate
delegates.append(de!)
}
}
fileprivate var delegates:Array = []
//当前选中的是第几个item
var currentItemIndex = 0
fileprivate var screenHeight = UIScreen.main.bounds.height
fileprivate var screenWidth = UIScreen.main.bounds.width
fileprivate var tabBarItems:Array = []
fileprivate var tabBarItemTag = 0
fileprivate lazy var tabbar:XDTabBar = {
var height:CGFloat = 49
if UIScreen.main.bounds.size.height >= 812{
height = 49 + 34
}
let tab = XDTabBar.init(frame: CGRect.init(x: 0, y: UIScreen.main.bounds.height - height, width: UIScreen.main.bounds.width, height: height))
tab.xdTabBarDelegate = self
return tab
}()
override func viewDidLoad() {
super.viewDidLoad()
//添加自定义的tabBar
addTabBar()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
fileprivate func addTabBar(){
self.setValue(self.tabbar, forKey: "tabBar")
}
func addChildController(childController:UIViewController,title:String,normalImage:String,selectedImage:String){
childController.tabBarItem.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -3)
childController.tabBarItem.title = title
childController.tabBarItem.image = UIImage.init(named: normalImage)?.withRenderingMode(.alwaysOriginal)
childController.tabBarItem.selectedImage = UIImage.init(named: selectedImage)?.withRenderingMode(.alwaysOriginal)
childController.tabBarItem.setTitleTextAttributes([NSAttributedString.Key.font:UIFont.init(name: XDFont.pingFangSCMedium.rawValue, size: 10)!,NSAttributedString.Key.foregroundColor:UIColor.colorWithHexString(hex: "#333334")], for: .selected)
childController.tabBarItem.setTitleTextAttributes([NSAttributedString.Key.font:UIFont.init(name: XDFont.pingFangSCMedium.rawValue, size: 10)!,NSAttributedString.Key.foregroundColor:UIColor.colorWithHexString(hex: "#BDBDBD")], for: .normal)
childController.tabBarItem.tag = tabBarItemTag
tabBarItemTag += 1
tabBarItems.append(childController.tabBarItem)
self.addChild(childController)
}
/**
*TabBarDelegate
**/
func XDTabBarCenterButtonClicked() {
//点击了中间按钮\
if self.delegates.count != 0 {
debugPrint("点击了中间按钮")
self.delegates.forEach({ (dele) in
dele.XDTabBarControllerDidSelectedCenterButton!(tabBarController: self)
})
}
}
/**
获取当前显示的viewController
*/
func getCurrentViewController() -> UIViewController{
let rootController = UIApplication.shared.keyWindow?.rootViewController
if let tabController = rootController as? UITabBarController {
if let navController = tabController.selectedViewController as? UINavigationController{
return navController.children.last!
}else{
return tabController
}
}else if let navController = rootController as? UINavigationController {
return navController.children.last!
}else{
return rootController!
}
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
// super.tabBar(tabBar, didSelect: item)
if self.delegates.count != 0 {
currentItemIndex = self.tabBarItems.index(of: item)!
self.delegates.forEach({ (dele) in
dele.XDTabBarControllerDidSelectedItemAtIndex!(tabBarController: self, index: currentItemIndex)
})
}
}
// 隐藏状态栏
override var prefersStatusBarHidden: Bool {
get {
return false
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
外部调用:
let tabBarController = XDTabBarController()
let homeNV:UINavigationController = UINavigationController.init(rootViewController: ViewController())
let subScribeNV:UINavigationController = UINavigationController.init(rootViewController: SecondViewController())
let personalNV:UINavigationController = UINavigationController.init(rootViewController: ThirdViewController())
let attentionNV:UINavigationController = UINavigationController.init(rootViewController: FourthViewController())
tabBarController.addChildController(childController: homeNV, title: "精选", normalImage: "icon_tab_精选常态", selectedImage: "icon_tab_精选点击态")
tabBarController.addChildController(childController: subScribeNV, title: "关注", normalImage: "icon_tab_频道常态", selectedImage: "icon_tab_频道点击态")
tabBarController.addChildController(childController: personalNV, title: "订阅", normalImage: "icon_tab_订阅常态", selectedImage: "icon_tab_订阅点击态")
tabBarController.addChildController(childController: attentionNV, title: "我的", normalImage: "icon_tab_我的常态", selectedImage: "icon_tab_我的点击态")
最后,demo地址:https://github.com/a1259667899/XDTabBarController