iOS之自定义tabBar

在iOS原生的tabBar中,能够实现按钮的点击事件,能够实现视图控制器的切换等,但是在实际工程中,对于tabBar的要求的功能往往是系统自己实现不了的,所以我们这里就需要用到自定义的tabBar了。
对于tabBar上展示视图控制器,我们会采用的是在把几个视图控制直接加载到tabBarController上去。这里新建三个视图控制器,由于在
一、系统样式
ViewController会有其他代码,所以我们这里另一写一个类,在这里只设置一个背景颜色就可以了。所以我们先新建一个类叫做WJViewController,让它继承自UIViewController。这里设置视图的背景颜色,这里可以设置为随机色。

// 设置背景颜色为随机色
    self.view.backgroundColor = [UIColor colorWithRed:arc4random() % 256 /255.0 green:arc4random() % 256 /255.0 blue:arc4random() % 256 /255.0 alpha:1.0];

然后新建三个视图控制器,继承自WJViewController,这样三个视图控制的背景颜色都有了。新建的三个类,分别命名为WJFirstViewController、WJSecondViewController、WJThirdViewController。然后我们去实现相关方法。
1.首先创建一个tabBarController,用于接收实例化好的视图控制器。
// 1.创建标签栏控制器 UITabBarController *tabBarController = [[UITabBarController alloc]init];
2.然后就可创建需要让标签栏控制器管理的子视图控制器:

// 1>.第一个视图控制器
    WJFirstViewController *first = [[WJFirstViewController alloc]init];
    first.tabBarItem.title = @"first";
    first.tabBarItem.image = [[UIImage imageNamed:@"tiaoman_u.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    first.tabBarItem.selectedImage = [[UIImage imageNamed:@"tiaoman_d.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
// 2>.第二个视图控制器
    WJSecondViewController *second = [[WJSecondViewController alloc]init];
    second.tabBarItem.title = @"second";
    second.tabBarItem.image = [[UIImage imageNamed:@"faxian_u.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    second.tabBarItem.selectedImage = [[UIImage imageNamed:@"faxian_d.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
// 3>.第三个视图控制器
    WJThirdViewController *third = [[WJThirdViewController alloc]init];
    third.tabBarItem.title = @"third";
    third.tabBarItem.image = [[UIImage imageNamed:@"wode_u.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    third.tabBarItem.selectedImage = [[UIImage imageNamed:@"wode_d.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

3.对子视图控制器进行管理
有两种方案可以选择:一是用数组进行接收,二是用一个方法进行接收子视图控制器。
法一:
//tabBarController.viewControllers = @[first, second, third];
法二:

    [tabBarController addChildViewController:first];
    [tabBarController addChildViewController:second];
    [tabBarController addChildViewController:third];

这里最好使用第二种方法进行对子视图控制器进行管理。
代码进行到这里就可以把视图控制器tabBarController展示在Windows上了,所以需要把tabBarController设为根视图。

    // 可以通过每个已经显示在界面上的视图,拿到当前应用程序的window
    self.view.window.rootViewController = tabBarController;
}

这样代码进行到这里系统样式的功能就已经实现了,但是对于我们实际要求的功能相差甚远,所以我们开始自定义的tabBar的创建。
二、自定义样式
下面我们来简单分析下系统的tabBar的功能。
2.1分析
分析首先按钮有按钮点击事件,点击按钮后会改变相应的视图控制器,而且点击按钮后的图标会有相应的改变。而自定义的tabBar也需要达到这些功能,甚至还需要达到自定义按钮的形状的和功能的要求。
对于系统的tabBar来说,能够展示按钮的文字和图片,而这些内容是加载在tabBar的_UITabBarBackgroundView上的UITabBarButton上的UILabel和UIImageView上的。而我们要自定义按钮就需要覆盖系统本身的控件,但这些控件我们是拿不到的,用点语法这些控件是不能联想出来的,但我们又要实现和系统一样的功能,我们只能用一个方法把tabBar上的所有控件移除,因此要用遍历的方法实现移除;我们移除了系统自带的tabBar后就需要自定义一个wjTabBar来代替原有的tabBar。然后在wjTabBar上添加按钮,然后设置相应的点击事件,更改相应的状态就可实现和系统一样的功能。

// 声明一个wjTabBar的全局变量
@property (nonatomic, strong) WJTabBar *wj_tabBar;
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    // 2.删除自动创建的tabBarButton
    for (UIView *view in self.tabBar.subviews) {
        // 打印tabBar上所有控件
        NSLog(@"%@",self.tabBar.subviews);
        // 移除tabBar上所有的子控件
        [view removeFromSuperview];
    }
    // 把self.wj_tabBar添加到视图上
    [self.tabBar addSubview:self.wj_tabBar];
}

对wj_tabBar设置懒加载

#pragma mark - 懒加载
- (WJTabBar *)wj_tabBar {
    if (!_wj_tabBar) {
        // 创建wj_tabBar
        _wj_tabBar = [[WJTabBar alloc]init];
        // 设置frame
        _wj_tabBar.frame = self.tabBar.bounds;
        // 设置一个背景颜色
        _wj_tabBar.backgroundColor = [UIColor cyanColor];
    }
    return _wj_tabBar;
}

2.2现在分析下按钮的创建:
按钮的创建应该视图控制器创建有关,所以在设计方法的时候应该传一个视图控制器的参数;按钮还应该设置文字,选中时的图片和普通状态下的图片。
2.2.1把tabBar展示到视图上
考虑到后期调用的方便,可以在.h文件中暴露出接口,以供使用;所以在.h文件中设置方法,然后在.m文件实现相关的方法。

#pragma mark - 添加子视图控制器
- (void)addController:(UIViewController *)controller withTitle:(NSString *)title imageName:(NSString *)imageName selectedImageName:(NSString *)selectedImageName {

    // 设置tabBarItem的子视图
    controller.tabBarItem.title = title;
    controller.tabBarItem.image = [[UIImage imageNamed:imageName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    controller.tabBarItem.selectedImage = [[UIImage imageNamed:selectedImageName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    
    // 将视图控制器添加到标签栏控制器中
    [self addChildViewController:controller];
    
    // 可以让自己定制的tabBar去创建一个对应的按钮
    [self.wj_tabBar addButtonWithTabBarItem:controller.tabBarItem];
    
    /* 测试的
    static int i = 0;
    if (i == 0) {
        WJTabBarButton *button = [[WJTabBarButton alloc]initWithFrame:CGRectMake(10, 0, 45, 45)];
        button.item = controller.tabBarItem;
        
        button.normalColor = [UIColor blueColor];
        button.selectedColor = [UIColor greenColor];
        [button addTarget:self action:@selector(firstClick:)];
        [self.wj_tabBar addSubview:button];
        i = 1;
    }
     */
}

然后去ViewControllers去添加视图控制器到tabBar上

#pragma mark - 创建视图控制器
- (void)wj_creatControll {
    // 1.创建标签栏控制器
    WJTabBarController *tabBarController = [[WJTabBarController alloc]init];
    
    WJFirstViewController *first = [[WJFirstViewController alloc]init];
    [tabBarController addController:first withTitle:@"first" imageName:@"tiaoman_u.png" selectedImageName:@"tiaoman_d.png"];
    
    WJSecondViewController *second = [[WJSecondViewController alloc]init];
    [tabBarController addController:second withTitle:@"second" imageName:@"faxian_u.png" selectedImageName:@"faxian_d.png"];
    
    //WJThirdViewController *third = [[WJThirdViewController alloc]init];
    //[tabBarController addController:third withTitle:@"third" imageName:@"wode_u.png" selectedImageName:@"wode_d.png"];
    
    // 可以通过每个已经显示在界面上的视图,拿到当前应用程序的window
    self.view.window.rootViewController = tabBarController;
    
    // 设置第几个被选中
    tabBarController.selectedIndex = 0;
    
    // 这只centerView
    UIButton *center = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 60, 60)];
    [center setImage:[UIImage imageNamed:@"luffy1@2x.png"] forState:UIControlStateNormal];
    tabBarController.centerView = center;
}

以上涉及到了WJTabBarButton这个类,这个类直接继承自UIView,在这个类中定义了几个属性:

2.2.2创建按钮
我们这里就可以去创建按钮了,首先去创建出按钮,然后去设计frame。
自定义的tabBar,我们就可以实现系统实现不了的样式,比如中间的按钮样式特别定制,比如像微博的tabBar中间的‘+’按钮。我们下面就实现实现下有特殊按钮的样式的tabBar。

#pragma mark - 当考虑有centerView的时候
- (void)setFrameWithCenter {
    // 通用属性
    CGFloat tabBarWidth = self.frame.size.width;
    CGFloat tabBarHeight = self.frame.size.height;
    NSInteger buttonCount = self.subviews.count;
    if (self.centerView) {
        // 有centerView的情况
        buttonCount -= 1;
    }
    
    // centerView的相关属性
    CGFloat centerWidth = self.centerView.frame.size.width;
    CGFloat centerHeight = self.centerView.frame.size.height;
    CGFloat centerX = (tabBarWidth - centerWidth) / 2.0f;
    CGFloat centerY;
    if (centerHeight <= tabBarHeight) {
        centerY = (tabBarHeight - centerHeight) / 2.0f;
    }
    else {
        centerY = tabBarHeight - centerHeight;
    }
    self.centerView.frame = CGRectMake(centerX, centerY, centerWidth, centerHeight);
    
    // 按钮frame
    CGFloat buttonY = 0;
    CGFloat buttonHeight = self.frame.size.height;
    CGFloat buttonWidth = (tabBarWidth - centerWidth) / buttonCount;
    CGFloat buttonX;
    int i = 0;
    for (UIView *wjview in self.subviews) {
        // 拿到按钮
        if (wjview.tag != CenterTag) {
            WJTabBarButton *button = (WJTabBarButton *)wjview;
            // 计算frame
            // 中间按钮之前的button
            if (i < buttonCount / 2) {
                buttonX = i * buttonWidth;
            }
            else {
                // 中间按钮之后的button
                buttonX = i * buttonWidth + centerWidth;
            }
            
            // 设置frame
            button.frame = CGRectMake(buttonX, buttonY, buttonWidth, buttonHeight);
            // 设置默认选中的按钮
            if (i == self.selectedIndex) {
                button.isSelected = YES;
            }
            // 设置tag值
            button.tag = ButtonTag + i;
            ++i;
        }
    }
}

下面给centerView进行赋值

#pragma mark - centerView赋值
-(void)setCenterView:(UIView *)centerView {
    
    _centerView = centerView;
   
    UIView *view = (UIView *)[self viewWithTag:CenterTag];
    if (view) {
        // 移除之前的tag
        [view removeFromSuperview];
    }
    else {
        _centerView.tag = CenterTag;
        // 显示在界面上
        [self addSubview:_centerView];
    }
}

但在外边调用的时候需要传一个中间按钮的方法,把中间按钮的一些属性传给上面的代码即:

#pragma mark - 外部给centerView赋值
-(void)setCenterView:(UIView *)centerView {
    
    _centerView = centerView;
    // 在tabBar上显示中间的视图
    self.wj_tabBar.centerView = centerView;
}

最后在layoutSubView中实现以上的方法。

2.2.3创建按钮上的文字和图片

// 其实就是一个模型
@property (nonatomic, strong) UITabBarItem *item;

// ==================属性===============

// 按钮是否选中
@property (nonatomic, assign) BOOL isSelected;

// 按钮选中颜色
@property (nonatomic, strong) UIColor *selectedColor;

// 按钮普通状态颜色
@property (nonatomic, strong) UIColor *normalColor;

// 添加事件
- (void)addTarget:(id)target action:(SEL)action;

在.m文件中实现相关方法;

首先对一些属性设置默认值,这里可以提供两个初始化的方法,以便以后可以用frame的方法进行初始化和直接init方法进行初始化。

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        
        // 设置属性默认值
        // 选中状态的颜色
        self.selectedColor = [UIColor redColor];
        // 普通状态的颜色 
       self.normalColor = [UIColor lightGrayColor];
    }
    return self;
}
- (instancetype)init {
    if (self = [super init]) {
        // 设置属性默认值
        self.selectedColor = [UIColor redColor];
        self.normalColor = [UIColor lightGrayColor];
    }
    return self;
}

然后在按钮上去创建界面,
去创建子视图,这里只是单纯的创建子视图,而不去创建子视图的frame属性。这里去重写setter方法;

- (void)setItem:(UITabBarItem *)item {
    
    _item = item;
    
    // 移除按钮上原来的子视图
    for (UIView *view in self.subviews) {
        [view removeFromSuperview];
    }

    // 创建对应的子视图
    // 1.图片
    if (item.image || item.selectedImage) {
        _imageView = [[UIImageView alloc]init];
        [self addSubview:_imageView];
        _imageView.image = item.image;
        // 不缩放
        [_imageView setContentMode:UIViewContentModeCenter];
    }
    
    // 2.文字
    if (item.title) {
        _textLabel = [[UILabel alloc] init];
        [self addSubview:_textLabel];
        _textLabel.text = item.title;
        _textLabel.textAlignment = NSTextAlignmentCenter;
        _textLabel.font = [UIFont systemFontOfSize:12.0];
    }
}

下一步就去设置子视图的frame属性,让其在视图即将创建的时候去设置frame属性,因为在这个时候,tabBar上的按钮的个数已经全部创建完成,所以使用layoutSubviews方法设置frame属性,这里我们先封装一个方法去设置子视图的frame。在计算视图的frame的时候最好用相对位置,最好不要把位置写死了,万一以后要调整的话,一个参数更改,其他的参数位置也会跟着变化。

// 计算frame
- (void)setSubViewFrame {
    
    // 通用
    CGFloat buttonW = self.frame.size.width;
    CGFloat buttonH = self.frame.size.height;
    
    // 1.计算图片的frame
    // 有图片
    if (_imageView) {
        CGFloat imageX = 0;
        CGFloat imageY = 0;
        CGFloat imageW = buttonW;
        CGFloat imageH;
        if (_textLabel) {
            // 有文字
            imageH = buttonH * 4 / 5.0f;
        }
        else {
            // 没有文字
            imageH = buttonH;
        }
        _imageView.frame = CGRectMake(imageX, imageY, imageW, imageH);
    }
    
    // 2.计算文字的frame
    if (_textLabel) {
        CGFloat textX = 0;
        CGFloat textW = buttonW;
        CGFloat textY;
        CGFloat textH;
        if (_imageView) {
            // 有图片
            textY = buttonH * 4 / 5.0f;
            textH = buttonH / 5.0f;
        }
        else {
            // 没有图片
            textY = 0;
            textH = buttonH;
        }
        _textLabel.frame = CGRectMake(textX, textY, textW, textH);
    }
}

然后把以上的方法在layoutSubviews中去实现下就可以了。
这里可以设置最终文字所要展示的文字颜色:

// 设置最终所需属性
    _textLabel.textColor = self.normalColor;

2.3添加按钮的点击事件:

#pragma mark - 按钮的点击事件
- (void)addTarget:(id)target action:(SEL)action {
    _target = target;
    _action = action;
}
#pragma mark - 添加事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   /*
   // 在这里可以先测试下按钮的点击效果
    if (self.isSelected) {
        
        self.isSelected = NO;
    }
    else {
        
        self.isSelected = YES;
    }
    */
    
    // 响应点击事件
    if ([_target respondsToSelector:_action]) {
        [_target performSelector:_action withObject:self];
    }
}

按钮被点击后有选中的状态,之前的按钮就会失去被选中的状态,所以下面就去设置按钮的状态。
2.3.1按钮点击后改变选中状态

#pragma mark - 按钮点击

- (void)buttonOnClick:(WJTabBarButton *)button {
   
    // 把其他的按钮变成非选中状态
    // 获取到原来的按钮
    WJTabBarButton *wjButton = (WJTabBarButton *)[self viewWithTag:self.selectedIndex + ButtonTag];
    wjButton.isSelected = NO;
    wjButton.userInteractionEnabled = YES;
    
    // 把当前点击的按钮变成选中状态
    button.isSelected = YES;
    self.selectedIndex = button.tag - ButtonTag;
    button.userInteractionEnabled = NO;
}

2.3.2按钮改变后更改颜色状态和文字

#pragma mark - 改变状态
- (void)setIsSelected:(BOOL)isSelected {
    
    _isSelected = isSelected;
    
    if (isSelected) {
        // 被选中的时候,更改选中按钮的图片和选中时候颜色
        self.imageView.image = self.item.selectedImage;
        self.textLabel.textColor = self.selectedColor;
    }
    else {
         // 失去选中的时候,按钮变成普通状态,颜色变成普通状态的颜色
        self.imageView.image = self.item.image;
        self.textLabel.textColor = self.normalColor;
    }
}

2.4按钮点击后的视图控制器需要切换
分析:点击按钮视图切换,实质上是WJTabBar想要去切换视图控制器,但是他自己做不到,需要他人去帮他去做,这就要WJTabBarController去帮他实现视图的切换。我们可以用代理去实现点击按钮实现视图的切换。
那要实现代理,实在什么地方呢?答案是在选中的下标的set方法去实现,所以我们这里需要对选中下标的setter方法进行重写。

2.4.1对代理进行简单的分析:
代理的三要素:协议、代理、委托。这里的协议就是要切换到指定的视图控制器;代理就是WJTabBarController;委托就是WJTbaBar。
(1)代理方
首先在代理的.h文件去声明指定的协议

// 指定协议
@protocol WJTabBarDelegate <NSObject>

然后需要一个代理,这相当于是对代理名的重写

// 需要一个代理
@property (nonatomic, weak) id<WJTabBarDelegate> delegate;

最后在.m文件调用代理的方法

#pragma mark - 重写set方法,改变选中下标,调用代理方法
- (void)setSelectedIndex:(NSInteger)selectedIndex {
    _selectedIndex = selectedIndex;
    
    // 调用代理的方法
    [self.delegate changeControllerWithIndex:self.selectedIndex];
}

(2)委托方
首先要准守协议方法

// 遵守协议方法
@interface WJTabBarController ()<WJTabBarDelegate>

然后去设置代理人,这个代理人应该是在tabBar被加载的时候就应该设置的,所以应该在wj_tabBar的懒加载中加上代理人的设置

#pragma mark - 懒加载
- (WJTabBar *)wj_tabBar {
    if (!_wj_tabBar) {
        _wj_tabBar = [[WJTabBar alloc]init];
        _wj_tabBar.frame = self.tabBar.bounds;
        _wj_tabBar.backgroundColor = [UIColor cyanColor];
      
        // 设置代理人
        _wj_tabBar.delegate = self;
        
    }
    return _wj_tabBar;
}

最后要实现代理方法,(这是委托人需要执行的)

#pragma mark - 实现协议方法(切换视图控制器)
- (void)changeControllerWithIndex:(NSInteger)index {
    // 切换视图控制器
    self.selectedIndex = index;
}

以上就是自定义按钮的大概实现步骤。

三:总结
要实现自定义的按钮,就是需要覆盖系统原生的按钮,然后移除tabBar上的所有子控件,然后在tabBar上去创建一个自定义的tabBar,然后在tabBar上创建按钮,在按钮上去创建图片和文字;如果要添加中间的那种特殊定制的按钮,只需要把frame值设置好就应该可以了。实现页面的切换的话,就需要用到代理,让tabBarController去实现tabBar想实现的功能就可以了。


后面还有一篇较为简单的方法定义tabBar的:
http://www.jianshu.com/p/a3002314db32

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容