前言
最近的项目中有需求在 tabbar 中间添加凸起按钮, 并且点击时按钮要旋转, 看了仿闲鱼的凸起, 点击后是 present 出来 View, 而不是像常规的 tabbar 上添加一个页面 (亲测, 闲鱼的超出 Tabbar 部分点击是没有反应的, 这是 bug 啊, 下文对这个问题有详解), 所以不符合要求, 经过一段摸索最后得的一个比较好的效果, 下面看效果图
(本文简书地址: https://www.jianshu.com/p/5160a1b48679)
需求分析
tabbar 有 5 个 item, 每个对应一个页面
中间 item 为凸起按钮
中间按钮点击后旋转
效果实现
设置 5 个 item 我们一步步来解决这个问题, 首先创建 MCTabBarController 继承 UITabBarController, 然后和常规一样创建 5 个 item, 中间的按钮不设置图片, 代码如下
- //MCTabBarController.m
- // 添加子控制器
- - (void)addChildViewControllers{
- // 图片大小建议 32*32
- [self addChildrenViewController:[[ViewController alloc] init] andTitle:@"首页" andImageName:@"tab1"];
- [self addChildrenViewController:[[ViewController alloc] init] andTitle:@"扩展" andImageName:@"tab2"];
- // 中间这个不设置东西, 只占位
- [self addChildrenViewController:[[ViewController alloc] init] andTitle:@"旋转" andImageName:@""];
- [self addChildrenViewController:[[ViewController alloc] init] andTitle:@"发现" andImageName:@"tab3"];
- [self addChildrenViewController:[[ViewController alloc] init] andTitle:@"我的" andImageName:@"tab4"];
- }
- - (void)addChildrenViewController:(UIViewController *)childVC andTitle:(NSString *)title andImageName:(NSString *)imageName{
- childVC.tabBarItem.image = [UIImage imageNamed:imageName];
- // 选中的颜色由 tabbar 的 tintColor 决定
- childVC.tabBarItem.selectedImage = [UIImage imageNamed:imageName];
- childVC.title = title;
- BaseNavigationController *baseNav = [[BaseNavigationController alloc] initWithRootViewController:childVC];
- [self addChildViewController:baseNav];
- }
这样实现的效果如下图所示
添加凸起按钮 我们可以在 UITabBar 上添加我们的凸起按钮, 让他的位置在没有设置的中间按钮偏上, 按钮的点击和中间按钮点击绑定, 这里直接在 MCTabBarController.m 中添加会有问题 1, 因为凸起按钮的 frame 超出了 UITabBar 的 frame, 这样超出的区域点击按钮会没有响应 (图二红框区域), 原因和解决办法详情参考我的这篇 iOS UIButton 点击无响应的解决办法 http://www.jianshu.com/p/7a35d6c25bfe , 由于要在 UITabBar 上添加凸起按钮, 并且处理点击无效的问题, 所以这里创建了 MCTabBar 继承 UITabBar
2, 由于 UITabBar 是 readonly 的, 所以我们不能直接对他进行赋值, 这里利用 KVC 访问私有变量将 MCTabBar 赋值给 "tabBar" 具体实现 MCTabBar
- #import <UIKit/UIKit.h>
- @interface MCTabBar : UITabBar
- @property (nonatomic, strong) UIButton *centerBtn; // 中间按钮
- @end
- @implementation MCTabBar
- - (instancetype)init{
- if (self = [super init]){
- [self initView];
- }
- return self;
- }
- - (void)initView{
- _centerBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- // 设定 button 大小为适应图片
- UIImage *normalImage = [UIImage imageNamed:@"tabbar_add"];
- _centerBtn.frame = CGRectMake(0, 0, normalImage.size.width, normalImage.size.height);
- [_centerBtn setImage:normalImage forState:UIControlStateNormal];
- // 去除选择时高亮
- _centerBtn.adjustsImageWhenHighlighted = NO;
- // 根据图片调整 button 的位置 (图片中心在 tabbar 的中间最上部, 这个时候由于按钮是有一部分超出 tabbar 的, 所以点击无效, 要进行处理)
- _centerBtn.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - normalImage.size.width)/2.0, - normalImage.size.height/2.0, normalImage.size.width, normalImage.size.height);
- [self addSubview:_centerBtn];
- }
- // 处理超出区域点击无效的问题
- - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
- if (self.hidden){ // 如果 tabbar 隐藏了, 那么直接执行系统方法
- return [super hitTest:point withEvent:event];
- }else {
- // 转换坐标
- CGPoint tempPoint = [self.centerBtn convertPoint:point fromView:self];
- // 判断点击的点是否在按钮区域内
- if (CGRectContainsPoint(self.centerBtn.bounds, tempPoint)){
- // 返回按钮
- return _centerBtn;
- }else {
- return [super hitTest:point withEvent:event];
- }
- }
- }
利用 KVC 赋值
- //MCTabBarController.m
- - (void)viewDidLoad {
- [super viewDidLoad];
- _mcTabbar = [[MCTabBar alloc] init];
- [_mcTabbar.centerBtn addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
- // 选中时的颜色
- _mcTabbar.tintColor = [UIColor colorWithRed:27.0/255.0 green:118.0/255.0 blue:208/255.0 alpha:1];
- // 透明设置为 NO, 显示白色, view 的高度到 tabbar 顶部截止, YES 的话到底部
- _mcTabbar.translucent = NO;
- // 利用 KVC 将自己的 tabbar 赋给系统 tabBar
- [self setValue:_mcTabbar forKeyPath:@"tabBar"];
- self.selectItem = 0; // 默认选中第一个
- self.delegate = self;
- [self addChildViewControllers];
- }
点击旋转 在中间按钮的点击事件执行时旋转第二个 index, 然后执行旋转动画, 在 tabbar 的代理事件中监听旋中中间按钮的事件, 然后执行旋转动画, 其他按钮则移除动画, 代码如下
- - (void)buttonAction:(UIButton *)button{
- self.selectedIndex = 2;// 关联中间按钮
- if (self.selectItem != 2){ // 如果是选中的旋转按钮则不再次进行旋转
- [self rotationAnimation];
- }
- self.selectItem = 2;
- }
- //tabbar 选择时的代理
- - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
- if (tabBarController.selectedIndex == 2){// 选中中间的按钮
- if (self.selectItem != 2){
- [self rotationAnimation];
- }
- }else {
- [_mcTabbar.centerBtn.layer removeAllAnimations];
- }
- }
- // 旋转动画
- - (void)rotationAnimation{
- CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
- rotationAnimation.toValue = [NSNumber numberWithFloat:M_PI*2.0];
- rotationAnimation.duration = 3.0;
- rotationAnimation.repeatCount = HUGE;
- [_mcTabbar.centerBtn.layer addAnimation:rotationAnimation forKey:@"key"];
- }
其他 这里写了 BaseNavigationController 继承自 UINavigationController, 处理了 push 后隐藏底部 UITabBar 的情况, 并解决了 iPhonX 上 push 时 UITabBar 上移的问题.
来源: https://juejin.im/post/5c0784cc5188251ba9057cea