如果你还在为系统的UISegmentedControl蹩脚的功能而耿耿于怀,如果你还在仅仅为了制作一个简单的标签而大动干戈,如果你还在因为UISegmentedControl连最基本的手势都木得支持,那么本文或许能帮到你!得嘞,先看效果,最好下载下来运行一下:
-
特点:
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的话会出现下图的效果:
所以在代码中将segment所在界面的controller给传到了childController 中,在childController中需要push的时候用segment坐在界面的controller的navigationController去push,这样做的效果就是正常的了:
我在代码中是这样调用的:
//其中upController就是segment所在界面的controller,也就是上面的传值:v0.upController = self;
[self.upController.navigationController pushViewController:push animated:YES];
当然不这样做也行,就是采用present的方式去跳转,这样就不用去传值,得到的效果也是正常的:
具体的实现和效果大家可以下载demo看一下,关于这一点我查了很多资料,可能是我搜索的方式不对,一直没找到好的解决方法,如果谁有更好的解决办法,还请烦劳告知,不胜感激!
附:
下载链接:ZYGSegment :https://github.com/hungryBoy/segmentDemo
- End