自定义UISegmentedControl分段控制器之ABSegmentView

https://github.com/luffySaw/ABSegmentView
如果每个产品设计都能根据苹果的UI风格来的话,那就省事很多了,但是现实是残酷的,而且如果都用一套苹果原生UI的,恩,看起来也很蛋疼。每个app打开都一个鸟UI,恩,也会视觉疲劳。因此就必须自定义各种各样的UI控件来满足产品UI需求。
今天分享一个自己产品UI需求效果的分段控制器。因为系统的UISegmentedControl局限性,很多效果没法实现,只能自定义,纵观各种app,基本上难看到原生的UISegmentedControl
先看下效果图



这个自定义SegmentView除了基本的标题选择、底部滚动条等,还支持小红点提醒。实现也很简单,当然也可以根据业务需求,自己慢慢增加功能。具体看代码:

#import <UIKit/UIKit.h>

@class ABSegmentStyle;

@interface ABSegmentStyle : NSObject

/** 滚动条的颜色 */
@property (nonatomic, strong) UIColor *bottomLineColor;
/** 滚动条的高度 默认为2 */
@property (assign, nonatomic) CGFloat bottomLineHeight;
/** 标题的字体 默认为17 */
@property (strong, nonatomic) UIFont *titleFont;
/** 标题一般状态的颜色 */
@property (strong, nonatomic) UIColor *normalTitleColor;
/** 标题选中状态的颜色 */
@property (strong, nonatomic) UIColor *selectedTitleColor;
/** 隐藏底部边框线,默认YES */
@property (assign, nonatomic) BOOL hiddenBottomBorderLine;

@end


typedef void(^TitleBtnOnClickBlock)(NSString *title, NSInteger index);

@interface ABSegmentView : UIView

-(instancetype)initWithFrame:(CGRect)frame segmentStyle:(ABSegmentStyle *)segmentStyle titles:(NSArray<NSString *> *)titles titleDidClick:(TitleBtnOnClickBlock)titleDidClick;

@property (nonatomic, assign, readonly) NSInteger selectedIndex;

/**
 设置选中的下标

 @param index 下标
 @param animated 动画
 */
- (void)setSelectedIndex:(NSInteger)index animated:(BOOL)animated;

/**
 根据外部滚动视图来调整底部横线位置

 @param scrollView 外部滚动视图
 */
- (void)adjustScollViewDidScroll:(UIScrollView *)scrollView;

/**
 显示小红点

 @param index 位置
 */
- (void)showBaggeOnItemIndex:(NSInteger)index;

/**
 隐藏小红点
 
 @param index 位置
 */
- (void)hideBaggeOnItemIndex:(NSInteger)index;

@end
#import "ABSegmentView.h"

@implementation ABSegmentStyle

- (instancetype)init
{
    if(self = [super init]) {
        self.bottomLineHeight = 3.0;
        self.bottomLineColor = [UIColor wildtoGreenNormal];
        self.titleFont = [UIFont systemFontOfSize:17.0];
        self.normalTitleColor = [UIColor titleLightGreyColor];
        self.selectedTitleColor = [UIColor wildtoGreenNormal];
        self.hiddenBottomBorderLine = YES;
    }
    return self;
}

@end


@interface ABSegmentView ()
{
    NSUInteger _currentIndex;
    NSUInteger _oldIndex;
}
@property (nonatomic, strong) ABSegmentStyle *segmentStyle;
@property (nonatomic, weak) UIView *bottomLine;
@property (nonatomic, weak) UIView *moveClearView;
@property (nonatomic, strong) NSArray *titles;
@property (nonatomic, strong) NSMutableArray *titleViews;
@property (nonatomic, strong) NSMutableArray *titleLabelRects;
@property (nonatomic, copy) TitleBtnOnClickBlock titleBtnOnClick;
@property (nonatomic, weak) UIView *viewBottomBorderLine;

@end

@implementation ABSegmentView

- (UIView *)bottomLine
{
    if (!_bottomLine) {
        UIView *lineView = [[UIView alloc] init];
        lineView.backgroundColor = self.segmentStyle.bottomLineColor;
        [self.moveClearView addSubview:lineView];
        _bottomLine = lineView;
    }
    return _bottomLine;
}

-(UIView *)moveClearView
{
    if (!_moveClearView) {
        UIView *moveClearView = [[UIView alloc] init];
        moveClearView.backgroundColor = [UIColor clearColor];
        [self addSubview:moveClearView];
        _moveClearView = moveClearView;
    }
    return _moveClearView;
}

-(NSMutableArray *)titleViews
{
    if (!_titleViews) {
        _titleViews = [NSMutableArray array];
    }
    return _titleViews;
}

-(NSMutableArray *)titleLabelRects
{
    if (!_titleLabelRects) {
        _titleLabelRects = [NSMutableArray array];
    }
    return _titleLabelRects;
}

-(instancetype)initWithFrame:(CGRect)frame segmentStyle:(ABSegmentStyle *)segmentStyle titles:(NSArray<NSString *> *)titles titleDidClick:(TitleBtnOnClickBlock)titleDidClick
{
    if (self = [super initWithFrame:frame]) {
        self.titles = titles;
        self.titleBtnOnClick = titleDidClick;
        self.segmentStyle = segmentStyle;
        _currentIndex = 0;
        _oldIndex = 0;
        
        [self setuptitleViews];
        [self setupUI];
    }
    return self;
}

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

- (void)setuptitleViews
{
    NSInteger index = 0;
    for (NSString *title in self.titles) {
        
        UIView *titleView = [[UIView alloc]initWithFrame:CGRectZero];
        titleView.tag = index;
        titleView.userInteractionEnabled = YES;
        UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(titleViewOnClick:)];
        [titleView addGestureRecognizer:tapGes];
        titleView.backgroundColor = [UIColor clearColor];
        [self addSubview:titleView];
        
        //标题
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
        label.tag = index;
        label.text = title;
        label.textColor = self.segmentStyle.normalTitleColor;
        label.font = self.segmentStyle.titleFont;
        [titleView addSubview:label];
        
        //红点提醒
        UIView *redPointView = [[UIView alloc]initWithFrame:CGRectZero];
        redPointView.backgroundColor = ColorWithHex(@"ff3b30");
        redPointView.layer.cornerRadius = 4;
        redPointView.layer.masksToBounds = YES;
        redPointView.tag = 250;
        redPointView.hidden = YES;
        [titleView addSubview:redPointView];
        
        [self.titleViews addObject:titleView];
        
        CGRect bounds = [title boundingRectWithSize:CGSizeMake(MAXFLOAT, 0.0) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.segmentStyle.titleFont} context:nil];
        [self.titleLabelRects addObject:[NSValue valueWithCGRect:bounds]];
        
        index++;
    }
}

- (void)setupUI
{
    [self setupTtitleLabelFrame];
    [self setupBottomLineFrame];
    [self setupViewBottomBorderLine];
}

- (void)setupTtitleLabelFrame
{
    CGFloat labelX = 0;
    CGFloat labelY = 0;
    CGFloat labelW = self.frame.size.width / self.titleViews.count;
    CGFloat labelH = self.frame.size.height;
    
    NSInteger index = 0;
    
    for (UIView *titleView in self.titleViews) {
        labelX = index * labelW;
        titleView.frame = CGRectMake(labelX, labelY, labelW, labelH);
        
        for (UIView *subView in titleView.subviews) {
            if ([subView isKindOfClass:[UILabel class]]) {
                UILabel *subLabel = (UILabel *)subView;
                if (subLabel.tag == titleView.tag) {
                    CGRect labBounds = [self.titleLabelRects[index] CGRectValue];
                    CGFloat subLabelW = labBounds.size.width;
                    CGFloat subLabelH = labBounds.size.height;
                    CGFloat subLabelX = (titleView.ab_width - subLabelW) / 2;
                    CGFloat subLabelY = (titleView.ab_height - subLabelH) / 2;
                    subLabel.frame = CGRectMake(subLabelX, subLabelY, subLabelW, subLabelH);
                    [self solveUIWidgetFuzzy:subLabel];
                    if (index == 0) {
                        subLabel.textColor = self.segmentStyle.selectedTitleColor;
                    }
                    
                    UIView *redPointView = [titleView viewWithTag:250];
                    if (redPointView) {
                        CGFloat redPointW = 8;
                        CGFloat redPointH = 8;
                        CGFloat redPointX = CGRectGetMaxX(subLabel.frame);
                        CGFloat redPointY = subLabel.ab_y - redPointH / 2;
                        redPointView.frame = CGRectMake(redPointX, redPointY, redPointW, redPointH);
                    }
                }
            }
            continue;
        }
        
        index++;
    }
}

- (void)solveUIWidgetFuzzy:(UIView *)view
{
    CGRect frame = view.frame;
    int x = floor(frame.origin.x);
    int y = floor(frame.origin.y);
    int w = floor(frame.size.width) + 1;
    int h = floor(frame.size.height) + 1;
    
    view.frame = CGRectMake(x, y, w, h);
}

- (void)setupBottomLineFrame
{
    UIView *firstTitleView = [self.titleViews firstObject];
    
    if (self.moveClearView) {
        self.moveClearView.ab_y = 0;
        self.moveClearView.ab_width = firstTitleView.frame.size.width;
        self.moveClearView.ab_height = firstTitleView.frame.size.height;
        self.moveClearView.ab_x = firstTitleView.frame.origin.x;
    }
    
    CGRect titleLabRect = [[self.titleLabelRects firstObject] CGRectValue];
    CGFloat lineW = titleLabRect.size.width;
    CGFloat lineH = self.segmentStyle.bottomLineHeight;
    CGFloat lineY = self.moveClearView.frame.size.height - lineH;
    
    if (self.bottomLine) {
        self.bottomLine.ab_x = (self.moveClearView.frame.size.width - lineW) / 2;
        self.bottomLine.ab_y = lineY;
        self.bottomLine.ab_width = lineW;
        self.bottomLine.ab_height = lineH;
    }
}

-(void)setupViewBottomBorderLine
{
    CGFloat lineH = [GlobalPublicMethods singleLineAdjustOffset];
    UIView *viewBottomBorderLine = [[UIView alloc]initWithFrame:CGRectMake(0, self.ab_height - lineH, self.ab_width, lineH)];
    viewBottomBorderLine.backgroundColor = [UIColor separatorColor];
    [self addSubview:viewBottomBorderLine];
    self.viewBottomBorderLine = viewBottomBorderLine;
    self.viewBottomBorderLine.hidden = self.segmentStyle.hiddenBottomBorderLine;
}

#pragma mark - titleViewOnClick
- (void)titleViewOnClick:(UITapGestureRecognizer *)tapGes
{
    UIView *currentTitleView = tapGes.view;
    if (!currentTitleView) {
        return;
    }
    _currentIndex = currentTitleView.tag;
    
    [self adjustUIWhenBtnOnClickWithAnimated:YES];
}

- (void)adjustUIWhenBtnOnClickWithAnimated:(BOOL)animated
{
    if (_currentIndex == _oldIndex) {
        return;
    }

    CGFloat animatedTime = animated ? 0.2 : 0.0;
    
    CGRect titleLabelRect = [self.titleLabelRects[_currentIndex] CGRectValue];
    CGFloat currentBottomLinW = titleLabelRect.size.width;
    
    UIView *oldTitleView = self.titleViews[_oldIndex];
    UILabel *oldTitleLabel = nil;
    for (UIView *subView in oldTitleView.subviews) {
        if ([subView isKindOfClass:[UILabel class]]) {
            UILabel *subLabel = (UILabel *)subView;
            if (subLabel.tag == oldTitleView.tag) {
                oldTitleLabel = subLabel;
            }
        }
    }
    UIView *currentTitleView = self.titleViews[_currentIndex];
    UILabel *currentTitleLabel = nil;
    for (UIView *subView in currentTitleView.subviews) {
        if ([subView isKindOfClass:[UILabel class]]) {
            UILabel *subLabel = (UILabel *)subView;
            if (subLabel.tag == currentTitleView.tag) {
                currentTitleLabel = subLabel;
            }
        }
    }
    
    __weak typeof(self) weakSelf = self;
    [UIView animateWithDuration:animatedTime animations:^{
        oldTitleLabel.textColor = weakSelf.segmentStyle.normalTitleColor;
        currentTitleLabel.textColor = weakSelf.segmentStyle.selectedTitleColor;
        
        weakSelf.moveClearView.ab_x = currentTitleView.ab_x;
        
        weakSelf.bottomLine.ab_width = currentBottomLinW;
        weakSelf.bottomLine.ab_x = (weakSelf.moveClearView.ab_width - currentBottomLinW) / 2;
    }];
    
    _oldIndex = _currentIndex;
    
    _selectedIndex = _currentIndex;
    
    if (self.titleBtnOnClick) {
        self.titleBtnOnClick(currentTitleLabel.text,_currentIndex);
    }
}

- (void)setSelectedIndex:(NSInteger)index animated:(BOOL)animated
{
    if (index < 0 || index >= self.titles.count) {
        return;
    }
    _currentIndex = index;
    [self adjustUIWhenBtnOnClickWithAnimated:animated];
}

-(void)adjustScollViewDidScroll:(UIScrollView *)scrollView
{
    CGFloat pageWidth = scrollView.frame.size.width;
    CGFloat offsetX = scrollView.contentOffset.x;
    CGFloat changeX = offsetX / (pageWidth / self.frame.size.width) / self.titles.count;
    self.moveClearView.ab_x = changeX;
}

-(void)showBaggeOnItemIndex:(NSInteger)index
{
    if (index < 0 || index > self.titleViews.count) {
        return;
    }
    
    UIView *titleView = self.titleViews[index];
    UIView *redPointView = [titleView viewWithTag:250];
    if (redPointView) {
        if (redPointView.hidden) {
            redPointView.hidden = NO;
        }
    }
}

- (void)hideBaggeOnItemIndex:(NSInteger)index
{
    if (index < 0 || index > self.titleViews.count) {
        return;
    }
    
    UIView *titleView = self.titleViews[index];
    UIView *redPointView = [titleView viewWithTag:250];
    if (redPointView) {
        if (!redPointView.hidden) {
            redPointView.hidden = YES;
        }
    }
}

@end

使用初始化也很简单:

    ABSegmentStyle *style = [[ABSegmentStyle alloc]init];
    style.titleFont = [UIFont boldSystemFontOfSize:17];

    YTWeakSelf(self);
    ABSegmentView *segmentView = [[ABSegmentView alloc]initWithFrame:CGRectMake(0, 0, kWidthValue(225), 44) segmentStyle:style titles:@[YTLocalizedString(@"跑步"),YTLocalizedString(@"自行车"),YTLocalizedString(@"铁三")] titleDidClick:^(NSString *title, NSInteger index) {
        if (weakself.scrollview) {
            [weakself.scrollview setContentOffset:CGPointMake(weakself.scrollview.frame.size.width * index, 0) animated:NO];
            [weakself scrollViewDidEndScrollingAnimation:weakself.scrollview];
            weakself.scrollview.scrollEnabled = YES;
        }
    }];
    self.navigationItem.titleView = segmentView;
    self.segmentView = segmentView;
    [segmentView setSelectedIndex:1 animated:NO];
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    CGFloat pageWidth = scrollView.frame.size.width;
    NSInteger page = scrollView.contentOffset.x / pageWidth;
    [self scrollViewDidEndScrollingAnimation:scrollView];
    
    [self.segmentView setSelectedIndex:page animated:YES];
}

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