自定义UISegmentedControl分段控制器之ABSeg
2018-06-08 本文已影响41人
SAW_
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];
}