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