构建自定义TabBarController

<small>
构建自定义TabBarController需要满足以下几个基本步骤:

  1. 移除本身自带的tabBar,选用自定义tabBar
  2. tabBar上的button也需要自定义
  3. 设定协议实现点击tabBar上的button实现控制器的跳转

能够满足以上的功能,我们得TabBarController就可以开始干活了。

满足之后,扩展的功能就因项目而定了。

第一步:构建三个类

  1. XXTabBarController
  2. XXTabBar
  3. XXTabBarButton
    名字就自己爱好取了。

GWTabBarButton

我们先构建最简单的按钮,只需要设置它的标题,默认状态图标,选中状态图标,默认前景色,选中状态前景色就可以了。

import <UIKit/UIKit.h>

@interface GWTabBarButton : UIButton

/**
 根据UITabBarItem属性进行初始化
 @param tabBarItem tabBarItem对象
 @return GWTabBarButton对象
 */
- (instancetype)initWithTabBarItem:(UITabBarItem *)tabBarItem;
@end
#import "GWTabBarButton.h"

@implementation GWTabBarButton

#pragma mark 初始化按钮
- (instancetype)initWithTabBarItem:(UITabBarItem *)tabBarItem{
    if(self = [super init])
    {
        [self setTitle:tabBarItem.title forState:UIControlStateNormal];
        [self setImage:tabBarItem.image forState:UIControlStateNormal];
        [self setImage:tabBarItem.selectedImage forState:UIControlStateSelected];
        [self setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
        [self setTitleColor:[UIColor blueColor] forState:UIControlStateSelected];
    }
    return self;
}

@end

UITabBarItem 并不是一个控件,因为它是继承于UIBarItem,而UIBarItem继承于NSObject。 它更像是个临时储存站,通过它我们将对应的数据储存在它的对象中,最后在需要的地方提取出来赋值给相应的控件。因为它刚好拥有title,image,selectedImage三个属性,其实也是为tabBar而生的一种Model,所以利用它我们能够很好的传递数据。

GWTabBar

自定义的tabBar我们选用UIView,而自定义tabBar作为一个View,可塑性更强,可以利用drawRect: 方法构建出不规则tabBar:


不规则tabBar.png

上面的其实就是在中间画了个圆.
下面的这个其实就是在中间加了个按钮


不规则tabBar.png

如上所示,程序只有想不到,没有你办不到。
而我们今天只是做一个简单的自定义tabBar:
#import <UIKit/UIKit.h>
#import "GWTabBarButton.h"
@class GWTabBar;
@protocol GWTabBarDelegate<NSObject>
/**
 实现切换页面

 @param tabBar tabBar对象
 @param oldIndex 曾经选中的按钮
 @param newIndex 当前选中的按钮
 */
- (void)tabBar:(GWTabBar *)tabBar didSelectItemFormIndex:(NSInteger)oldIndex toIndex:(NSInteger)newIndex;
@end

@interface GWTabBar : UIView
//代理
@property (nonatomic,readwrite,weak)id<GWTabBarDelegate> delegate;
//设定当前选中的按钮
@property (nonatomic,readwrite,unsafe_unretained)GWTabBarButton   *selectButton;
/**
 添加一个按钮

 @param tabBarButton 按钮
 */
- (void)addTabBarItem:(UITabBarItem *)tabBarButton;

@end

要想实现点击tabBar上的按钮,然后去切换页面的功能,首先得利用代理去完成。因为我们将TabBarController上的tabBar给移除了,所以这方面功能只能我们自己实现了。而这个方法的原理也比较简单:
我们为每个按钮绑定tag,通过不同的tag来识别我们是否点击了不同的按钮。当我们点击不同的按钮的时候,TabBarController也需要同时去设置它的selectedIndex属性来实现页面的切换。

除了设置按钮点击事件之外,我们还需要向外部抛出一个方法(addTabBarItem:),来实现添加按钮的功能。有了按钮之后,我们再对按钮进行排列和绑定tag。

#import "GWTabBar.h"
@interface GWTabBar()

@end

@implementation GWTabBar

- (instancetype)initWithFrame:(CGRect)frame{
    if(self = [super initWithFrame:frame])
    {
        [self setBackgroundColor:[UIColor whiteColor]];
        [self setAlpha:0.7];
    }
    return  self;
}

#pragma mark 初始化tabBar
- (void)addTabBarItem:(UITabBarItem *)tabBarItem{
    
    GWTabBarButton *tabBarButton = [[GWTabBarButton alloc]initWithTabBarItem:tabBarItem];
    
    [tabBarButton addTarget:self
                     action:@selector(tabBarButtonAction:)
           forControlEvents:UIControlEventTouchUpInside];

    [self addSubview:tabBarButton];
}


#pragma mark 跳转页面设置
- (void)tabBarButtonAction:(GWTabBarButton *) tabBarButton{
    if([self.delegate respondsToSelector:@selector(tabBar:didSelectItemFormIndex:toIndex:)]){
        [self.delegate tabBar:self didSelectItemFormIndex:self.selectButton.tag toIndex:tabBarButton.tag];
    }
    //重新设置按钮的选中状态
    self.selectButton.selected = NO;
    //由于点击该方法后,表明已经切换了按钮对象,所以之前的按钮对象需要放弃,重新指定按钮对象
    self.selectButton = tabBarButton;
    self.selectButton.selected = YES;
}

#pragma mark 设置按钮位置和绑定tag
- (void)layoutSubviews{
    [super layoutSubviews];
    
    for (NSInteger index = 0;index<self.subviews.count;index++){
        //从之前添加的子视图列表中获取按钮对象
        GWTabBarButton *tabBtn = self.subviews[index];
        //设定按钮布局,实现平均排列
        tabBtn.frame = CGRectMake(index*self.frame.size.width/self.subviews.count, 2, self.frame.size.width/self.subviews.count, tabBarHeight);
        tabBtn.titleLabel.font = [UIFont systemFontOfSize:GWTabBarFontSize];
        
        //设定图标和标题偏移,实现图上字下的布局方式
        CGSize btnImageBounds = tabBtn.imageView.bounds.size;
        CGSize btnTitleBounds = tabBtn.titleLabel.bounds.size;
        tabBtn.titleEdgeInsets = UIEdgeInsetsMake(0.0, -btnImageBounds.width/2, - btnImageBounds.height, btnImageBounds.width/2);
        tabBtn.imageEdgeInsets = UIEdgeInsetsMake(-btnTitleBounds.height-5,btnTitleBounds.width/2, 0.0, -btnTitleBounds.width/2);

        //绑定tag,便于tabBar代理方法的实现
        tabBtn.tag = index;
    }
}
@end
其中的layoutSubviews是对子视图重新布局时使用的,本身在父类是没有什么作用的,需要在子类中重写功能.而且layoutSubviews方法调用先于drawRect。

而layoutSubviews只有在以下情况下会被调用:
1. init初始化不会触发layoutSubviews,但是是用initWithFrame 进行初始化时,当rect的值不为CGRectZero时,也会触发
2. addSubview会触发layoutSubviews
3. 设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
4. 滚动一个UIScrollView会触发layoutSubviews
5. 旋转Screen会触发父UIView上的layoutSubviews事件
6.  改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件

GWTabBarController

tabBar和tabBarButton都做完了之后,我们只需要将东西放在tabBarController上面就可以了。

#import <UIKit/UIKit.h>
#import "GWTabBar.h"
@interface GWTabBarController : UITabBarController<GWTabBarDelegate>

@end
#import "GWTabBarController.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "ThirdViewController.h"
#import "FourthViewController.h"

@interface GWTabBarController ()
@property (nonatomic,readwrite,strong)GWTabBar  *gwTabBar;
@end

@implementation GWTabBarController

#pragma mark 移除自带的tabBar
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    
    for(UIView *child in self.tabBar.subviews){
        if([child isKindOfClass:[UIControl class]]){
            [child removeFromSuperview];
        }
    }

}


- (void)viewDidLoad {
    [super viewDidLoad];
    [self initlizeTabBar];
    [self initializeAllControllers];
    //设定默认启动第一页
    self.gwTabBar.selectButton = self.gwTabBar.subviews[0];
    self.gwTabBar.selectButton.selected = YES;
}

#pragma mark 初始化自定义tabBar
- (void)initlizeTabBar{
    
    //移除自带的tabBar,在之前先获取它的布局
    CGRect rect = self.tabBar.frame;
    //这里我只是感觉默认的高度有点高,微调了一下,如果不需要调整的话这里不需要重新设置
    rect.origin.y = FRAMEHEIGHT - tabBarHeight;
    rect.size.height = tabBarHeight;
    [self.tabBar removeFromSuperview];
    //创建tabBar
    self.gwTabBar = [[GWTabBar alloc] initWithFrame:rect];
    self.gwTabBar.delegate = self;
    [self.view addSubview:self.gwTabBar];
    
}

#pragma mark 实现点击切换页面
- (void)tabBar:(GWTabBar *)tabBar didSelectItemFormIndex:(NSInteger)oldIndex toIndex:(NSInteger)newIndex
{
    self.selectedIndex = newIndex;
}


#pragma mark 为TabBar初始化页面
- (void)initializeAllControllers{
    
    [self setTabBarItemWithController:[FirstViewController new] title:@"第一页" defaultImageName:@"camera_n" selectedImageName:@"camera_s"];
    
    [self setTabBarItemWithController:[SecondViewController new] title:@"第二页" defaultImageName:@"camera_n" selectedImageName:@"camera_s"];
    
    [self setTabBarItemWithController:[ThirdViewController new] title:@"第三页" defaultImageName:@"camera_n" selectedImageName:@"camera_s"];
    
    [self setTabBarItemWithController:[FourthViewController new] title:@"第四页" defaultImageName:@"camera_n" selectedImageName:@"camera_s"];
    
}


/**
 将页面添加到TabBar中

 @param controller 页面控制器
 @param title 标题
 @param defaultName 默认状态图标
 @param selectedName 选中状态图标
 */
- (void)setTabBarItemWithController:(UIViewController *)controller
                              title:(NSString *)title
                       defaultImageName:(NSString *)defaultName
                  selectedImageName:(NSString *)selectedName{
    //设置tabBarItem
    UITabBarItem *tabBarItem = [[UITabBarItem alloc]initWithTitle:title image:[UIImage imageNamed:defaultName] selectedImage:[UIImage imageNamed:selectedName]];
    controller.tabBarItem = tabBarItem;
    
    //将页面加载到TabBar中
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:controller];
    [self addChildViewController:nav];
    
    //将tabBarItem传入到TabBar中
    [self.gwTabBar addTabBarItem:tabBarItem];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

@end

最后附上Demo地址:https://github.com/yanggenwei/GWTabBarController

</small>

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

推荐阅读更多精彩内容