自定义segment

如果你还在为系统的UISegmentedControl蹩脚的功能而耿耿于怀,如果你还在仅仅为了制作一个简单的标签而大动干戈,如果你还在因为UISegmentedControl连最基本的手势都木得支持,那么本文或许能帮到你!得嘞,先看效果,最好下载下来运行一下:

自定义segment_第1张图片
Segment.gif
  • 特点:
    1、支持自定义标题
    2、支持自定义标题颜色(选中和非选中)
    3、支持自定义下划线的颜色
    4、支持手势,实现左右滑动屏幕切换页面
    5、使用kvo监测属性变化
    6、支持每个item对应的view,只需要将自定义的view添加到segSubviews即可
    7、支持添加childViewController,以便于复杂逻辑的处理
    8、使用方便,对于定制要求不高的用户,实现一句代码搞定
    demo地址:ZYGSegment Demo下载

1、.h文件

  • 协议
/**
 *  定义协议,用以在点击item时在界面做出相应的处理
 */
@protocol ZYGSegmentControlDelegate< NSObject>
/**
 *  创建segment的类需要实现的协议方法,可选
 *
 *  @param selection 选中的下标
 */
@optional
-(void)didSelectSegmentAtIndex:(NSInteger)selectedIndex;
@end
  • 属性
/**
 *  当前类的代理对象,把要实现选择segment的类的对象设置成代理
 */
@property (nonatomic, weak) id  delegate;
/**
 *  标题  ,目前仅支持字符串,暂时未考虑图片
 */
@property (nonatomic, strong) NSMutableArray *itemArray;
/**
 *  一一对应于点击每个item时显示的view
 */
@property (nonatomic, strong) NSMutableArray *segSubviews;
/**
 *  子控制器  segSubviews与subControllers只能设置一个,或者均不设置
 */
@property (nonatomic, strong) NSMutableArray *segSubControllers;
/**
 *  segment的背景色,不设置的话会采用默认值
 */
@property (strong, nonatomic) UIColor *segmentBackgroundColor;
/**
 *  segment的字体颜色,不设置的话采用默认值
 */
@property (strong, nonatomic) UIColor *titleColor;
/**
 *  segment选中时的字体颜色,不设置的话采用默认值
 */
@property (strong, nonatomic) UIColor *selectColor;
/**
 *  segment标题的字体,不设置的采用默认值
 */
@property (strong, nonatomic) UIFont  *titleFont;
/**
 *  segment的下划线的颜色,不设置的采用默认的红色
 */
@property (strong, nonatomic) UIColor *lineColor;
/**
 *  segment下划线动画的时间,不设置的话默认0.5s
 */
@property (assign, nonatomic) CGFloat duration;
  • 调用方法
/**
 *  创建segment,每次均会返回一个新的ZYGSegment对象,以便于实现segment的嵌套使用
 *
 *  @return ZYGSegment对象
 */
+(instancetype )initSegment;
/**
 *  要添加的items
 *  用上一个方法创建的对象调用这个方法即可
 *  @param items 标题数组
 *  @param frame segment的frame
 *  @param view  segment要添加的view
 */
-(void)addItems:(NSArray *)items frame:(CGRect )frame inView:(UIView *)view;

2、.m文件

(1) 为item定义默认值,可以直接在这修改,如果整个项目中使用到的segment效果都是一样的(颜色、字体大小),那么在这修改以后就达到了一劳永逸的效果,创建segment的时候就不用再去逐个修改属性。

/******************************************
 *                                        *
 *        以下为默认值,可以直接在此修改       *
 *                                        *
 ******************************************
 */
//默认值:item背景色
#define kSegmentBackgroundColor    [UIColor colorWithRed:253.0f/255 green:239.0f/255 blue:230.0f/255 alpha:1.0f]
//默认值:未选中时字体的颜色
#define  kTitleColor      [UIColor colorWithRed:77.0/255 green:77.0/255 blue:77.0/255 alpha:1.0f]
//默认值:选中时字体的颜色
#define  kSelectedColor   [UIColor colorWithRed:33.0/255 green:97.0/255 blue:31.0/255 alpha:1.0f]
//默认值:字体的大小
#define kTitleFont        [UIFont fontWithName:@".Helvetica Neue Interface" size:14.0f]
//默认值:下划线颜色
#define kDefaultLineColor   [UIColor redColor]
//默认值:初始选中的item下标
#define kDefaultIndex       0   
//默认值:下划线动画的时间
#define kDefaultDuration    0.5

(2)定义变量

{
    UIView *backView;//segment添加到的view
    CGFloat titleWidth;//item宽度
    UIView* lineView;//下划线
    NSInteger selectedIndex;//选中item的下标
    int itemCount;//item的数量
    int buttonTag;//选中item的标签值
    CGRect tempFrame;//保存segment的frame
}

(3)创建segment对象

+(instancetype )initSegment{
    segment = [[self alloc] init];
    return segment;
}

(4)重写初始化方法,利用kvo监测属性值变化

-(instancetype )init{
    if (self = [super init]) {
        //额外的操作 ....
    }
    return self;
}
-(instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.itemArray=[NSMutableArray array];
        //设置初始值(默认值),可以在上面直接修改
        selectedIndex = kDefaultIndex;
        self.titleFont = kTitleFont;
        self.segmentBackgroundColor = kSegmentBackgroundColor;
        self.titleColor = kTitleColor;
        self.selectColor = kSelectedColor;
        [self setBackgroundColor:self.segmentBackgroundColor];
        //使用kvo监测属性值变化
        [self addObserver:self forKeyPath:@"segmentBackgroundColor" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"backgroundColor"];
        [self addObserver:self forKeyPath:@"titleColor" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"titleColor"];
        [self addObserver:self forKeyPath:@"selectColor" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"selectColor"];
        [self addObserver:self forKeyPath:@"titleFont" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"titleFont"];
        [self addObserver:self forKeyPath:@"lineColor" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"lineColor"];
        [self addObserver:self forKeyPath:@"segSubviews" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"viewsArr"];
        [self addObserver:self forKeyPath:@"segSubControllers" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"subControllers"];
    }
    return self;
}

(5)初始化item,外部调用

-(void)addItems:(NSArray *)items frame:(CGRect)frame inView:(UIView *)view{
    backView = view;//segment添加到的view
    tempFrame = frame;//保存segment的frame
    segment.frame = frame;
    [segment addItems:items];
    [view addSubview:segment];
    [self addSwipGestureIn:view];//添加手势,实现左右滑动切换页面
}

添加item的真实面目,是的,全是button:

-(void)addItems:(NSArray *)items{
    itemCount = (int) items.count;
    titleWidth=(self.bounds.size.width)/itemCount;
    for (int i=0; i

(6)选中item的处理

-(void)changeTheSegment:(UIButton*)button
{
    [self selectIndex:button.tag];
    buttonTag = (int)button.tag;
}
-(void)selectIndex:(NSInteger)index{
    if (selectedIndex!=index) {
        [self handleSelectItemEventWith:index];
    }
}

参考:其中添加controller时有点误解,感谢KevinLee,猫神的文章

-(void)handleSelectItemEventWith:(NSInteger )index{
    if (index > itemCount) {
        return;
    }
    [self.itemArray[selectedIndex] setSelected:NO];
    [self.itemArray[index] setSelected:YES];
    [UIView animateWithDuration:self.duration ? self.duration: kDefaultDuration animations:^{
        [lineView setFrame:CGRectMake(index*titleWidth,self.bounds.size.height-2, titleWidth, 2)];
    }];
    selectedIndex=index;
    //下面这一坨本应该单独拉出来再封装一些的,由于时间关系,暂且先放这吧
    if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectSegmentAtIndex:)]) {
        if (self.segSubviews && self.segSubviews.count) {
            for (int i=0; i<=index; i++) {
                UIView *view = self.segSubviews[i];
                if (i != index) {
                    [view removeFromSuperview];
                }else{
                    if (!view.frame.size.height){
                        view.frame = CGRectMake(tempFrame.origin.x, tempFrame.origin.y + tempFrame.size.height, tempFrame.size.width, backView.frame.size.height);
                    }
                    
                    [backView addSubview:view];
                }
            }
        }else if (self.segSubControllers && self.segSubControllers.count){
            for (int i=0; i

(7)获得segment所在界面的controller

    //这里用到了响应者链
-(UIViewController *)getController{
    UIResponder *responder = backView.nextResponder;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = responder.nextResponder;
    }
    return (UIViewController *)responder;
}

(8)利用kvo监测属性值的变化,做出相应的处理

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{NSString *cate = (__bridge NSString *)context;
    //改变背景色
    if ([cate isEqualToString:@"backgroundColor"]) {
        [self setBackgroundColor:self.segmentBackgroundColor];
    }
    //改变下划线颜色
    if ([cate isEqualToString:@"lineColor"]) {
        [lineView setBackgroundColor:self.lineColor];
    }
    //添加views
    if ([cate isEqualToString:@"viewsArr"]) {
        UIView *selectedView = self.segSubviews[0];
        [backView addSubview:selectedView];
        if (!selectedView.frame.size.height) {
            //因为view是从segment的item下面开始的,所以大小不再等于整个屏幕,在这需要修改一下
            selectedView.frame = CGRectMake(tempFrame.origin.x, tempFrame.origin.y + tempFrame.size.height, tempFrame.size.width, backView.frame.size.height);
        }
        //修改默认选中的item
        [self handleSelectItemEventWith:kDefaultIndex];
    }
    //添加controllers
    if ([cate isEqualToString:@"subControllers"]) {
        UIViewController *vController = self.segSubControllers[0];
        UIView *selectedView = vController.view;
        selectedView.frame = CGRectMake(tempFrame.origin.x, tempFrame.origin.y + tempFrame.size.height, tempFrame.size.width, backView.frame.size.height);
        [backView addSubview:selectedView];
        [self handleSelectItemEventWith:kDefaultIndex];
    }
    //改变字体颜色和大小
    for (UIButton *button in self.subviews) {
        if ([button isKindOfClass:[UIButton class]]) {
            if ([cate isEqualToString:@"titleColor"]){
                [button setTitleColor:self.titleColor forState:UIControlStateNormal];
            }else if ([cate isEqualToString:@"selectColor"]){
                [button setTitleColor:self.selectColor forState:UIControlStateSelected];
            }else if ([cate isEqualToString:@"titleFont"]){
                [button.titleLabel setFont:self.titleFont];
            }
        }
}
}

(9)移除观察者

-(void)dealloc{
    [self removeObserver:self forKeyPath:@"segmentBackgroundColor" context:@"segmentBackgroundColor"];
    [self removeObserver:self forKeyPath:@"titleColor" context:@"titleColor"];
    [self removeObserver:self forKeyPath:@"selectColor" context:@"selectColor"];
    [self removeObserver:self forKeyPath:@"titleFont" context:@"titleFont"];
    [self removeObserver:self forKeyPath:@"viewsArr" context:@"viewsArr"];
    [self removeObserver:self forKeyPath:@"subControllers" context:@"subControllers"];
}

(10)手势处理

-(void)addSwipGestureIn:(UIView *)view{
    //创建左滑手势
    UISwipeGestureRecognizer *leftSwip = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipTheScreen:)];
    leftSwip.direction = UISwipeGestureRecognizerDirectionLeft;
    [view addGestureRecognizer:leftSwip];
    
    //创建右滑手势
    UISwipeGestureRecognizer *rightSwip = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipTheScreen:)];
    rightSwip.direction = UISwipeGestureRecognizerDirectionRight;
    [view addGestureRecognizer:rightSwip];
}
    //滑动屏幕的处理
-(void)swipTheScreen:(UISwipeGestureRecognizer *)swip{
    if (swip.direction & UISwipeGestureRecognizerDirectionRight){
        if (buttonTag > 0) {
            buttonTag --;
            [self selectIndex:buttonTag];
        }
    }else if (swip.direction & UISwipeGestureRecognizerDirectionLeft){
        if (buttonTag < itemCount-1) {
            buttonTag ++;
            [self selectIndex:buttonTag];
        }
    }
}

3、使用

  • 导入头文件: import "ZYGSegment.h"
    遵守协议 : ,如果设置了seg.segSubviews或者seg.segSubControllers,可以不用遵守协议,也不用写协议方法,只需要创建好相应的view和contorller即可,下面介绍三种使用方法,任意一种都是可以的,视需求而定:
    (1)最简单的使用(一句代码):
[[ZYGSegment initSegment] addItems:@[@"标题0",@"标题1",@"标题2"] frame:CGRectMake(0, 264, self.view.frame.size.width, 34) inView:self.view];

这样使用看起来确实简单明了,貌似我少了个东西,是的,delegate!不设置delegate怎么拿到选择item的事件呢?看来我当时确实漏掉了一个,如果在这个方法中增加一个delegate真的一句代码就可以搞定了,所以还是老老实实多写一句吧,感兴趣的小盆友可以在这个方法中增加一个delegate参数,我就不往里面加了,那么现在只能这样使用了:

    ZYGSegment *seg = [ZYGSegment initSegment];
    seg.delegate = self;
    [seg addItems:@[@"标题0",@"标题1",@"标题2"] frame:CGRectMake(0, 64, self.view.frame.size.width, 34) inView:self.view];

然后是协议方法:

-(void)didSelectSegmentAtIndex:(NSInteger)selectedIndex{
    kDLOG(@"选中:%ld",selectedIndex);
}

这样就建立起来我们的segment了,当然这样建立起来的segment是比较简单的,如果想深一层的定制可以对segment属性自定义,不设置的话采用默认属性,如果默认的属性已经满足了需求,则不需要再设置,也可以在ZYGSegment.m里面对其默认属性统一修改:

    //对segment属性自定义,可选,不设置的话采用默认属性
    seg.segmentBackgroundColor = [UIColor whiteColor];
    seg.titleColor = [UIColor blueColor];
    seg.selectColor = [UIColor redColor];
    seg.titleFont = [UIFont fontWithName:@".Helvetica Neue Interface" size:14.0f];
    seg.lineColor = [UIColor blackColor];
    seg.duration = 0.3;

(2)自定义view
在上面的基础之上可以设置每个item对应的view,这个时候可以不用遵从协议,当然也就不用实现协议的方法,如果你有特殊的需求,仍然可以遵从协议,实现协议的方法,在里面做相应的处理:

    viewArr = [[NSMutableArray alloc] init];
    for (int i=0; i<3; i++) {
        //将view或者imageView按次序添加进viewArr中,就得到了item相对应的view,更进一步地,我们可以对各个view进行定制,这样你点击不同的item就得到相应的view,这个时候如果没有什么特别的需求,协议方法是不用再去实现的,具体效果见demo
        UIView *view = [[UIView alloc] init];
        view.backgroundColor=[UIColor colorWithRed:NUM green:NUM blue:NUM alpha:1.0f];
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake( 30, 40, 150, 20)];
        label.text = [NSString stringWithFormat:@"这是第%d个view",i];
        [view addSubview:label];
        [viewArr addObject:view];
    }
    seg.segSubviews = viewArr;

(3)带controller的segment
如果你的界面逻辑相当复杂,在同一个界面处理的话会造成很大的麻烦,这个时候大家当然是想着能把处理的逻辑分开来,所以我加入了childViewController,将处理逻辑分散到各个子controller中:

//设置各个item对应的controller
    ViewController0 *v0 = [[ViewController0 alloc] init];
    ViewController1 *v1 = [[ViewController1 alloc] init];
    ViewController2 *v2 = [[ViewController2 alloc] init];
    //设置将viewcontroller添加到的controller,这样做的原因我在下面会详细说
    v0.upController = self;
    v1.upController = self;
    v2.upController = self;
    //为controller添加导航(如果需要的话,若不设置在子controller调用push的方法是不起作用的,建议加上,即便暂时用不到,更改需求时就方便了,当然用present就另说了)
    UINavigationController *nav0 = [[UINavigationController alloc] initWithRootViewController:v0];
    UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:v1];
    UINavigationController *nav2 = [[UINavigationController alloc] initWithRootViewController:v2];
//    controllerArr = [NSMutableArray arrayWithArray:@[v0,v1,v2]];
    controllerArr = [NSMutableArray arrayWithArray:@[nav0,nav1,nav2]];
    seg.segSubControllers = controllerArr;
  • issure
    也可以称之为一个小bug,就是在子controller里面采用push的话会出现下图的效果:
自定义segment_第2张图片
Simulator Screen Shot 2016年3月31日 上午11.40.39.png

所以在代码中将segment所在界面的controller给传到了childController 中,在childController中需要push的时候用segment坐在界面的controller的navigationController去push,这样做的效果就是正常的了:

自定义segment_第3张图片
Simulator Screen Shot 2016年3月31日 上午11.58.15.png

我在代码中是这样调用的:

//其中upController就是segment所在界面的controller,也就是上面的传值:v0.upController = self;
[self.upController.navigationController pushViewController:push animated:YES];

当然不这样做也行,就是采用present的方式去跳转,这样就不用去传值,得到的效果也是正常的:

自定义segment_第4张图片
Simulator Screen Shot 2016年4月1日 上午9.56.47.png

具体的实现和效果大家可以 下载demo看一下,关于这一点我查了很多资料,可能是我搜索的方式不对,一直没找到好的解决方法,如果谁有更好的解决办法,还请烦劳告知,不胜感激!

附:
下载链接:ZYGSegment :https://github.com/hungryBoy/segmentDemo

  • End