之前的一個項目用到了自定義的TabBar,在這里做一下筆記。
大概效果是這樣的:

WX20181212-140452@2x.png
中間一個凸出的圓形按鈕,很像咸魚和喜馬拉雅的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()方法,在這個里面重新布局item。這里有一個很重要的點(diǎn),中間的圓形按鈕不是barItem,它就是個button,所以要單獨(dú)添加。
為什么要重寫layoutSubViews方法而不是在init里面寫布局?
因為init方法執(zhí)行的時候,這些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?
直接修改這個屬性,在劉海屏上面會出現(xiàn)分割線。
點(diǎn)擊中間的圓形按鈕超出tabBar的部分,沒有響應(yīng),怎么解決?
要解決這個問題,需要重寫hitTest方法,讓button響應(yīng)這個事件,注釋已經(jīng)很相信,至于原理,下一篇再作記錄:
//重寫hitTest方法,讓突出的部分也有點(diǎn)擊效果
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
//這一個判斷是關(guān)鍵,不判斷的話push到其他頁面,點(diǎn)擊中間按鈕的位置也是會有反應(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)換到中間按鈕的身上,生成一個新的點(diǎn)
let newP = self.convert(point, to: self.centerButton)
//判斷如果這個新的點(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到其他的頁面了,這個時候還是讓系統(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? {
//這一個判斷是關(guān)鍵,不判斷的話push到其他頁面,點(diǎn)擊中間按鈕的位置也是會有反應(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)換到中間按鈕的身上,生成一個新的點(diǎn)
let newP = self.convert(point, to: self.centerButton)
//判斷如果這個新的點(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到其他的頁面了,這個時候還是讓系統(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
這個自定義的tabBarController繼承自系統(tǒng)的UITabBarController,為了讓外部使用最接近系統(tǒng)的tabBarController,可以提供一個類似于系統(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)
}
剩下的就是一些配置項,完整代碼如下:
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)前選中的是第幾個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)")