CYLTabBarController 1.29.2

CYLTabBarController 1.29.2

TestsTested
LangLanguage Obj-CObjective C
License MIT
ReleasedLast Release Aug 2021

Maintained by ChenYilong.



[CN]CYLTabBarController【一行代码实现 Lottie 动画 TabBar】

[EN]CYLTabBarController [An animated tabBar supported by Lottie with one line of code]


[CN]阅读须知

[EN]Before Reading

[CN]谁在用?上架APP列表[EN]who are using?List of App published

APP name link icon image APP name link icon image
海底捞 App Store Link enter image description here enter image description here C家 App Store Link enter image description here Lottie 效果
淘必中-淘宝推出的一款具有营销购物功能的APP App Store Link enter image description here enter image description here 慧诊健康 App Store Link enter image description here enter image description here
究竟大学 App Store Link enter image description here enter image description here 蕉片 - 短视频拍摄剪辑出电影大片 App Store Link enter image description here
闪剧 - 可以玩的短视频 App Store Link enter image description here enter image description here 土狗云商城 App Store Link enter image description here
Analyst.ai App Store Link enter image description here enter image description here 美膳校园 App Store Link enter image description here enter image description here
全聚星 App Store Link enter image description here enter image description here Yesoul App Store Link enter image description here enter image description here
贵州充电通 App Store Link enter image description here enter image description here 卓尔购 App Store Link enter image description here enter image description here
卓越管家 App Store Link enter image description here enter image description here 悦守护 App Store Link
筑集采采购端 App Store Link enter image description here 筑集采供应端 App Store Link enter image description here
桂富宝 App Store Link enter image description here enter image description here 小猪奔奔 App Store Link enter image description here enter image description here
娃娃坞(开发中) 更多APP请访问 《使用 CYLTabBarController 做的 APP,来这里报道下吧》 如果你正在使用也请提交到这里 《使用 CYLTabBarController 做的 APP,来这里报道下吧》

[CN]导航

[EN]CONTENTS

与其他自定义TabBarController的区别

Comparetion with other Libraries

[CN]特点

[EN]Features
[CN]解释

[EN]Explanation
[CN]一行代码支持Lottie动画TabBar样式

[EN] It only needs one line of code to have an animated TabBar with Lottie.
[CN]使用方法

[EN]How to add animation of Lottie with one line of code.

https://github.com/ChenYilong

https://github.com/ChenYilong
[CN]低耦合,易删除

[EN]Loose coupling
[CN]1、TabBar设置与业务完全分离,最低只需传两个数组即可完成主流App框架搭建。

[EN]1. This library is independent of your business codes which only needs two array parameters to be passed.

[CN]2、 PlusButton 的所有设置都在单独的一个类( CYLPlusButton 的子类)中实现:删除该特定的类,就能完全将 PlusButton 从项目中删除掉。

[EN]2.PlusButton is also independent. If you delete the code of this class, you remove the PlusButton feature from your UI on screen completely.
[CN]TabBar 以及 TabBar 内的 TabBarItem 均使用系统原生的控件

[EN]TabBar and TabBarItem those parts of this library both are system objects.
[CN]因为使用原生的控件,并非 UIButtonUIView 。好处如下:

[EN]Advantages of quitting choosing UIButton or UIView:

1. 无需反复调“间距位置等”来接近系统效果。

[EN]There is not need to adjust those object to make them close to a system object appearance.

2. 在push到下一页时 TabBar 的隐藏和显示之间的过渡效果跟系统一致(详见“ 集成后的效果 ”部分,给出了效果图)

[EN]2. A push animation is same to a system objects appearance.

3. 原生控件,所以可以使用诸多系统API,比如:可以使用 [UITabBar appearance]; [UITabBarItem appearance]; 设置样式。(详见“补充说明 ”部分,给出了响应代码示例)

[EN]3.It is convenient to use the system API such as [UITabBar appearance];, [UITabBarItem appearance];, etc.
自动监测是否需要添加“加号”按钮,

并能自动设置位置

[EN] It is able to check if need to add a PlusButton automatically.
CYLTabBarController 既支持类似微信的“中规中矩”的 TabBarController 样式,并且默认就是微信这种样式,同时又支持类似“微博”或“淘宝闲鱼”这种具有不规则加号按钮的 TabBarController 。想支持这种样式,只需自定义一个加号按钮,CYLTabBarController 能检测到它的存在并自动将 tabBar 排序好,无需多余操作,并且也预留了一定接口来满足自定义需求。

“加号”按钮的样式、frame均在自定义的类中独立实现,不会涉及tabbar相关设置。
支持动态更新 可动态删除PlusButton ,可以动态更新样式

https://github.com/ChenYilong
即使加号按钮超出了tabbar的区域,

超出部分依然能响应点击事件
红线内的区域均能响应tabbar相关的点击事件,

https://github.com/ChenYilong
允许指定加号按钮位置 效果如下:

iTeaTime(技术清谈)

Airbnb-app效果:

iTeaTime(技术清谈)
支持让 TabBarItem 仅显示图标,并自动使图标垂直居中,支持自定义TabBar高度 效果可见Airbnb-app效果,或者下图

enter image description here
支持角标自定义View enter image description here
支持多TabBar嵌套,并指定PlusButton位置 enter image description here
支持CocoaPods 容易集成
支持Swift项目导入 兼容
支持横竖屏 --

(学习交流群:515295083)

集成后的效果:

既支持默认样式 同时也支持创建自定义的形状不规则加号按钮
https://github.com/ChenYilong https://github.com/ChenYilong

支持横竖屏

本仓库配套Demo的效果: 另一个Demo

使用CYLTabBarController实现了微博Tabbar框架,

效果如下
https://github.com/ChenYilong https://github.com/ChenYilong

项目结构

https://github.com/ChenYilong

做下说明:

├── CYLTabBarController  #核心库文件夹,如果不使用 CocoaPods 集成,请直接将这个文件夹拖拽带你的项目中
└── Example
   └── Classes
       ├── Module       #模块类文件夹
       │   ├── Home
       │   ├── Message
       │   ├── Mine
       │   └── SameCity
       └── View         #这里放着 CYLPlusButton 的子类 CYLPlusButtonSubclass,演示了如何创建自定义的形状不规则加号按钮
       
       

使用CYLTabBarController

四步完成主流App框架搭建:

  1. 第一步:使用CocoaPods导入CYLTabBarController
  2. 第二步:设置CYLTabBarController的两个数组:控制器数组和TabBar属性数组
  3. 第三步:将CYLTabBarController设置为window的RootViewController
  4. 第四步(可选):创建自定义的形状不规则加号按钮

第一步:使用CocoaPods导入CYLTabBarController

  1. CocoaPods 安装

  如果您的机器上已经安装了 CocoaPods,直接进入下一步即可。

  如果您的网络已经翻墙,在终端中运行如下命令直接安装:

    sudo gem install cocoapods  

  如果您的网络不能翻墙,可以通过国内 Ruby China 的 RubyGems 镜像进行安装。

  在终端依次运行以下命令:

    gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/     sudo gem install cocoapods  

  1. 查询 CocoaPods 源中的本库

  在终端中运行以下命令:

    pod search CYLTabBarController  

   这里注意,这个命令搜索的是本机上的最新版本,并没有联网查询。如果运行以上命令,没有搜到或者搜不到最新版本,您可以运行以下命令,更新一下您本地的 CocoaPods 源列表。

    pod repo update  

  1. 使用 CocoaPods 导入

  打开终端,进入到您的工程目录,执行以下命令,会自动生成一个 Podfile 文件。

    pod init  

  然后使用 CocoaPods 进行安装。如果尚未安装 CocoaPods,运行以下命令进行安装:

gem install cocoapods

  打开 Podfile,在您项目的 target 下加入以下内容。(此处示例可能是旧版本,使用时请替换为最新版,最新版信息可以从这里获取:

  在文件 Podfile 中加入以下内容:

pod 'CYLTabBarController', '~> 1.24.0'

  然后在终端中运行以下命令:

pod install

  或者这个命令:

# 禁止升级 CocoaPods 的 spec 仓库,否则会卡在 Analyzing dependencies,非常慢
pod update --verbose --no-repo-update

  如果提示找不到库,则可去掉 --no-repo-update

  完成后1.24.0,CocoaPods 会在您的工程根目录下生成一个 .xcworkspace 文件。您需要通过此文件打开您的工程,而不是之前的 .xcodeproj

CocoaPods 使用说明

指定 CYLTabBarController 版本

CocoaPods 中,有几种设置 CYLTabBarController 版本的方法。如:

>= 1.n.X 会根据您本地的 CocoaPods 源列表,导入不低于 1.(n+1).X 版本的 CYLTabBarController。

~> 1.n.X 会根据您本地的 CocoaPods 源列表,介于 1.n.X~1.(n+1).0 之前版本的 CYLTabBarController。

建议选择后者:锁定版本,便于团队开发。如:

(此处示例可能是旧版本,使用时请替换为最新版,最新版信息可以从这里获取:

pod 'CYLTabBarController', '~> 1.24.0'
  • 升级本地 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 文档

第二步:设置CYLTabBarController的两个数组:控制器数组和TabBar属性数组

//MainTabBarController

@interface MainTabBarController : CYLTabBarController
@end


- (instancetype)init {
   if (!(self = [super init])) {
       return nil;
   }
   /**
    * 以下两行代码目的在于手动设置让TabBarItem只显示图标,不显示文字,并让图标垂直居中。
    * 等效于在 `-tabBarItemsAttributesForController` 方法中不传 `CYLTabBarItemTitle` 字段。
    * 更推荐后一种做法。
    */
   UIEdgeInsets imageInsets = UIEdgeInsetsZero;//UIEdgeInsetsMake(4.5, 0, -4.5, 0);
   UIOffset titlePositionAdjustment = UIOffsetMake(0, -3.5);
   CYLTabBarController *tabBarController = [CYLTabBarController tabBarControllerWithViewControllers:self.viewControllers
                                                                              tabBarItemsAttributes:self.tabBarItemsAttributesForController
                                                                                        imageInsets:imageInsets
                                                                            titlePositionAdjustment:titlePositionAdjustment
                                                                                            context:nil
                                            ];
   [self customizeTabBarAppearance:tabBarController];
   self.navigationController.navigationBar.hidden = YES;
   return (self = (MainTabBarController *)tabBarController);
}

- (NSArray *)viewControllers {
   CYLHomeViewController *firstViewController = [[CYLHomeViewController alloc] init];
   UIViewController *firstNavigationController = [[CYLBaseNavigationController alloc]
                                                  initWithRootViewController:firstViewController];
   [firstViewController cyl_setHideNavigationBarSeparator:YES];
   CYLSameCityViewController *secondViewController = [[CYLSameCityViewController alloc] init];
   UIViewController *secondNavigationController = [[CYLBaseNavigationController alloc]
                                                   initWithRootViewController:secondViewController];
   [secondViewController cyl_setHideNavigationBarSeparator:YES];
   NSArray *viewControllers = @[
                                firstNavigationController,
                                secondNavigationController,
                                ];
   return viewControllers;
}

- (NSArray *)tabBarItemsAttributesForController {
   NSDictionary *firstTabBarItemsAttributes = @{
                                                CYLTabBarItemTitle : @"首页",
                                                CYLTabBarItemImage : self.darkMode ? @"home_highlight" : @"home_normal",  /* NSString and UIImage are supported*/
                                                CYLTabBarItemSelectedImage : @"home_highlight",  /* NSString and UIImage are supported*/
                                                };
   NSDictionary *secondTabBarItemsAttributes = @{
                                                 CYLTabBarItemTitle : @"鱼塘",
                                                 CYLTabBarItemImage : self.darkMode ? @"fishpond_highlight" : @"fishpond_normal",
                                                 CYLTabBarItemSelectedImage : @"fishpond_highlight",
                                                 };
   

   NSArray *tabBarItemsAttributes = @[
                                      firstTabBarItemsAttributes,
                                      secondTabBarItemsAttributes,
                                      ];
   return tabBarItemsAttributes;
}

在这个字典中,CYLTabBarItemImageCYLTabBarItemSelectedImage 支持 NSStringUIImage 两种格式。CYLTabBarItemTitle 不设置将只展示图标,并会对布局作出居中处理。

第三步:将CYLTabBarController设置为window的RootViewController

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/* *省略部分:   * */
   [self.window setRootViewController:self.tabBarController];
/* *省略部分:   * */
   return YES;
}

或者将 CYLTabBarController 的子类设为 RootViewCOntroller ,也可以将CYLTabBarController子类的 NavigationViewController 作为 RootViewCOntroller,方便动态更新,Demo 中就是采用后者。

第四步(可选):创建自定义的形状不规则加号按钮

创建一个继承于 CYLPlusButton 的类,要求和步骤:

  1. 实现 CYLPlusButtonSubclassing 协议

  2. 子类将自身类型进行注册:调用 [YourClass registerPlusButton],需要在 RootViewCOntroller 的 ViewDidLoad 中注册,也可以在 -application:didFinishLaunchingWithOptions: 方法里面操作。

这里注意,不建议在子类的 +load 方法中调用,比如像下面这样做,在 iOS10 系统上有 Crash 的风险:

+ (void)load {
   [super registerPlusButton];
}

协议提供了可选方法:

+ (NSUInteger)indexOfPlusButtonInTabBar;
+ (CGFloat)multiplierOfTabBarHeight:(CGFloat)tabBarHeight;
+ (UIViewController *)plusChildViewController;
+ (BOOL)shouldSelectPlusChildViewController;

作用分别是:

+ (NSUInteger)indexOfPlusButtonInTabBar;

用来自定义加号按钮的位置,如果不实现默认居中,但是如果 tabbar 的个数是奇数则必须实现该方法,否则 CYLTabBarController 会抛出 exception 来进行提示。

主要适用于如下情景:

Airbnb-app效果:

+ (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则表示偏下。

+ (CGFloat)constantOfPlusButtonCenterYOffsetForTabBarHeight:(CGFloat)tabBarHeight;

参考 +multiplierOfTabBarHeight: 中的公式:

PlusButtonCenterY = multiplierOfTabBarHeight * taBarHeight + constantOfPlusButtonCenterYOffset;

也就是说: constantOfPlusButtonCenterYOffset 大于0会向下偏移,小于0会向上偏移。

注意:实现了该方法,但没有实现 +multiplierOfTabBarHeight: 方法,在这种情况下,会在预设逻辑的基础上进行偏移。

详见Demo中的 CYLPlusButtonSubclass 类的实现。

+ (UIViewController *)plusChildViewController;

详见: 点击 PlusButton 跳转到指定 UIViewController

另外,如果加号按钮超出了边界,一般需要手动调用如下代码取消 tabbar 顶部默认的阴影,可在 AppDelegate 类中调用:

   //去除 TabBar 自带的顶部阴影
   [[UITabBar appearance] setShadowImage:[[UIImage alloc] init]];        

// iOS10 后 需要使用 -[CYLTabBarController hideTabBarShadowImageView] 见 AppDelegate 类中的演示;

如何调整、自定义 PlusButton 与其它 TabBarItem 的宽度?

CYLTabBarController 规定:

TabBarItem 宽度 =  ( TabBar 总宽度 -  PlusButton 宽度  ) / (TabBarItem 个数)

所以想自定义宽度,只需要修改 PlusButton 的宽度即可。

比如你就可以在 Demo中的 CYLPlusButtonSubclass.m 类里:

[button sizeToFit]; 

改为

button.frame = CGRectMake(0.0, 0.0, 250, 100);
button.backgroundColor = [UIColor redColor];

效果如下, 1.24.0

同时你也可以顺便测试下 CYLTabBarController 的这一个特性:

即使加号按钮超出了tabbar的区域,超出部分依然能响应点击事件

并且你可以在项目中的任意位置读取到 PlusButton 的宽度,借助 CYLTabBarController.h 定义的 CYLPlusButtonWidth 这个extern。可参考 +[CYLTabBarControllerConfig customizeTabBarAppearance:] 里的用法。

补充说明

自定义 TabBar 样式

如果想更进一步的自定义 TabBar 样式可在 -application:didFinishLaunchingWithOptions: 方法中设置

/**
*  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 将不会被触发这个代理方法。

//CYLTabBarController.h

@protocol CYLTabBarControllerDelegate <NSObject>

/*!
* @param tabBarController The tab bar controller containing viewController.
* @param control Selected UIControl in TabBar.
* @attention If PlusButton also add an action, then this delegate method will not be invoked when the PlusButton is selected.
*/
- (void)tabBarController:(UITabBarController *)tabBarController didSelectControl:(UIControl *)control;

@end

下面这个方法能捕获跳转前所在的控制器,以及跳转到的目标控制器。

//UITabBarController.h
@protocol UITabBarControllerDelegate <NSObject>
@optional
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController NS_AVAILABLE_IOS(3_0);
@end

注意:在调用该方法时应该始终调用 [[self cyl_tabBarController] updateSelectionStatusIfNeededForTabBarController:tabBarController shouldSelectViewController:viewController]; 来确保 PlusButton 的选中状态。示例如下:

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
   [[self cyl_tabBarController] updateSelectionStatusIfNeededForTabBarController:tabBarController shouldSelectViewController:viewController];
   return YES;
}

相关用法已经在 Demo 中展示。

遵循协议的方式如下:

@interface AppDelegate ()<UITabBarControllerDelegate, CYLTabBarControllerDelegate>

@end

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   //...
       tabBarControllerConfig.tabBarController.delegate = self;
   //...
   return YES;
}

点击 TabBarButton 时添加动画

Demo 演示的效果图:

实现如下代理方法,就能得到对应的选中控件,可以在控件上直接添加动画。

//CYLTabBarController.h

@protocol CYLTabBarControllerDelegate <NSObject>

/*!
* @param tabBarController The tab bar controller containing viewController.
* @param control Selected UIControl in TabBar.
*/
- (void)tabBarController:(UITabBarController *)tabBarController didSelectControl:(UIControl *)control;

@end

Demo 中示例代码如下:

遵循协议

@interface AppDelegate ()<UITabBarControllerDelegate, CYLTabBarControllerDelegate>

@end
//AppDelegate.m
- (void)tabBarController:(UITabBarController *)tabBarController didSelectControl:(UIControl *)control {
   UIView *animationView;
   // 如果 PlusButton 也添加了点击事件,那么点击 PlusButton 后不会触发该代理方法。
   if ([control isKindOfClass:[CYLExternPlusButton class]]) {
       UIButton *button = CYLExternPlusButton;
       animationView = button.imageView;
   } else if ([control isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
       for (UIView *subView in control.subviews) {
           if ([subView isKindOfClass:NSClassFromString(@"UITabBarSwappableImageView")]) {
               animationView = subView;
           }
       }
   }
   
   if ([self cyl_tabBarController].selectedIndex % 2 == 0) {
       [self addScaleAnimationOnView:animationView];
   } else {
       [self addRotateAnimationOnView:animationView];
   }
}

//缩放动画
- (void)addScaleAnimationOnView:(UIView *)animationView {
   //需要实现的帧动画,这里根据需求自定义
   CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
   animation.keyPath = @"transform.scale";
   animation.values = @[@1.0,@1.3,@0.9,@1.15,@0.95,@1.02,@1.0];
   animation.duration = 1;
   animation.calculationMode = kCAAnimationCubic;
   [animationView.layer addAnimation:animation forKey:nil];
}

//旋转动画
- (void)addRotateAnimationOnView:(UIView *)animationView {
   [UIView animateWithDuration:0.32 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
       animationView.layer.transform = CATransform3DMakeRotation(M_PI, 0, 1, 0);
   } completion:nil];
   
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       [UIView animateWithDuration:0.70 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
           animationView.layer.transform = CATransform3DMakeRotation(2 * M_PI, 0, 1, 0);
       } completion:nil];
   });
}

横竖屏适配

TabBar 横竖屏适配时,如果你添加了 PlusButton,且适配时用到了 TabBarItem 的宽度, 不建议使用系统的UIDeviceOrientationDidChangeNotification , 请使用库里的 CYLTabBarItemWidthDidChangeNotification 来更新 TabBar 布局,最典型的场景就是,根据 TabBarItem 在不同横竖屏状态下的宽度变化来切换选中的TabBarItem 的背景图片。Demo 里 CYLTabBarControllerConfig.m 给出了这一场景的用法:

CYLTabBarController.h 中提供了 CYLTabBarItemWidth 这一extern常量,并且会在 TabBarItem 的宽度发生变化时,及时更新该值,所以用法就如下所示:

- (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 ,所以在任意对象中,一行代码就可以访问到一个初始化好的 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

用法:

//导入 CYLTabBarController.h
#import "CYLTabBarController.h"

- (void)viewDidLoad {
   [super viewDidLoad];
   CYLTabBarController *tabbarController = [self cyl_tabBarController];
   /*...*/
}

点击 PlusButton 跳转到指定 UIViewController

提供了一个协议方法来完成本功能:

实现该方法后,能让 PlusButton 的点击效果与跟点击其他 TabBar 按钮效果一样,跳转到该方法指定的 UIViewController 。

注意:必须同时实现 +indexOfPlusButtonInTabBar 来指定 PlusButton 的位置。

遵循几个协议:

#pragma mark - CYLPlusButtonSubclassing

+ (UIViewController *)plusChildViewController {
   UIViewController *plusChildViewController = [[UIViewController alloc] init];
   plusChildViewController.view.backgroundColor = [UIColor redColor];
   plusChildViewController.navigationItem.title = @"PlusChildViewController";
   UIViewController *plusChildNavigationController = [[UINavigationController alloc]
                                                  initWithRootViewController:plusChildViewController];
   return plusChildNavigationController;
}

+ (NSUInteger)indexOfPlusButtonInTabBar {
   return 2;
}

+ (BOOL)shouldSelectPlusChildViewController {
   BOOL isSelected = CYLExternPlusButton.selected;
   if (isSelected) {
//        HDLLogDebug("🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"PlusButton is selected");
   } else {
//        HDLLogDebug("🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"PlusButton is not selected");
   }
   return YES;
}

+ (CGFloat)multiplierOfTabBarHeight:(CGFloat)tabBarHeight {
   return  0.3;
}

+ (CGFloat)constantOfPlusButtonCenterYOffsetForTabBarHeight:(CGFloat)tabBarHeight {
   return (CYL_IS_IPHONE_X ? - 6 : 4);
}

//+ (NSString *)tabBarContext {
//    return NSStringFromClass([self class]);
//}

另外你可以通过下面这个方法获取到 PlusButton 的点击事件:

+ (BOOL)shouldSelectPlusChildViewController;

用法如下:

+ (BOOL)shouldSelectPlusChildViewController {
    BOOL isSelected = CYLExternPlusButton.selected;
    if (isSelected) {
        NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"PlusButton is selected");
    } else {
        NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"PlusButton is not selected");
    }
    return YES;
}

让TabBarItem仅显示图标,并使图标垂直居中

要想实现该效果,只需要在设置 tabBarItemsAttributes该属性时不传 title 即可。

比如:在Demo的基础上,注释掉图中红框部分:

注释前 注释后
https://github.com/ChenYilong https://github.com/ChenYilong

可以通过这种方式来达到 Airbnb-app 的效果:

如果想手动设置偏移量来达到该效果: 可以在 -setViewControllers: 方法前设置 CYLTabBarControllerimageInsetstitlePositionAdjustment 属性

这里注意:设置这两个属性后,TabBar 中所有的 TabBarItem 都将被设置。并且第一种做法的逻辑将不会执行,也就是说该做法优先级要高于第一种做法。

做法在demo中已经给出。

但是想达到Airbnb-app的效果只有这个接口是不行的,还需要自定义下 TabBar 的高度,你需要设置 CYLTabBarControllertabBarHeight 属性。你可以在Demo的 CYLTabBarControllerConfig.m 中的 -customizeTabBarAppearance: 方法中设置。

注:“仅显示图标,并使图标垂直居中”这里所指的“图标”,其所属的类是私有类: UITabBarSwappableImageView,所以 CYLTabBarController 在相关的接口命名时会包含 SwappableImageView 字样。另外,使用该特性需要 pod update 到 1.5.5以上的版本。

多TabBar嵌套,并指定PlusButton位置

该功能旧版本可能并不支持,建议更新最新版中使用。

效果图:

实现 PlusButton 的如下协议方法指定 context:

//CYLPlusButtonSubclassing
+ (NSString *)tabBarContext;

当该值与 TabBarController 的 context 能够匹配上,PlusButton 将会展示。如果 PlusButton 与 TabBarController 均未制定 context 值,那么默认 context 值是相等的。

目前仅支持一个 PlusButton 展示一次,不限层级。如果与多个 TabBarController 的 context 能够匹配上,仅展示在最先一次的匹配上的 TabBarController 上。

在 Swift 项目中使用 CYLTabBarController

仓库中给出了一个Swift Demo,文件夹叫做 Example-Swift。

如果想在Swift 项目中,将本库作为静态库使用:

CocoaPods 1.5.0+ (with Xcode 9+):

platform :ios, '8.0'
# Uncomment the next line when you want all Pods as static framework
# use_modular_headers!
pod 'CYLTabBarController', '~> 1.29.0', :modular_headers => true

作为动态库使用:

platform :ios, '8.0'
use_frameworks!

感谢@WeMadeCode 提供的 Swift 版 Demo,原仓库地址:WeMadeCode/CYLTabBarController-Swift

具体的编写步骤参考热心网友提供的教程: 《从头开始swift2.1 仿搜材通项目(三) 主流框架Tabbed的搭建》

这里注意,文章的早期一个版本的示例代码有问题(笔者注:现在已经更新了),少了设置 PlusButton 大小的代码: 这将导致 PlusButton 点击事件失效,具体修改代码如下:

搭配 Storyboard 使用 CYLTabBarController

参考:

  • 见这里 issue讨论
  • 这里 ,里面有个文件夹CYLTabBarControllerTestDemo,这个Demo演示了如何搭配 Storyboard 使用。

源码实现原理

参考: 《[Note] CYLTabBarController》

更多文档信息可查看 CocoaDocs:CYLTabBarController

FAQ

更多Q-A内容,可以在这里查看: issue-FAQ Q:为什么放置6个TabBarItem会显示异常?

A:

Apple 规定:

一个 TabBar 上只能出现最多5个 TabBarItem ,第六个及更多的将不被显示。

另外注意,Apple检测的是 UITabBarItem 及其子类,所以放置“加号按钮”,这是 UIButton 不在“5个”里面。

最多只能添加5个 TabBarItem ,也就是说加上“加号按钮”,一共最多在一个 TabBar 上放置6个控件。否则第6个及之后出现 TabBarItem 会被自动屏蔽掉。而且就Apple的审核机制来说,超过5个也会被直接拒绝上架。

Q:我把 demo 两侧的 item 各去掉一个后,按钮的响应区域就变成下图的样子了:

A:v1.5.5 版本已经修复了该问题,现在不会出现类似的问题了:点击按钮区域却不响应,响应区域有偏移。

Q: 如何实现添加选中背景色的功能 ,像下面这样:

A:我已经在 Demo 中添加了如何实现该功能的代码: 详情见 CYLTabBarControllerConfig 类中下面方法的实现:

/**
*  更多TabBar自定义设置:比如:tabBarItem 的选中和不选中文字和背景图片属性、tabbar 背景图片属性
*/
- (void)customizeTabBarAppearance:(CYLTabBarController *)tabBarController;

效果如下:

Q: 当 ViewController 设置的 self.titletabBarItemsAttributes 中对应的 title 不一致的时候,会出现如图的错误,排序不对了

A:在 v1.0.7 版本中已经修复了该 bug,但是也需要注意:

请勿使用 self.title = @"同城"; 这种方式,请使用 self.navigationItem.title = @"同城";

self.title = @"同城"; 这种方式,如果和 tabBarItemsAttributes 中对应的 title 不一致的时候可能会导致如下现象(不算 bug,但看起来也很奇怪):

规则如下:

   self.navigationItem.title = @"同城";    //✅sets navigation bar title.The right way to set the title of the navigation
   self.tabBarItem.title = @"同城";   //❌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 : 当使用这个方法时 -[UIViewController cyl_popSelectTabBarChildViewControllerAtIndex:] 系列方法时,会出现如下的黑边问题。

A: 这个是 iOS 系统的BUG,经测试iOS9.3已经修复了,如果在更早起版本中出现了,可以通过下面将 rootWindow 的背景色改为白色来避免:比如你可以 Appdelegate 类里这样设置:

//#import "CYLTabBarController.h"
   [[self cyl_tabBarController] rootWindow].backgroundColor = [UIColor whiteColor];

Q:我现在已经做好了一个比较简单的中间凸起的 icon 但是超过了49这个高度的位置是不能效应的 我想请问你的demo哪个功能是可以使我超出的范围也可以响应的呢?

A: 这个是自动做的,但是 CYLTabBarController 只能保证的是:只要是 UIButton 的 frame 区域内就能响应。

请把 button 的背景颜色设置为显眼的颜色,比如红色,比如像下面的plus按钮,红色部分是能接收点击事件的,但是超出了红色按钮的,黄色的图片区域,依然是无法响应点击事件的。

这是因为,在响应链上,UIControl 能响应点击事件, UIImage 无法响应。

Q:为什么在iOS10上会Crash,iOS9上不会?

A: 在注册加号按钮时,需要在 -application:didFinishLaunchingWithOptions: 方法里面调用 [YourClass registerPlusButton]

这里注意,不能在子类的 +load 方法中调用,比如像下面这样做,在 iOS10 系统上有 Crash 的风险:

+ (void)load {
   [super registerPlusButton];
}

Q: 我的样式是点击 plusButton 后跳转到一个 ViewController,但是选中了一次中间的 plusButton 之后,再点别的 tabItem ,中间不会变成 normal 的状态。

A: 有两种情况会造成这个问题:

  1. 应该是你的 tabBar 设置了 delegate 了,你要是 tabBar 的代理没设置的话,默认会有这个 selected 状态切换的处理。你设置代理后,会覆盖我的行为。所以手动加上就好了。
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
   [[self cyl_tabBarController] updateSelectionStatusIfNeededForTabBarController:tabBarController shouldSelectViewController:viewController];
   return YES;
}
  1. plusButton 添加了自定义点击事件或者自定义手势,因为这样会造成点击事件冲突或手势冲突,当需要 pushViewController 的时候,这个库会自动的添加点击事件,你这里重新加了点击事件所以冲突了;

在你项目的基础,把 plusButton 的点击事件取消掉,也就是 addTarget 这一行注释掉,手势事件也同理,应该就ok了

A: PlusButton 与其他的 TabBarItem 距离没有平均分布

(对应于 issue#36 )

把这 Demo 里的这一行代码改下:

[button sizeToFit];

改成:

button.frame = CGRectMake(0.0, 0.0, w, h);

那么如果单是放一个照相机的图片,一般是多大的尺寸?

这个要看设计图,通常情况下,你可以写死与其他TabBarItem一样大小:

[UIScreen mainScreen].bounds.size.width / [CYLTabBarController allItemsInTabBarCount]

Q:如何兼容 Lottie 动画? A:用法见:https://github.com/ChenYilong/CYLTabBarController/issues/341

(更多iOS开发干货,欢迎关注 微博@iOS程序犭袁


Posted by Posted by 微博@iOS程序犭袁 & 公众号@iTeaTime技术清谈 原创作品,版权声明:License MIT