自定義TabBar的實(shí)現(xiàn)

之前的一個(gè)項(xiàng)目用到了自定義的TabBar征绎,在這里做一下筆記筒严。
大概效果是這樣的:


WX20181212-140452@2x.png

中間一個(gè)凸出的圓形按鈕扛芽,很像咸魚和喜馬拉雅的tabBar。
自定義tabBar通常的辦法就是繼承系統(tǒng)的UITabBarController,然后使用KVO的方式替換系統(tǒng)的tabBar實(shí)現(xiàn)叉弦。

self.setValue(self.tabbar, forKey: "tabBar")

1.自定義tabBar

接下來就是實(shí)現(xiàn)self.tabbar,同樣繼承UITabBar丐一,重寫layoutSubviews()方法,在這個(gè)里面重新布局item卸奉。這里有一個(gè)很重要的點(diǎn)钝诚,中間的圓形按鈕不是barItem,它就是個(gè)button榄棵,所以要單獨(dú)添加凝颇。

為什么要重寫layoutSubViews方法而不是在init里面寫布局?

因?yàn)閕nit方法執(zhí)行的時(shí)候疹鳄,這些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)
            }
            //布局默認(rèn)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?

直接修改這個(gè)屬性瘪弓,在劉海屏上面會(huì)出現(xiàn)分割線垫蛆。

點(diǎn)擊中間的圓形按鈕超出tabBar的部分,沒有響應(yīng)腺怯,怎么解決袱饭?

要解決這個(gè)問題,需要重寫hitTest方法呛占,讓button響應(yīng)這個(gè)事件虑乖,注釋已經(jīng)很相信,至于原理晾虑,下一篇再作記錄:

//重寫hitTest方法疹味,讓突出的部分也有點(diǎn)擊效果
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        //這一個(gè)判斷是關(guān)鍵,不判斷的話push到其他頁面帜篇,點(diǎn)擊中間按鈕的位置也是會(huì)有反應(yīng)的糙捺,這樣就不好了
        //self.isHidden == NO 說明當(dāng)前頁面是有tabbar的,那么肯定是在導(dǎo)航控制器的根控制器頁面
        //在導(dǎo)航控制器根控制器頁面笙隙,那么我們就需要判斷手指點(diǎn)擊的位置是否在中間按鈕身上
        //是的話讓中間按鈕自己處理點(diǎn)擊事件洪灯,不是的話讓系統(tǒng)去處理點(diǎn)擊事件就可以了
        if self.isHidden == false {
            //將當(dāng)前tabbar的觸摸點(diǎn)轉(zhuǎn)換坐標(biāo)系,轉(zhuǎn)換到中間按鈕的身上竟痰,生成一個(gè)新的點(diǎn)
            let newP = self.convert(point, to: self.centerButton)
            //判斷如果這個(gè)新的點(diǎn)是在中間按鈕身上签钩,那么處理點(diǎn)擊事件最合適的view就是中間按鈕
            if self.centerButton.point(inside: newP, with: event){
                return self.centerButton
            }else{//如果點(diǎn)不在發(fā)布中間身上,直接讓系統(tǒng)處理就可以了
                return super.hitTest(point, with: event)
            }
        }else{//tabbar隱藏了凯亮,那么說明已經(jīng)push到其他的頁面了边臼,這個(gè)時(shí)候還是讓系統(tǒng)去判斷最合適的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)
            }
            //布局默認(rèn)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方法哄尔,讓突出的部分也有點(diǎn)擊效果
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        //這一個(gè)判斷是關(guān)鍵假消,不判斷的話push到其他頁面,點(diǎn)擊中間按鈕的位置也是會(huì)有反應(yīng)的岭接,這樣就不好了
        //self.isHidden == NO 說明當(dāng)前頁面是有tabbar的富拗,那么肯定是在導(dǎo)航控制器的根控制器頁面
        //在導(dǎo)航控制器根控制器頁面臼予,那么我們就需要判斷手指點(diǎn)擊的位置是否在中間按鈕身上
        //是的話讓中間按鈕自己處理點(diǎn)擊事件,不是的話讓系統(tǒng)去處理點(diǎn)擊事件就可以了
        if self.isHidden == false {
            //將當(dāng)前tabbar的觸摸點(diǎn)轉(zhuǎn)換坐標(biāo)系啃沪,轉(zhuǎn)換到中間按鈕的身上粘拾,生成一個(gè)新的點(diǎn)
            let newP = self.convert(point, to: self.centerButton)
            //判斷如果這個(gè)新的點(diǎn)是在中間按鈕身上,那么處理點(diǎn)擊事件最合適的view就是中間按鈕
            if self.centerButton.point(inside: newP, with: event){
                return self.centerButton
            }else{//如果點(diǎn)不在發(fā)布中間身上创千,直接讓系統(tǒng)處理就可以了
                return super.hitTest(point, with: event)
            }
        }else{//tabbar隱藏了缰雇,那么說明已經(jīng)push到其他的頁面了,這個(gè)時(shí)候還是讓系統(tǒng)去判斷最合適的view處理就好了
            return super.hitTest(point, with: event)
        }
    }
    @objc  func centerButtonClicked(){
        debugPrint("中間按鈕點(diǎn)擊了")
        if self.xdTabBarDelegate != nil {
            self.xdTabBarDelegate?.XDTabBarCenterButtonClicked()
        }
    }
   
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

2.自定義tabBarController

這個(gè)自定義的tabBarController繼承自系統(tǒng)的UITabBarController追驴,為了讓外部使用最接近系統(tǒng)的tabBarController械哟,可以提供一個(gè)類似于系統(tǒng)的添加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)
    }

剩下的就是一些配置項(xiàng),完整代碼如下:

import UIKit
 @objc protocol XDTabBarControllerDelegate:NSObjectProtocol {
    @objc optional func XDTabBarControllerDidSelectedItemAtIndex(tabBarController:XDTabBarController,index:Int)//選擇item
    @objc optional func XDTabBarControllerDidSelectedCenterButton(tabBarController:XDTabBarController)//點(diǎn)擊中間按鈕
}
class XDTabBarController: UITabBarController,XDTabBarDelegate {
    var xdTabBarControllerDelegate:XDTabBarControllerDelegate?{
        didSet{
            let de = xdTabBarControllerDelegate
            delegates.append(de!)
        }
    }
    fileprivate var delegates:Array<XDTabBarControllerDelegate> = []
    //當(dāng)前選中的是第幾個(gè)item
    var currentItemIndex = 0
    fileprivate var screenHeight = UIScreen.main.bounds.height
    fileprivate var screenWidth = UIScreen.main.bounds.width
    fileprivate var tabBarItems:Array<UITabBarItem> = []
    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() {
        //點(diǎn)擊了中間按鈕\
        if self.delegates.count != 0 {
            debugPrint("點(diǎn)擊了中間按鈕")
            self.delegates.forEach({ (dele) in
                dele.XDTabBarControllerDidSelectedCenterButton!(tabBarController: self)
            })
        }
    }
    /**
     獲取當(dāng)前顯示的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)
            })
        }
    }
    // 隱藏狀態(tài)欄
    override var prefersStatusBarHidden: Bool {
        get {
            return false
        }
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

外部調(diào)用:

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_精選常態(tài)", selectedImage: "icon_tab_精選點(diǎn)擊態(tài)")
        tabBarController.addChildController(childController: subScribeNV, title: "關(guān)注", normalImage: "icon_tab_頻道常態(tài)", selectedImage: "icon_tab_頻道點(diǎn)擊態(tài)")
        tabBarController.addChildController(childController: personalNV, title: "訂閱", normalImage: "icon_tab_訂閱常態(tài)", selectedImage: "icon_tab_訂閱點(diǎn)擊態(tài)")
        tabBarController.addChildController(childController: attentionNV, title: "我的", normalImage: "icon_tab_我的常態(tài)", selectedImage: "icon_tab_我的點(diǎn)擊態(tài)")

最后殿雪,demo地址:https://github.com/a1259667899/XDTabBarController

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末暇咆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子丙曙,更是在濱河造成了極大的恐慌爸业,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亏镰,死亡現(xiàn)場(chǎng)離奇詭異扯旷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拆挥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門薄霜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纸兔,你說我怎么就攤上這事惰瓜。” “怎么了汉矿?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵崎坊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我洲拇,道長(zhǎng)奈揍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任赋续,我火速辦了婚禮男翰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纽乱。我一直安慰自己蛾绎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著租冠,像睡著了一般鹏倘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上顽爹,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天纤泵,我揣著相機(jī)與錄音,去河邊找鬼镜粤。 笑死捏题,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肉渴。 我是一名探鬼主播涉馅,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼黄虱!你這毒婦竟也來了稚矿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬榮一對(duì)情侶失蹤捻浦,失蹤者是張志新(化名)和其女友劉穎晤揣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朱灿,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昧识,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盗扒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跪楞。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖侣灶,靈堂內(nèi)的尸體忽然破棺而出甸祭,到底是詐尸還是另有隱情,我是刑警寧澤褥影,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布池户,位于F島的核電站,受9級(jí)特大地震影響凡怎,放射性物質(zhì)發(fā)生泄漏校焦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一统倒、第九天 我趴在偏房一處隱蔽的房頂上張望寨典。 院中可真熱鬧,春花似錦房匆、人聲如沸耸成。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墓猎。三九已至,卻和暖如春赚楚,著一層夾襖步出監(jiān)牢的瞬間毙沾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來泰國打工宠页, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留左胞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓举户,卻偏偏與公主長(zhǎng)得像烤宙,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俭嘁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 1躺枕、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,969評(píng)論 3 119
  • 在iOS原生的tabBar中,能夠?qū)崿F(xiàn)按鈕的點(diǎn)擊事件供填,能夠?qū)崿F(xiàn)視圖控制器的切換等拐云,但是在實(shí)際工程中,對(duì)于tabBa...
    請(qǐng)輸入賬號(hào)名閱讀 22,166評(píng)論 9 11
  • 你早已離開 我卻后知后覺 如飛逝的時(shí)光 如開敗的夏花 恍若流星一時(shí)的璀璨 像沒了感情的木偶 像只狼狽的落水狗 我沒...
    花少顏閱讀 201評(píng)論 1 3
  • 這個(gè)月內(nèi)的一些有趣的題 Letter Array http://codeforces.com/gym/100482...
    TimeMage閱讀 445評(píng)論 0 1
  • 文/Emily 有一個(gè)朋友近她,不滿足于眼前的生活和工作叉瘩,想要快速提升自己的能力,他看到有人辭掉高薪工作粘捎,靠全職寫作賺...
    Emily右耳閱讀 230評(píng)論 0 0