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
    评论
iOS 中,可以使用 Objective-C 自定义 TabBar。下面是一个简单的步骤: 1. 创建一个继承自 UITabBarController 的类,例如 MyTabBarController。 2. 在 MyTabBarController.m 中,实现 viewDidLoad 方法,在其中创建自定义 TabBar。 ```objc - (void)viewDidLoad { [super viewDidLoad]; // 创建自定义 TabBar MyTabBar *myTabBar = [[MyTabBar alloc] init]; myTabBar.delegate = self; [self setValue:myTabBar forKey:@"tabBar"]; } ``` 3. 在 MyTabBarController.m 中,实现 UITabBarDelegate 协议中的方法,例如 tabBar:didSelectItem:,处理 TabBar 点击事件。 ```objc - (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item { // 处理 TabBar 点击事件 } ``` 4. 创建一个继承自 UITabBar 的类,例如 MyTabBar。 5. 在 MyTabBar.m 中,实现 layoutSubviews 方法,在其中布局 TabBar。 ```objc - (void)layoutSubviews { [super layoutSubviews]; // 布局 TabBar } ``` 6. 在 MyTabBar.m 中,实现 drawRect: 方法,在其中绘制 TabBar。 ```objc - (void)drawRect:(CGRect)rect { // 绘制 TabBar } ``` 7. 在 MyTabBar.m 中,实现 touchUpInside 方法,在其中处理 TabBar 点击事件。 ```objc - (void)touchUpInside:(UIButton *)button { // 处理 TabBar 点击事件 } ``` 8. 在 MyTabBar.m 中,添加 TabBar 上的按钮,例如: ```objc - (void)layoutSubviews { [super layoutSubviews]; // 添加 TabBar 上的按钮 UIButton *button1 = [UIButton buttonWithType:UIButtonTypeCustom]; button1.frame = CGRectMake(0, 0, self.frame.size.width / 4, self.frame.size.height); [button1 addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:button1]; UIButton *button2 = [UIButton buttonWithType:UIButtonTypeCustom]; button2.frame = CGRectMake(self.frame.size.width / 4, 0, self.frame.size.width / 4, self.frame.size.height); [button2 addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:button2]; UIButton *button3 = [UIButton buttonWithType:UIButtonTypeCustom]; button3.frame = CGRectMake(self.frame.size.width / 2, 0, self.frame.size.width / 4, self.frame.size.height); [button3 addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:button3]; UIButton *button4 = [UIButton buttonWithType:UIButtonTypeCustom]; button4.frame = CGRectMake(self.frame.size.width * 3 / 4, 0, self.frame.size.width / 4, self.frame.size.height); [button4 addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:button4]; } ``` 9. 在 MyTabBar.m 中,重写 setItems:animated: 方法,在其中设置 TabBar 上按钮的图标和标题。 ```objc - (void)setItems:(NSArray<UITabBarItem *> *)items animated:(BOOL)animated { [super setItems:items animated:animated]; // 设置 TabBar 上按钮的图标和标题 for (int i = 0; i < items.count; i++) { UITabBarItem *item = items[i]; UIButton *button = self.subviews[i + 1]; [button setImage:item.image forState:UIControlStateNormal]; [button setTitle:item.title forState:UIControlStateNormal]; [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; } } ``` 以上就是使用 Objective-C 自定义 TabBar 的简单步骤。当然,还可以根据需求进行更详细的定制。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值