CYLTabBarController【低耦合集成TabBarController】
<p align="center">
</a>

</a>
導(dǎo)航
- 與其他自定義TabBarController的區(qū)別
- 集成后的效果
- 項(xiàng)目結(jié)構(gòu)
- 使用CYLTabBarController
- 第一步:使用CocoaPods導(dǎo)入CYLTabBarController
- 第二步:設(shè)置CYLTabBarController的兩個(gè)數(shù)組:控制器數(shù)組和TabBar屬性數(shù)組
- 第三步:將CYLTabBarController設(shè)置為window的RootViewController
- 第四步(可選):創(chuàng)建自定義的形狀不規(guī)則加號(hào)按鈕
- 補(bǔ)充說明
- 自定義 TabBar 樣式
- 橫豎屏適配
- 訪問初始化好的 CYLTabBarController 對(duì)象
- 點(diǎn)擊 PlusButton 跳轉(zhuǎn)到指定 UIViewController
- 讓TabBarItem僅顯示圖標(biāo),并使圖標(biāo)垂直居中
- 在 Swift 項(xiàng)目中使用 CYLTabBarController
- 源碼實(shí)現(xiàn)原理
- Q-A
與其他自定義TabBarController的區(qū)別
| - | 特點(diǎn) | 解釋 |
|---|---|---|
| 1 | 低耦合,易刪除 | 1、TabBar設(shè)置與業(yè)務(wù)完全分離,最低只需傳兩個(gè)數(shù)組即可完成主流App框架搭建。</p> 2、 PlusButton 的所有設(shè)置都在單獨(dú)的一個(gè)類( CYLPlusButton 的子類)中實(shí)現(xiàn):刪除該特定的類,就能完全將 PlusButton 從項(xiàng)目中刪除掉。 |
| 2 |
TabBar 以及 TabBar 內(nèi)的 TabBarItem 均使用系統(tǒng)原生的控件 |
因?yàn)槭褂迷目丶?,并?UIButton 或 UIView 。好處如下:</p> 1. 無(wú)需反復(fù)調(diào)“間距位置等”來(lái)接近系統(tǒng)效果。</p> 2. 在push到下一頁(yè)時(shí) TabBar 的隱藏和顯示之間的過渡效果跟系統(tǒng)一致(詳見“ 集成后的效果 ”部分,給出了效果圖) </p> 3. 原生控件,所以可以使用諸多系統(tǒng)API,比如:可以使用 [UITabBar appearance]; 、[UITabBarItem appearance]; 設(shè)置樣式。(詳見“補(bǔ)充說明 ”部分,給出了響應(yīng)代碼示例) |
| 3 | 自動(dòng)監(jiān)測(cè)是否需要添加“加號(hào)”按鈕,</p>并能自動(dòng)設(shè)置位置 |
CYLTabBarController 既支持類似微信的“中規(guī)中矩”的 TabBarController 樣式,并且默認(rèn)就是微信這種樣式,同時(shí)又支持類似“微博”或“淘寶閑魚”這種具有不規(guī)則加號(hào)按鈕的 TabBarController 。想支持這種樣式,只需自定義一個(gè)加號(hào)按鈕,CYLTabBarController 能檢測(cè)到它的存在并自動(dòng)將 tabBar 排序好,無(wú)需多余操作,并且也預(yù)留了一定接口來(lái)滿足自定義需求。</p>“加號(hào)”按鈕的樣式、frame均在自定義的類中獨(dú)立實(shí)現(xiàn),不會(huì)涉及tabbar相關(guān)設(shè)置。 |
| 4 | 即使加號(hào)按鈕超出了tabbar的區(qū)域,</p>超出部分依然能響應(yīng)點(diǎn)擊事件 | 紅線內(nèi)的區(qū)域均能響應(yīng)tabbar相關(guān)的點(diǎn)擊事件,</p>![]() enter image description here
|
| 5 | 允許指定加號(hào)按鈕位置 | 效果如下:</p>![]() enter image description here
![]() enter image description here
|
| 6 | 支持讓 TabBarItem 僅顯示圖標(biāo),并自動(dòng)使圖標(biāo)垂直居中,支持自定義TabBar高度 |
效果可見Airbnb-app效果,或者下圖</p>![]() enter image description here
|
| 7 | 支持CocoaPods | 容易集成 |
| 8 | 支持Swift項(xiàng)目導(dǎo)入 | 兼容 |
| 9 | 支持橫豎屏 | -- |
(學(xué)習(xí)交流群:561873398)
集成后的效果:
| 既支持默認(rèn)樣式 | 同時(shí)也支持創(chuàng)建自定義的形狀不規(guī)則加號(hào)按鈕 |
|---|---|
![]() enter image description here
|
![]() enter image description here
|
支持橫豎屏

| 本倉(cāng)庫(kù)配套Demo的效果: | 另一個(gè)Demo 使用CYLTabBarController實(shí)現(xiàn)了微博Tabbar框架,效果如下 |
|---|---|
![]() enter image description here
|
![]() enter image description here
|
項(xiàng)目結(jié)構(gòu)

做下說明:
├── CYLTabBarController #核心庫(kù)文件夾,如果不使用 CocoaPods 集成,請(qǐng)直接將這個(gè)文件夾拖拽帶你的項(xiàng)目中
└── Example
└── Classes
├── Module #模塊類文件夾
│ ├── Home
│ ├── Message
│ ├── Mine
│ └── SameCity
└── View #這里放著 CYLPlusButton 的子類 CYLPlusButtonSubclass,演示了如何創(chuàng)建自定義的形狀不規(guī)則加號(hào)按鈕
使用CYLTabBarController
四步完成主流App框架搭建:
- 第一步:使用CocoaPods導(dǎo)入CYLTabBarController
- 第二步:設(shè)置CYLTabBarController的兩個(gè)數(shù)組:控制器數(shù)組和TabBar屬性數(shù)組
- 第三步:將CYLTabBarController設(shè)置為window的RootViewController
- 第四步(可選):創(chuàng)建自定義的形狀不規(guī)則加號(hào)按鈕
第一步:使用CocoaPods導(dǎo)入CYLTabBarController
在 Podfile 中進(jìn)行如下導(dǎo)入:
pod 'CYLTabBarController'
然后使用 cocoaPods 進(jìn)行安裝:
如果尚未安裝 CocoaPods, 運(yùn)行以下命令進(jìn)行安裝:
gem install cocoapods
安裝成功后就可以安裝依賴了:
建議使用如下方式:
# 禁止升級(jí)CocoaPods的spec倉(cāng)庫(kù),否則會(huì)卡在 Analyzing dependencies ,非常慢
pod update --verbose --no-repo-update
如果提示找不到庫(kù),則可去掉 --no-repo-update
pod update
第二步:設(shè)置CYLTabBarController的兩個(gè)數(shù)組:控制器數(shù)組和TabBar屬性數(shù)組
- (void)setupViewControllers {
CYLHomeViewController *firstViewController = [[CYLHomeViewController alloc] init];
UIViewController *firstNavigationController = [[UINavigationController alloc]
initWithRootViewController:firstViewController];
CYLSameFityViewController *secondViewController = [[CYLSameFityViewController alloc] init];
UIViewController *secondNavigationController = [[UINavigationController alloc]
initWithRootViewController:secondViewController];
CYLTabBarController *tabBarController = [[CYLTabBarController alloc] init];
[self customizeTabBarForController:tabBarController];
[tabBarController setViewControllers:@[
firstNavigationController,
secondNavigationController,
]];
self.tabBarController = tabBarController;
}
/*
*
在`-setViewControllers:`之前設(shè)置TabBar的屬性,
*
*/
- (void)customizeTabBarForController:(CYLTabBarController *)tabBarController {
NSDictionary *dict1 = @{
CYLTabBarItemTitle : @"首頁(yè)",
CYLTabBarItemImage : @"home_normal",
CYLTabBarItemSelectedImage : @"home_highlight",
};
NSDictionary *dict2 = @{
CYLTabBarItemTitle : @"同城",
CYLTabBarItemImage : @"mycity_normal",
CYLTabBarItemSelectedImage : @"mycity_highlight",
};
NSArray *tabBarItemsAttributes = @[ dict1, dict2 ];
tabBarController.tabBarItemsAttributes = tabBarItemsAttributes;
}
第三步:將CYLTabBarController設(shè)置為window的RootViewController
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/* *省略部分: * */
[self.window setRootViewController:self.tabBarController];
/* *省略部分: * */
return YES;
}
第四步(可選):創(chuàng)建自定義的形狀不規(guī)則加號(hào)按鈕
創(chuàng)建一個(gè)繼承于 CYLPlusButton 的類,要求和步驟:
實(shí)現(xiàn)
CYLPlusButtonSubclassing協(xié)議子類將自身類型進(jìn)行注冊(cè),一般可在
application的applicationDelegate方法里面調(diào)用[YourClass registerSubClass]或者在子類的+load方法中調(diào)用:
+(void)load {
[super registerSubclass];
}
協(xié)議提供了可選方法:
+ (NSUInteger)indexOfPlusButtonInTabBar;
+ (CGFloat)multiplerInCenterY;
+ (UIViewController *)plusChildViewController;
作用分別是:
+ (NSUInteger)indexOfPlusButtonInTabBar;
用來(lái)自定義加號(hào)按鈕的位置,如果不實(shí)現(xiàn)默認(rèn)居中,但是如果 tabbar 的個(gè)數(shù)是奇數(shù)則必須實(shí)現(xiàn)該方法,否則 CYLTabBarController 會(huì)拋出 exception 來(lái)進(jìn)行提示。
主要適用于如下情景:

Airbnb-app效果:

+ (CGFloat)multiplerInCenterY;
該方法是為了調(diào)整自定義按鈕中心點(diǎn)Y軸方向的位置,建議在按鈕超出了 tabbar 的邊界時(shí)實(shí)現(xiàn)該方法。返回值是自定義按鈕中心點(diǎn)Y軸方向的坐標(biāo)除以 tabbar 的高度,如果不實(shí)現(xiàn),會(huì)自動(dòng)進(jìn)行比對(duì),預(yù)設(shè)一個(gè)較為合適的位置,如果實(shí)現(xiàn)了該方法,預(yù)設(shè)的邏輯將失效。
詳見Demo中的 CYLPlusButtonSubclass 類的實(shí)現(xiàn)。
+ (UIViewController *)plusChildViewController;
詳見: 點(diǎn)擊 PlusButton 跳轉(zhuǎn)到指定 UIViewController
另外,如果加號(hào)按鈕超出了邊界,一般需要手動(dòng)調(diào)用如下代碼取消 tabbar 頂部默認(rèn)的陰影,可在 AppDelegate 類中調(diào)用:
//去除 TabBar 自帶的頂部陰影
[[UITabBar appearance] setShadowImage:[[UIImage alloc] init]];
如何調(diào)整、自定義 PlusButton 與其它 TabBarItem 的寬度?
CYLTabBarController 規(guī)定:
TabBarItem 寬度 = ( TabBar 總寬度 - PlusButton 寬度 ) / (TabBarItem 個(gè)數(shù))
所以想自定義寬度,只需要修改 PlusButton 的寬度即可。
比如你就可以在 Demo中的 CYLPlusButtonSubclass.m 類里:
把
[button sizeToFit];
改為
button.frame = CGRectMake(0.0, 0.0, 250, 100);
button.backgroundColor = [UIColor redColor];
效果如下,

同時(shí)你也可以順便測(cè)試下 CYLTabBarController 的這一個(gè)特性:
即使加號(hào)按鈕超出了tabbar的區(qū)域,超出部分依然能響應(yīng)點(diǎn)擊事件
并且你可以在項(xiàng)目中的任意位置讀取到 PlusButton 的寬度,借助 CYLTabBarController.h 定義的 CYLPlusButtonWidth 這個(gè)extern??蓞⒖?+[CYLTabBarControllerConfig customizeTabBarAppearance:] 里的用法。
補(bǔ)充說明
自定義 TabBar 樣式
如果想更進(jìn)一步的自定義 TabBar 樣式可在 -application:didFinishLaunchingWithOptions: 方法中設(shè)置
/**
* tabBarItem 的選中和不選中文字屬性、背景圖片
*/
- (void)customizeInterface {
// 普通狀態(tài)下的文字屬性
NSMutableDictionary *normalAttrs = [NSMutableDictionary dictionary];
normalAttrs[NSForegroundColorAttributeName] = [UIColor grayColor];
// 選中狀態(tài)下的文字屬性
NSMutableDictionary *selectedAttrs = [NSMutableDictionary dictionary];
selectedAttrs[NSForegroundColorAttributeName] = [UIColor darkGrayColor];
// 設(shè)置文字屬性
UITabBarItem *tabBar = [UITabBarItem appearance];
[tabBar setTitleTextAttributes:normalAttrs forState:UIControlStateNormal];
[tabBar setTitleTextAttributes:selectedAttrs forState:UIControlStateSelected];
// 設(shè)置背景圖片
UITabBar *tabBarAppearance = [UITabBar appearance];
[tabBarAppearance setBackgroundImage:[UIImage imageNamed:@"tabbar_background"]];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/* *省略部分: * */
[self.window makeKeyAndVisible];
[self customizeInterface];
return YES;
}
橫豎屏適配
TabBar 橫豎屏適配時(shí),如果你添加了 PlusButton,且適配時(shí)用到了 TabBarItem 的寬度, 不建議使用系統(tǒng)的UIDeviceOrientationDidChangeNotification , 請(qǐng)使用庫(kù)里的 CYLTabBarItemWidthDidChangeNotification 來(lái)更新 TabBar 布局,最典型的場(chǎng)景就是,根據(jù) TabBarItem 在不同橫豎屏狀態(tài)下的寬度變化來(lái)切換選中的TabBarItem 的背景圖片。Demo 里 CYLTabBarControllerConfig.m 給出了這一場(chǎng)景的用法:
CYLTabBarController.h 中提供了 CYLTabBarItemWidth 這一extern常量,并且會(huì)在 TabBarItem 的寬度發(fā)生變化時(shí),及時(shí)更新該值,所以用法就如下所示:
- (void)updateTabBarCustomizationWhenTabBarItemWidthDidUpdate {
void (^deviceOrientationDidChangeBlock)(NSNotification *) = ^(NSNotification *notification) {
[self tabBarItemWidthDidUpdate];
};
[[NSNotificationCenter defaultCenter] addObserverForName:CYLTabBarItemWidthDidChangeNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:deviceOrientationDidChangeBlock];
}
- (void)tabBarItemWidthDidUpdate {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if ((orientation == UIDeviceOrientationLandscapeLeft) || (orientation == UIDeviceOrientationLandscapeRight)) {
NSLog(@"Landscape Left or Right !");
} else if (orientation == UIDeviceOrientationPortrait){
NSLog(@"Landscape portrait!");
}
CGSize selectionIndicatorImageSize = CGSizeMake(CYLTabBarItemWidth, [self cyl_tabBarController].tabBar.bounds.size.height);
[[self cyl_tabBarController].tabBar setSelectionIndicatorImage:[[self class]
imageFromColor:[UIColor yellowColor]
forSize:selectionIndicatorImageSize
withCornerRadius:0]];
}

訪問初始化好的 CYLTabBarController 對(duì)象
對(duì)于任意 NSObject 對(duì)象:
CYLTabBarController.h 中為 NSObject 提供了分類方法 -cyl_tabBarController ,所以在任意對(duì)象中,一行代碼就可以訪問到一個(gè)初始化好的 CYLTabBarController 對(duì)象,-cyl_tabBarController 的作用你可以這樣理解:與獲取單例對(duì)象的 +shareInstance 方法作用一樣。
接口如下:
// CYLTabBarController.h
@interface NSObject (CYLTabBarController)
/**
* If `self` is kind of `UIViewController`, this method will return the nearest ancestor in the view controller hierarchy that is a tab bar controller. If `self` is not kind of `UIViewController`, it will return the `rootViewController` of the `rootWindow` as long as you have set the `CYLTabBarController` as the `rootViewController`. Otherwise return nil. (read-only)
*/
@property (nonatomic, readonly) CYLTabBarController *cyl_tabBarController;
@end
用法:
//導(dǎo)入 CYLTabBarController.h
#import "CYLTabBarController.h"
- (void)viewDidLoad {
[super viewDidLoad];
CYLTabBarController *tabbarController = [self cyl_tabBarController];
/*...*/
}
點(diǎn)擊 PlusButton 跳轉(zhuǎn)到指定 UIViewController
提供了一個(gè)協(xié)議方法來(lái)完成本功能:

實(shí)現(xiàn)該方法后,能讓 PlusButton 的點(diǎn)擊效果與跟點(diǎn)擊其他 UITabBarButton 效果一樣,跳轉(zhuǎn)到該方法指定的 UIViewController 。
注意:必須同時(shí)實(shí)現(xiàn) +indexOfPlusButtonInTabBar 來(lái)指定 PlusButton 的位置。
遵循兩個(gè)協(xié)議:

讓TabBarItem僅顯示圖標(biāo),并使圖標(biāo)垂直居中
要想實(shí)現(xiàn)該效果,只需要在設(shè)置 tabBarItemsAttributes該屬性時(shí)不傳 title 即可。
比如:在Demo的基礎(chǔ)上,注釋掉圖中紅框部分:

| 注釋前 | 注釋后 |
|---|---|
![]() enter image description here
|
![]() enter image description here
|
可以通過這種方式來(lái)達(dá)到 Airbnb-app 的效果:

如果想手動(dòng)設(shè)置偏移量來(lái)達(dá)到該效果:
可以在 -setViewControllers: 方法前設(shè)置 CYLTabBarController 的 imageInsets 和 titlePositionAdjustment 屬性
這里注意:設(shè)置這兩個(gè)屬性后,TabBar 中所有的 TabBarItem 都將被設(shè)置。并且第一種做法的邏輯將不會(huì)執(zhí)行,也就是說該做法優(yōu)先級(jí)要高于第一種做法。
做法如下:

但是想達(dá)到Airbnb-app的效果只有這個(gè)接口是不行的,還需要自定義下 TabBar 的高度,你需要設(shè)置 CYLTabBarController 的 tabBarHeight 屬性。你可以在Demo的 CYLTabBarControllerConfig.m 中的 -customizeTabBarAppearance: 方法中設(shè)置。
注:“僅顯示圖標(biāo),并使圖標(biāo)垂直居中”這里所指的“圖標(biāo)”,其所屬的類是私有類: UITabBarSwappableImageView,所以 CYLTabBarController 在相關(guān)的接口命名時(shí)會(huì)包含 SwappableImageView 字樣。
在 Swift 項(xiàng)目中使用 CYLTabBarController
參考: 《從頭開始swift2.1 仿搜材通項(xiàng)目(三) 主流框架Tabbed的搭建》
這里注意,文章的示例代碼有問題,少了設(shè)置 PlusButton 大小的代碼:
這將導(dǎo)致 PlusButton 點(diǎn)擊事件失效,具體修改代碼如下:

源碼實(shí)現(xiàn)原理
參考: 《[Note] CYLTabBarController》
更多文檔信息可查看 CocoaDocs:CYLTabBarController 。
Q-A
Q:為什么放置6個(gè)TabBarItem會(huì)顯示異常?
A:
Apple 規(guī)定:
一個(gè)
TabBar上只能出現(xiàn)最多5個(gè)TabBarItem,第六個(gè)及更多的將不被顯示。
另外注意,Apple檢測(cè)的是 UITabBarItem 及其子類,所以放置“加號(hào)按鈕”,這是 UIButton 不在“5個(gè)”里面。
最多只能添加5個(gè) TabBarItem ,也就是說加上“加號(hào)按鈕”,一共最多在一個(gè) TabBar 上放置6個(gè)控件。否則第6個(gè)及之后出現(xiàn) TabBarItem 會(huì)被自動(dòng)屏蔽掉。而且就Apple的審核機(jī)制來(lái)說,超過5個(gè)也會(huì)被直接拒絕上架。
Q: 如何實(shí)現(xiàn)添加選中背景色的功能 ,像下面這樣:

A:我已經(jīng)在 Demo 中添加了如何實(shí)現(xiàn)該功能的代碼:
詳情見 CYLTabBarControllerConfig 類中下面方法的實(shí)現(xiàn):
/**
* 更多TabBar自定義設(shè)置:比如:tabBarItem 的選中和不選中文字和背景圖片屬性、tabbar 背景圖片屬性
*/
- (void)customizeTabBarAppearance:(CYLTabBarController *)tabBarController;
效果如下:

Q: 當(dāng) ViewController 設(shè)置的 self.title 和 tabBarItemsAttributes 中對(duì)應(yīng)的 title 不一致的時(shí)候,會(huì)出現(xiàn)如圖的錯(cuò)誤,排序不對(duì)了
A:在 v1.0.7 版本中已經(jīng)修復(fù)了該 bug,但是也需要注意:
請(qǐng)勿使用 self.title = @"同城"; 這種方式,請(qǐng)使用 self.navigationItem.title = @"同城";
self.title = @"同城"; 這種方式,如果和 tabBarItemsAttributes 中對(duì)應(yīng)的 title 不一致的時(shí)候可能會(huì)導(dǎo)致如下現(xiàn)象(不算 bug,但看起來(lái)也很奇怪):

規(guī)則如下:
self.navigationItem.title = @"同城"; //?sets navigation bar title.The right way to set the title of the navigation
self.tabBarItem.title = @"同城23333"; //?sets tab bar title. Even the `tabBarItem.title` changed, this will be ignored in tabbar.
self.title = @"同城1"; //?sets both of these. Do not do this???? This may cause something strange like this : http://i68.tinypic.com/282l3x4.jpg
Q : 當(dāng)使用這個(gè)方法時(shí) -[UIViewController cyl_jumpToOtherTabBarControllerItem:(Class)ClassType performSelector:arguments:returnValue:] 會(huì)出現(xiàn)如下的黑邊問題。

A: 這個(gè)是 iOS 系統(tǒng)的BUG,經(jīng)測(cè)試iOS9.3已經(jīng)修復(fù)了,如果在更早起版本中出現(xiàn)了,可以通過下面將 rootWindow 的背景色改為白色來(lái)避免:比如你可以 Appdelegate 類里這樣設(shè)置:
//#import "CYLTabBarController.h"
[[self cyl_tabBarController] rootWindow].backgroundColor = [UIColor whiteColor];
Q:我現(xiàn)在已經(jīng)做好了一個(gè)比較簡(jiǎn)單的中間凸起的 icon 但是超過了49這個(gè)高度的位置是不能效應(yīng)的 我想請(qǐng)問你的demo哪個(gè)功能是可以使我超出的范圍也可以響應(yīng)的呢?
A: 這個(gè)是自動(dòng)做的,但是 CYLTabBarController 只能保證的是:只要是 UIButton 的 frame 區(qū)域內(nèi)就能響應(yīng)。
請(qǐng)把 button 的背景顏色設(shè)置為顯眼的顏色,比如紅色,比如像下面的plus按鈕,紅色部分是能接收點(diǎn)擊事件的,但是超出了紅色按鈕的,黃色的圖片區(qū)域,依然是無(wú)法響應(yīng)點(diǎn)擊事件的。

這是因?yàn)?,在響?yīng)鏈上,UIControl 能響應(yīng)點(diǎn)擊事件, UIImage 無(wú)法響應(yīng)。
(更多iOS開發(fā)干貨,歡迎關(guān)注 微博@iOS程序犭袁 )
Posted by 微博@iOS程序犭袁
原創(chuàng)文章,版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
<p align="center"><a target="_blank">







