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 對象
- 點(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. 無需反復(fù)調(diào)“間距位置等”來接近系統(tǒng)效果缝裁。</p> 2. 在push到下一頁時(shí) TabBar 的隱藏和顯示之間的過渡效果跟系統(tǒng)一致(詳見“ 集成后的效果 ”部分扫皱,給出了效果圖) </p> 3. 原生控件,所以可以使用諸多系統(tǒng)API捷绑,比如:可以使用 [UITabBar appearance]; 啸罢、[UITabBarItem appearance]; 設(shè)置樣式。(詳見“補(bǔ)充說明 ”部分胎食,給出了響應(yīng)代碼示例) |
3 | 自動(dòng)監(jiān)測是否需要添加“加號(hào)”按鈕,</p>并能自動(dòng)設(shè)置位置 |
CYLTabBarController 既支持類似微信的“中規(guī)中矩”的 TabBarController 樣式允懂,并且默認(rèn)就是微信這種樣式厕怜,同時(shí)又支持類似“微博”或“淘寶閑魚”這種具有不規(guī)則加號(hào)按鈕的 TabBarController 。想支持這種樣式蕾总,只需自定義一個(gè)加號(hào)按鈕火本,CYLTabBarController 能檢測到它的存在并自動(dòng)將 tabBar 排序好银室,無需多余操作,并且也預(yù)留了一定接口來滿足自定義需求。</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
|
支持橫豎屏
本倉庫配套Demo的效果: | 另一個(gè)Demo 使用CYLTabBarController實(shí)現(xiàn)了微博Tabbar框架,效果如下 |
---|---|
enter image description here
|
enter image description here
|
項(xiàng)目結(jié)構(gòu)
做下說明:
├── CYLTabBarController #核心庫文件夾撩满,如果不使用 CocoaPods 集成蜒程,請直接將這個(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
安裝成功后就可以安裝依賴了:
建議使用如下方式:
# 禁止升級CocoaPods的spec倉庫伺帘,否則會(huì)卡在 Analyzing dependencies 昭躺,非常慢
pod update --verbose --no-repo-update
如果提示找不到庫,則可去掉 --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 : @"首頁",
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)行注冊,一般可在
application
的applicationDelegate
方法里面調(diào)用[YourClass registerSubClass]
或者在子類的+load
方法中調(diào)用:
+(void)load {
[super registerSubclass];
}
協(xié)議提供了可選方法:
+ (NSUInteger)indexOfPlusButtonInTabBar;
+ (CGFloat)multiplerInCenterY;
+ (UIViewController *)plusChildViewController;
作用分別是:
+ (NSUInteger)indexOfPlusButtonInTabBar;
用來自定義加號(hào)按鈕的位置礼殊,如果不實(shí)現(xiàn)默認(rèn)居中驹吮,但是如果 tabbar
的個(gè)數(shù)是奇數(shù)則必須實(shí)現(xiàn)該方法,否則 CYLTabBarController
會(huì)拋出 exception
來進(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)行比對,預(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í)你也可以順便測試下 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
, 請使用庫里的 CYLTabBarItemWidthDidChangeNotification
來更新 TabBar
布局辐怕,最典型的場景就是,根據(jù) TabBarItem
在不同橫豎屏狀態(tài)下的寬度變化來切換選中的TabBarItem
的背景圖片从绘。Demo 里 CYLTabBarControllerConfig.m
給出了這一場景的用法:
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 對象
對于任意 NSObject
對象:
CYLTabBarController.h
中為 NSObject
提供了分類方法 -cyl_tabBarController
陕截,所以在任意對象中,一行代碼就可以訪問到一個(gè)初始化好的 CYLTabBarController
對象批什,-cyl_tabBarController
的作用你可以這樣理解:與獲取單例對象的 +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é)議方法來完成本功能:
實(shí)現(xiàn)該方法后,能讓 PlusButton 的點(diǎn)擊效果與跟點(diǎn)擊其他 UITabBarButton 效果一樣驻债,跳轉(zhuǎn)到該方法指定的 UIViewController 乳规。
注意:必須同時(shí)實(shí)現(xiàn) +indexOfPlusButtonInTabBar
來指定 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
|
可以通過這種方式來達(dá)到 Airbnb-app 的效果:
如果想手動(dòng)設(shè)置偏移量來達(dá)到該效果:
可以在 -setViewControllers:
方法前設(shè)置 CYLTabBarController
的 imageInsets
和 titlePositionAdjustment
屬性
這里注意:設(shè)置這兩個(gè)屬性后冻辩,TabBar
中所有的 TabBarItem
都將被設(shè)置。并且第一種做法的邏輯將不會(huì)執(zhí)行拆祈,也就是說該做法優(yōu)先級要高于第一種做法恨闪。
做法如下:
但是想達(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檢測的是 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ī)制來說逢倍,超過5個(gè)也會(huì)被直接拒絕上架。
Q: 如何實(shí)現(xiàn)添加選中背景色的功能 景图,像下面這樣:
![screen shot 2015-10-28 at 9 21 56 am](https://cloud.githubusercontent.com/assets/7238866/10777333/5d7811c8-7d55-11e5-88be-8cb11bbeaf90.png)
A:我已經(jīng)在 Demo 中添加了如何實(shí)現(xiàn)該功能的代碼:
詳情見 CYLTabBarControllerConfig
類中下面方法的實(shí)現(xiàn):
/**
* 更多TabBar自定義設(shè)置:比如:tabBarItem 的選中和不選中文字和背景圖片屬性较雕、tabbar 背景圖片屬性
*/
- (void)customizeTabBarAppearance:(CYLTabBarController *)tabBarController;
效果如下:
![simulator screen shot 2015 10 28 11 44 32](https://cloud.githubusercontent.com/assets/2911921/10779397/34956b0a-7d6b-11e5-82d9-fa75aa34e8d0.png)
Q: 當(dāng) ViewController
設(shè)置的 self.title
和 tabBarItemsAttributes
中對應(yīng)的 title
不一致的時(shí)候,會(huì)出現(xiàn)如圖的錯(cuò)誤挚币,排序不對了
A:在 v1.0.7 版本中已經(jīng)修復(fù)了該 bug亮蒋,但是也需要注意:
請勿使用 self.title = @"同城";
這種方式,請使用 self.navigationItem.title = @"同城";
self.title = @"同城";
這種方式妆毕,如果和 tabBarItemsAttributes
中對應(yīng)的 title
不一致的時(shí)候可能會(huì)導(dǎo)致如下現(xiàn)象(不算 bug慎玖,但看起來也很奇怪):
![enter image description here](http://i68.tinypic.com/282l3x4.jpg)
規(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)測試iOS9.3已經(jīng)修復(fù)了凄吏,如果在更早起版本中出現(xiàn)了,可以通過下面將 rootWindow
的背景色改為白色來避免:比如你可以 Appdelegate
類里這樣設(shè)置:
//#import "CYLTabBarController.h"
[[self cyl_tabBarController] rootWindow].backgroundColor = [UIColor whiteColor];
Q:我現(xiàn)在已經(jīng)做好了一個(gè)比較簡單的中間凸起的 icon 但是超過了49這個(gè)高度的位置是不能效應(yīng)的 我想請問你的demo哪個(gè)功能是可以使我超出的范圍也可以響應(yīng)的呢?
A: 這個(gè)是自動(dòng)做的闰蛔,但是 CYLTabBarController
只能保證的是:只要是 UIButton
的 frame 區(qū)域內(nèi)就能響應(yīng)痕钢。
請把 button 的背景顏色設(shè)置為顯眼的顏色,比如紅色序六,比如像下面的plus按鈕任连,紅色部分是能接收點(diǎn)擊事件的,但是超出了紅色按鈕的例诀,黃色的圖片區(qū)域随抠,依然是無法響應(yīng)點(diǎn)擊事件的。
這是因?yàn)榉蓖浚陧憫?yīng)鏈上拱她,UIControl
能響應(yīng)點(diǎn)擊事件, UIImage
無法響應(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">