自定义segment

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

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 <ZYGSegmentControlDelegate> 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<items.count; i++) {
        UIButton* button=[[UIButton alloc]initWithFrame:CGRectMake(i*titleWidth, 0, titleWidth, self.bounds.size.height-2)];
        [button setTitle:items[i] forState:UIControlStateNormal];
        [button.titleLabel setFont:self.titleFont];
        [button setTitleColor:self.titleColor forState:UIControlStateNormal];
        [button setTitleColor:self.selectColor forState:UIControlStateSelected];
        [button setTag:i];
        [button addTarget:self action:@selector(changeTheSegment:) forControlEvents:UIControlEventTouchUpInside];
        if (!lineView) {
           lineView=[[UIView alloc]initWithFrame:CGRectMake((kDefaultIndex < itemCount ? kDefaultIndex: 0) * titleWidth, self.bounds.size.height-2, titleWidth, 2)];
            [lineView setBackgroundColor:kDefaultLineColor];
            [self addSubview:lineView];
        }
        [self addSubview:button];
        [self.itemArray addObject:button];
    }
    if (kDefaultIndex < itemCount) {
        [self.itemArray[kDefaultIndex] setSelected:YES];
    }else{
        [[self.itemArray firstObject] setSelected:YES];
    }
}

(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<self.segSubControllers.count; i++) {
                UIViewController *vController = self.segSubControllers[i];
                if (i != index) {
                    [vController.view removeFromSuperview];
                }else{
                    vController.view.frame = CGRectMake(tempFrame.origin.x, tempFrame.origin.y + tempFrame.size.height, tempFrame.size.width, backView.frame.size.height);
                    UIViewController *addedController = [self getController];
                    //这里需要注意一点,当添加一个controller的view到当前controller的时候不能只用[v0.view addSubview: v1.view],这样写后期会出现各种奇妙的问题,这一点感谢猫神和KevinLee的文章,应该把子controller添加到当前的controller中,这样才不会出问题
                    //关键的两句
                    [addedController addChildViewController:vController];
                    [vController didMoveToParentViewController:addedController];
                    [backView addSubview:vController.view];
                    
                }
            }
        }
        [self.delegate didSelectSegmentAtIndex:selectedIndex];
    }else{
        kDLOG(@"代理未实现方法");
    }
}

(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"
    遵守协议 :<ZYGSegmentControlDelegate> ,如果设置了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的话会出现下图的效果:
Simulator Screen Shot 2016年3月31日 上午11.40.39.png

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

Simulator Screen Shot 2016年3月31日 上午11.58.15.png

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

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

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

Simulator Screen Shot 2016年4月1日 上午9.56.47.png

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

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

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

推荐阅读更多精彩内容