自定义TabBar的实现

之前的一个项目用到了自定义的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

你可能感兴趣的:(自定义TabBar的实现)