iOS之高度自定义TabBar的优雅实现

需求的由来

日常开发里,有时候产品经理会走过来丢给你一个无厘头的需求,App的底部TabBar 需要支持后台配置数量和动态替换位置,还可以支持显示gif,还可以支持凸起效果,我的天,提我的40米大刀来,产品你先跑39米。对于这样的开发需求,有时候是很崩溃的,因为涉及到的业务代码改动可能就很大了。

传统的实现

传统实现自定义TabBar 的方式通过setValue:forKey:的方式给TabController赋值一个自定义的tab,因为这样我们能做的事毕竟也有限,因为自定义的TabBar归根于低依然是UITabBar类,能用api依然有限。

如何做到完全让开发者随意去布局这个tabBar,思考想到了自定义view就好了,你需要什么样的效果就往上面加,需要注意的是做好约束相关变化监听。

更巧妙的实现

实现的一套TabBarItem-TabBar组合,通过addSubview:的方式添加到原生的TabBar上即可。
相关实现如下:

自定义TabBarItem的.h
基本的UI控件

@property (nonatomic, strong) UITabBarItem *tabBarItem;             //关联的原生TabBarItem
@property (nonatomic, assign, getter=isSelected) BOOL selected;     //选择状态
@property (nonatomic, strong) FLAnimatedImageView *imageView;       //图标
@property (nonatomic, strong) UILabel *titleLabel;                  //文字
@property (nonatomic, strong) UIColor *itemTitleColor;              //文字默认颜色
@property (nonatomic, strong) UIColor *selectedItemTitleColor;      //文字选中颜色
@property (nonatomic, strong) UIFont *itemTitleFont;                //文字字体
@property (nonatomic, strong) UIFont *badgeTitleFont;               //Badge字体
/**
 *  初始化,默认文字和图标间距参数,接近官方数据ratio为5pt
 */
- (instancetype)initWithItemImageRatio:(CGFloat)itemImageRatio;

自定义TabBarItem的.m
需要监听原生的TabbarItem的相关值得变化,更新到自己的自定义item。

[tabBarItem addObserver:self forKeyPath:@"image" options:NSKeyValueObservingOptionNew context:nil];
[tabBarItem addObserver:self forKeyPath:@"selectedImage" options:NSKeyValueObservingOptionNew context:nil];
[tabBarItem addObserver:self forKeyPath:@"badgeValue" options:NSKeyValueObservingOptionNew context:nil];
[tabBarItem addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];

基类BaseTabBarController
基类BaseTabBarController的主要工作有以下几个:

  1. loadView时添加自定义的tabBar到原生的TabBar上,设置事件代理和做好约束
  2. 在BaseTabBarController自身的生命相关周期函数里需要把原生的tabBarItem设置隐藏
  3. 重写 setViewControllers:函数,并启动监听,需要注意的是添加到导航控制器的方式不能使用addChildViewController:方式 添加到导航控制器的方式不能使用addChildViewController:方式
  4. 重写 setSelectedIndex:触发点击,让自定义的item监听到相关变化,从而更新状态和值。
  5. 如下方法展示

隐藏原生item:

#self.tabBar原生
[self.tabBar.subviews enumerateObjectsUsingBlock:^(__kindof UIView * obj, NSUInteger idx, BOOL * stop) {
      if ([obj isKindOfClass:[UIControl class]]) {
          [obj setHidden:YES];
      }
}];

重写setViewControllers:

#self.gtTabBar自定view
// 1.赋值nav的方式只能选[setViewControllers:]
- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers{
   
    [self.gtTabBar.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    self.gtTabBar.tabBarItems = [NSMutableArray array];
    
    for (UINavigationController *nav in viewControllers) {
        
        // 2.为了启动KVO,赋值和关联的tabBarItem需要相同
        NSString *title = nav.tabBarItem.title;
        nav.tabBarItem.title = title;
        [self.gtTabBar addTabBarItem:nav.tabBarItem];
    }

    [super setViewControllers:viewControllers];
}

重写setSelectedIndex:

- (void)setSelectedIndex:(NSUInteger)selectedIndex {
    [super setSelectedIndex:selectedIndex];
    
    # 因为自定义的item监听有原生item的selected属性,当这里状态改变的同时,状态会传递到自定义的组合上。
    self.gtTabBar.selectedItem.selected = NO;
    self.gtTabBar.selectedItem = self.gtTabBar.tabBarItems[selectedIndex];
    self.gtTabBar.selectedItem.selected = YES;
}

最后自定义点击事件通过代理传递给外部

#pragma mark - XXTabBarDelegate Method

- (void)tabBar:(GTTabBar *)tabBarView didSelectedItemFrom:(NSInteger)from to:(NSInteger)to {
    
    self.selectedIndex = to;
}

总结

  1. 通过自定义一套组合的tabBar和item实现高度自定义的假tabBar,对于需要更高自定义的需求几乎可以很好支持,维护和扩展都很方便
  2. 目前也用在项目项目中,虽然遇见过一两个小问题,最后都解决了,算是完善该框架的实践代价

遇见的问题记录

Bug描述:
记得有个bug-jira,测试小姐姐是这么描述的,在启动app过程中,退到后台再进去app偶发的自定义Tab上子控件位置显示位置不对或错乱,后来经过大量的测试,确实属实。

出现的原因:
因为在启动但未进入app首页的过程中,把app退出到后台时,某一些ios系统版本上TabBarController的部分生命周期函数不会走,这导致添加上去的自定义假Tab的生命周期函数也跟着不准,从而引出这个偶发的bug。

解决途径:
监听程序从后台回到前台的系统通知UIApplicationDidBecomeActiveNotification,从而再次检查TabBarController和定义的tabBar的相关约束,走一次layoutIfNeed就好了。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值