# CYLTabBarController【低耦合集成TabBarController】
## 导航 - [与其他自定义TabBarController的区别](#%E4%B8%8E%E5%85%B6%E4%BB%96%E8%87%AA%E5%AE%9A%E4%B9%89tabbarcontroller%E7%9A%84%E5%8C%BA%E5%88%AB) - [集成后的效果:](#%E9%9B%86%E6%88%90%E5%90%8E%E7%9A%84%E6%95%88%E6%9E%9C) - [项目结构](#%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84) - [使用CYLTabBarController](#%E4%BD%BF%E7%94%A8cyltabbarcontroller) - [第一步:使用CocoaPods导入CYLTabBarController](#%E7%AC%AC%E4%B8%80%E6%AD%A5%E4%BD%BF%E7%94%A8cocoapods%E5%AF%BC%E5%85%A5cyltabbarcontroller) - [第二步:设置CYLTabBarController的两个数组:控制器数组和TabBar属性数组](#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E8%AE%BE%E7%BD%AEcyltabbarcontroller%E7%9A%84%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E6%8E%A7%E5%88%B6%E5%99%A8%E6%95%B0%E7%BB%84%E5%92%8Ctabbar%E5%B1%9E%E6%80%A7%E6%95%B0%E7%BB%84) - [第三步:将CYLTabBarController设置为window的RootViewController](#%E7%AC%AC%E4%B8%89%E6%AD%A5%E5%B0%86cyltabbarcontroller%E8%AE%BE%E7%BD%AE%E4%B8%BAwindow%E7%9A%84rootviewcontroller) - [第四步(可选):创建自定义的形状不规则加号按钮](#%E7%AC%AC%E5%9B%9B%E6%AD%A5%E5%8F%AF%E9%80%89%E5%88%9B%E5%BB%BA%E8%87%AA%E5%AE%9A%E4%B9%89%E7%9A%84%E5%BD%A2%E7%8A%B6%E4%B8%8D%E8%A7%84%E5%88%99%E5%8A%A0%E5%8F%B7%E6%8C%89%E9%92%AE) - [补充说明](#%E8%A1%A5%E5%85%85%E8%AF%B4%E6%98%8E) - [自定义 `TabBar` 样式](#%E8%87%AA%E5%AE%9A%E4%B9%89-tabbar-%E6%A0%B7%E5%BC%8F) - [捕获 TabBar 点击事件](#%E6%8D%95%E8%8E%B7-tabbar-%E7%82%B9%E5%87%BB%E4%BA%8B%E4%BB%B6) - [点击 TabBarButton 时添加动画](#%E7%82%B9%E5%87%BB-tabbarbutton-%E6%97%B6%E6%B7%BB%E5%8A%A0%E5%8A%A8%E7%94%BB) - [横竖屏适配](#%E6%A8%AA%E7%AB%96%E5%B1%8F%E9%80%82%E9%85%8D) - [访问初始化好的 CYLTabBarController 对象](#%E8%AE%BF%E9%97%AE%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A5%BD%E7%9A%84-cyltabbarcontroller-%E5%AF%B9%E8%B1%A1) - [点击 PlusButton 跳转到指定 UIViewController](#%E7%82%B9%E5%87%BB-plusbutton-%E8%B7%B3%E8%BD%AC%E5%88%B0%E6%8C%87%E5%AE%9A-uiviewcontroller) - [让TabBarItem仅显示图标,并使图标垂直居中](#%E8%AE%A9tabbaritem%E4%BB%85%E6%98%BE%E7%A4%BA%E5%9B%BE%E6%A0%87%E5%B9%B6%E4%BD%BF%E5%9B%BE%E6%A0%87%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD) - [多TabBar嵌套,并指定PlusButton位置](#%E5%A4%9Atabbar%E5%B5%8C%E5%A5%97%E5%B9%B6%E6%8C%87%E5%AE%9Aplusbutton%E4%BD%8D%E7%BD%AE) - [在 Swift 项目中使用 CYLTabBarController](#%E5%9C%A8-swift-%E9%A1%B9%E7%9B%AE%E4%B8%AD%E4%BD%BF%E7%94%A8-cyltabbarcontroller) - [搭配 Storyboard 使用 CYLTabBarController](#%E6%90%AD%E9%85%8D-storyboard-%E4%BD%BF%E7%94%A8-cyltabbarcontroller) - [源码实现原理](#%E6%BA%90%E7%A0%81%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) - [FAQ](#faq) ## 与其他自定义TabBarController的区别 特点 |解释 -------------|------------- 低耦合,易删除 | 1、TabBar设置与业务完全分离,最低只需传两个数组即可完成主流App框架搭建。 2、 PlusButton 的所有设置都在单独的一个类( `CYLPlusButton` 的子类)中实现:删除该特定的类,就能完全将 PlusButton 从项目中删除掉。 `TabBar` 以及 `TabBar` 内的 `TabBarItem` 均使用系统原生的控件 | 因为使用原生的控件,并非 `UIButton` 或 `UIView` 。好处如下: 1. 无需反复调“间距位置等”来接近系统效果。 2. 在push到下一页时 `TabBar` 的隐藏和显示之间的过渡效果跟系统一致(详见“ [集成后的效果](https://github.com/ChenYilong/CYLTabBarController#集成后的效果) ”部分,给出了效果图) 3. 原生控件,所以可以使用诸多系统API,比如:可以使用 ` [UITabBar appearance];` 、` [UITabBarItem appearance];` 设置样式。(详见“[补充说明](https://github.com/ChenYilong/CYLTabBarController#补充说明) ”部分,给出了响应代码示例) 自动监测是否需要添加“加号”按钮,并能自动设置位置 |[CYLTabBarController](https://github.com/ChenYilong/CYLTabBarController) 既支持类似微信的“中规中矩”的 `TabBarController` 样式,并且默认就是微信这种样式,同时又支持类似“微博”或“淘宝闲鱼”这种具有不规则加号按钮的 `TabBarController` 。想支持这种样式,只需自定义一个加号按钮,[CYLTabBarController](https://github.com/ChenYilong/CYLTabBarController) 能检测到它的存在并自动将 `tabBar` 排序好,无需多余操作,并且也预留了一定接口来满足自定义需求。“加号”按钮的样式、frame均在自定义的类中独立实现,不会涉及tabbar相关设置。 即使加号按钮超出了tabbar的区域,超出部分依然能响应点击事件 | 红线内的区域均能响应tabbar相关的点击事件, 允许指定加号按钮位置 | 效果如下: Airbnb-app效果: 支持让 `TabBarItem` 仅显示图标,并自动使图标垂直居中,支持自定义TabBar高度 | 效果可见Airbnb-app效果,或者下图 支持自定义动画 |  支持角标自定义View |  支持多TabBar嵌套,并指定PlusButton位置 |  支持CocoaPods |容易集成 支持Swift项目导入 | 兼容 支持横竖屏 | -- (学习交流群:515295083) ## 集成后的效果: 既支持默认样式 | 同时也支持创建自定义的形状不规则加号按钮 -------------|------------ |  支持横竖屏  本仓库配套Demo的效果:| [另一个Demo](https://github.com/ChenYilong/CYLTabBarControllerDemoForWeib) 使用CYLTabBarController实现了微博Tabbar框架,效果如下 -------------|------------- | ## 项目结构  做下说明: ```Objective-C ├── CYLTabBarController #核心库文件夹,如果不使用 CocoaPods 集成,请直接将这个文件夹拖拽带你的项目中 └── Example └── Classes ├── Module #模块类文件夹 │ ├── Home │ ├── Message │ ├── Mine │ └── SameCity └── View #这里放着 CYLPlusButton 的子类 CYLPlusButtonSubclass,演示了如何创建自定义的形状不规则加号按钮 ``` ## 使用[CYLTabBarController](https://github.com/ChenYilong/CYLTabBarController) 四步完成主流App框架搭建: 1. [ 第一步:使用CocoaPods导入CYLTabBarController ](https://github.com/ChenYilong/CYLTabBarController#第一步使用cocoapods导入cyltabbarcontroller) 2. [第二步:设置CYLTabBarController的两个数组:控制器数组和TabBar属性数组](https://github.com/ChenYilong/CYLTabBarController#第二步设置cyltabbarcontroller的两个数组控制器数组和tabbar属性数组) 3. [第三步:将CYLTabBarController设置为window的RootViewController](https://github.com/ChenYilong/CYLTabBarController#第三步将cyltabbarcontroller设置为window的rootviewcontroller) 4. [第四步(可选):创建自定义的形状不规则加号按钮](https://github.com/ChenYilong/CYLTabBarController#第四步可选创建自定义的形状不规则加号按钮) ### 第一步:使用CocoaPods导入CYLTabBarController 1. CocoaPods 安装 如果您的机器上已经安装了 CocoaPods,直接进入下一步即可。 如果您的网络已经翻墙,在终端中运行如下命令直接安装: ``` sudo gem install cocoapods ``` 如果您的网络不能翻墙,可以通过国内 Ruby China 的 RubyGems 镜像进行安装。 在终端依次运行以下命令: ``` gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/ sudo gem install cocoapods ``` 2. 查询 CocoaPods 源中的本库 在终端中运行以下命令: ``` pod search CYLTabBarController ``` 这里注意,这个命令搜索的是本机上的最新版本,并没有联网查询。如果运行以上命令,没有搜到或者搜不到最新版本,您可以运行以下命令,更新一下您本地的 CocoaPods 源列表。 ``` pod repo update ``` 3. 使用 CocoaPods 导入 打开终端,进入到您的工程目录,执行以下命令,会自动生成一个 Podfile 文件。 ``` pod init ``` 然后使用 CocoaPods 进行安装。如果尚未安装 CocoaPods,运行以下命令进行安装: ``` gem install cocoapods ``` 打开 Podfile,在您项目的 target 下加入以下内容。(在此以 v1.6.7 版本为例) 在文件 `Podfile` 中加入以下内容: ``` pod 'CYLTabBarController', '~> 1.17.4' ``` 然后在终端中运行以下命令: ``` pod install ``` 或者这个命令: ``` # 禁止升级 CocoaPods 的 spec 仓库,否则会卡在 Analyzing dependencies,非常慢 pod update --verbose --no-repo-update ``` 如果提示找不到库,则可去掉 `--no-repo-update`。 完成后1.17.4,CocoaPods 会在您的工程根目录下生成一个 `.xcworkspace` 文件。您需要通过此文件打开您的工程,而不是之前的 `.xcodeproj`。 **CocoaPods 使用说明** **指定 CYLTabBarController 版本** CocoaPods 中,有几种设置 CYLTabBarController 版本的方法。如: `>= 1.16.X` 会根据您本地的 CocoaPods 源列表,导入不低于 `1.18.X` 版本的 CYLTabBarController。 `~> 1.16.X` 会根据您本地的 CocoaPods 源列表,介于 1.16.X~1.18.0 之前版本的 CYLTabBarController。 我们建议您锁定版本,便于团队开发。如,指定 1.17.4 版本。 ``` pod 'CYLTabBarController', '~> 1.17.4' ``` - 升级本地 CocoaPods 源 `CocoaPods 有一个中心化的源,默认本地会缓存 CocoaPods 源服务器上的所有 CYLTabBarController 版本。 如果搜索的时候没有搜到或者搜不到最新版本,可以执行以下命令更新一下本地的缓存。 ``` pod repo update ``` - 升级工程的 CYLTabBarController 版本 更新您工程目录中 Podfile 指定的 CYLTabBarController 版本后,在终端中执行以下命令。 ``` pod update ``` - 清除 Cocoapods 本地缓存 特殊情况下,由于网络或者别的原因,通过 CocoaPods 下载的文件可能会有问题。 这时候您可以删除 CocoaPods 的缓存(~/Library/Caches/CocoaPods/Pods/Release 目录),再次导入即可。 - 查看当前使用的 CYLTabBarController 版本 您可以在 Podfile.lock 文件中看到您工程中使用的 CYLTabBarController 版本。 关于 CocoaPods 的更多内容,您可以参考 [CocoaPods 文档](https://cocoapods.org/)。 ### 第二步:设置CYLTabBarController的两个数组:控制器数组和TabBar属性数组 ```Objective-C - (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:`之前设置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; } ``` 在这个字典中,`CYLTabBarItemImage` 和 `CYLTabBarItemSelectedImage` 支持 `NSString`、`UIImage` 两种格式。`CYLTabBarItemTitle` 不设置将只展示图标,并会对布局作出居中处理。 ### 第三步:将CYLTabBarController设置为window的RootViewController ```Objective-C - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { /* *省略部分: * */ [self.window setRootViewController:self.tabBarController]; /* *省略部分: * */ return YES; } ``` ### 第四步(可选):创建自定义的形状不规则加号按钮 创建一个继承于 CYLPlusButton 的类,要求和步骤: 1. 实现 `CYLPlusButtonSubclassing` 协议 2. 子类将自身类型进行注册,需要在 `-application:didFinishLaunchingWithOptions:` 方法里面调用 `[YourClass registerPlusButton]` 这里注意,不能在子类的 `+load` 方法中调用,比如像下面这样做,在 iOS10 系统上有 Crash 的风险: ```Objective-C + (void)load { [super registerPlusButton]; } ``` 协议提供了可选方法: ```Objective-C + (NSUInteger)indexOfPlusButtonInTabBar; + (CGFloat)multiplierOfTabBarHeight:(CGFloat)tabBarHeight; + (UIViewController *)plusChildViewController; + (BOOL)shouldSelectPlusChildViewController; ``` 作用分别是: ```Objective-C + (NSUInteger)indexOfPlusButtonInTabBar; ``` 用来自定义加号按钮的位置,如果不实现默认居中,但是如果 `tabbar` 的个数是奇数则必须实现该方法,否则 `CYLTabBarController` 会抛出 `exception` 来进行提示。 主要适用于如下情景:  Airbnb-app效果:  ```Objective-C + (CGFloat)multiplierOfTabBarHeight:(CGFloat)tabBarHeight; ``` 该方法是为了调整自定义按钮中心点Y轴方向的位置,建议在按钮超出了 `tabbar` 的边界时实现该方法。返回值是自定义按钮中心点Y轴方向的坐标除以 `tabbar` 的高度,如果不实现,会自动进行比对,预设一个较为合适的位置,如果实现了该方法,预设的逻辑将失效。 内部实现时,会使用该返回值来设置 PlusButton 的 centerY 坐标,公式如下: `PlusButtonCenterY = multiplierOfTabBarHeight * taBarHeight + constantOfPlusButtonCenterYOffset;` 也就是说:如果 constantOfPlusButtonCenterYOffset 为0,同时 multiplierOfTabBarHeight 的值是0.5,表示 PlusButton 居中,小于0.5表示 PlusButton 偏上,大于0.5则表示偏下。 ```Objective-C + (CGFloat)constantOfPlusButtonCenterYOffsetForTabBarHeight:(CGFloat)tabBarHeight; ``` 参考 `+multiplierOfTabBarHeight:` 中的公式: `PlusButtonCenterY = multiplierOfTabBarHeight * taBarHeight + constantOfPlusButtonCenterYOffset;` 也就是说: constantOfPlusButtonCenterYOffset 大于0会向下偏移,小于0会向上偏移。 注意:实现了该方法,但没有实现 `+multiplierOfTabBarHeight:` 方法,在这种情况下,会在预设逻辑的基础上进行偏移。 详见Demo中的 `CYLPlusButtonSubclass` 类的实现。 ```Objective-C + (UIViewController *)plusChildViewController; ``` 详见: [点击 PlusButton 跳转到指定 UIViewController](https://github.com/ChenYilong/CYLTabBarController#点击-plusbutton-跳转到指定-uiviewcontroller) 另外,如果加号按钮超出了边界,一般需要手动调用如下代码取消 tabbar 顶部默认的阴影,可在 AppDelegate 类中调用: ```Objective-C //去除 TabBar 自带的顶部阴影 [[UITabBar appearance] setShadowImage:[[UIImage alloc] init]]; ``` // iOS10 后 需要使用 `-[CYLTabBarController hideTabBadgeBackgroundSeparator]` 见 AppDelegate 类中的演示; 如何调整、自定义 `PlusButton` 与其它 `TabBarItem` 的宽度? `CYLTabBarController` 规定: ```Objective-C TabBarItem 宽度 = ( TabBar 总宽度 - PlusButton 宽度 ) / (TabBarItem 个数) ``` 所以想自定义宽度,只需要修改 `PlusButton` 的宽度即可。 比如你就可以在 Demo中的 `CYLPlusButtonSubclass.m` 类里: 把 ```Objective-C [button sizeToFit]; ``` 改为 ```Objective-C button.frame = CGRectMake(0.0, 0.0, 250, 100); button.backgroundColor = [UIColor redColor]; ``` 效果如下, 1.17.4  同时你也可以顺便测试下 `CYLTabBarController` 的这一个特性: > 即使加号按钮超出了tabbar的区域,超出部分依然能响应点击事件 并且你可以在项目中的任意位置读取到 `PlusButton` 的宽度,借助 `CYLTabBarController.h` 定义的 `CYLPlusButtonWidth` 这个extern。可参考 `+[CYLTabBarControllerConfig customizeTabBarAppearance:]` 里的用法。 ## 补充说明 ### 自定义 `TabBar` 样式 如果想更进一步的自定义 `TabBar` 样式可在 `-application:didFinishLaunchingWithOptions:` 方法中设置 ```Objective-C /** * tabBarItem 的选中和不选中文字属性、背景图片 */ - (void)customizeInterface { // 普通状态下的文字属性 NSMutableDictionary *normalAttrs = [NSMutableDictionary dictionary]; normalAttrs[NSForegroundColorAttributeName] = [UIColor grayColor]; // 选中状态下的文字属性 NSMutableDictionary *selectedAttrs = [NSMutableDictionary dictionary]; selectedAttrs[NSForegroundColorAttributeName] = [UIColor darkGrayColor]; // 设置文字属性 UITabBarItem *tabBar = [UITabBarItem appearance]; [tabBar setTitleTextAttributes:normalAttrs forState:UIControlStateNormal]; [tabBar setTitleTextAttributes:selectedAttrs forState:UIControlStateSelected]; // 设置背景图片 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 点击事件 实现 CYLTabBarController 的如下几个代理方法即可捕获点击事件。 下面这个方法能捕获当前点击的 `TabBar` 上的控件,可以是 `UITabBarButton`、也可以 `PlusButton`、也可以是添加到 `TabBar` 上的任意 `UIControl` 的子类。但是如果 `PlusButton` 也添加了点击事件,那么点击 `PlusButton` 将不会被触发这个代理方法。 ```Objective-C //CYLTabBarController.h @protocol CYLTabBarControllerDelegate