iOS-自定義分段控制

1.分段控制在App開發中是非常常見的,也有很多類似的demo實現的方式都是大同小異有UIScrollView加UIButton形式,UICollectionView形式,本文主要是UICollectionView的形式
實現方式

  1. 創建BaseView作為父類,在父類中添加可配置的屬性如:顏色(選中和未選中),字體的大小,是否需要線條,等
  2. 繼承父類創建對應的子類View,子類的view就是我們在開發中的分段控制的view,同時通過UICollectionView和自動化布局來實現,

直接上代碼
1.OCSegmentBaseView

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSUInteger, OCSegmentIndicatorViewAlign) {

    OCSegmentIndicatorViewAlignCenter = 0, // 局中對其
    OCSegmentIndicatorViewAlignLeft, // 左標題對其
};


@interface OCSegmentBaseView : UIView

// 標題顏色  默認:灰色 black
@property (nonatomic, strong) UIColor  *titleColor;

// 標題選中顏色 默認:紅色  red
@property (nonatomic, strong) UIColor  *titleselectedColor;

// 標題顏色  默認:灰色 black
@property (nonatomic, strong) UIColor  *subTitleColor;

// 標題選中顏色 默認:紅色  red
@property (nonatomic, strong) UIColor  *subTitleSelectedColor;

// 小線條顏色 默認:紅色  red
@property (nonatomic, strong) UIColor  *indicatorColor;

//正常的字體大小
@property (nonatomic, strong) UIFont   *titleFont;

//選擇的字體大小
@property (nonatomic, strong) UIFont   *titleSelectedFont;

//子標題的字體大小
@property (nonatomic, strong) UIFont   *subTitleFont;

//子標題選擇的字體大小
@property (nonatomic, strong) UIFont   *subTitleSelectedFont;

//第一個按鈕到左邊的距離
@property (nonatomic, assign) CGFloat   leftPadding;

//兩個按鈕間的距離
@property (nonatomic, assign) CGFloat   spacing;

//最后一個按鈕到右邊的距離
@property (nonatomic, assign) CGFloat   rightPadding;

//是否顯示下方的線 默認顯示
@property (nonatomic, assign) BOOL      indicatorShow;

//指示器線的高度
@property (nonatomic, assign) CGFloat   indicatorHeight;

//指示器線的寬度
@property (nonatomic, assign) CGFloat   indicatorWidth;

//默認的index
@property (nonatomic, assign) NSInteger defaultIndex;

//是否等份,默認是NO,(在一個指定寬中度實現等份)
@property (nonatomic, assign) BOOL      equalParts;

//指示器的圓角剪切
@property (nonatomic, assign) CGFloat   cornerRadius;

//動畫的持續時間
@property (nonatomic, assign) CGFloat   duration;

//indicatorView 對其方式
@property (nonatomic, assign) OCSegmentIndicatorViewAlign  align;


@end

NS_ASSUME_NONNULL_END

#import "OCSegmentBaseView.h"

@implementation OCSegmentBaseView

- (instancetype)initWithFrame:(CGRect)frame {
    
    if(self = [super initWithFrame:frame])
    {
        self.indicatorShow = YES;
        self.align = OCSegmentIndicatorViewAlignCenter;
        self.titleFont = [UIFont systemFontOfSize:14];
        self.subTitleFont = [UIFont systemFontOfSize:12];
        self.titleSelectedFont = [UIFont systemFontOfSize:15];
        self.subTitleSelectedFont = [UIFont systemFontOfSize:12];
        self.leftPadding = 0;
        self.rightPadding = 0;
        self.spacing = 0;
        self.indicatorHeight = 2;
        self.defaultIndex = 0;
        self.equalParts = NO;
        self.duration = 0.3;
        self.titleColor = [UIColor blackColor];
        self.titleselectedColor = [UIColor redColor];
        self.subTitleColor = [UIColor blackColor];
        self.subTitleSelectedColor = [UIColor redColor];
    }
    return self;
}

@end

2.OCSegmentTitleView

#import <UIKit/UIKit.h>
#import "OCSegmentBaseView.h"

@class OCSegmentTitleView;

@protocol OCSegmentTitleViewDelegate <NSObject>

@optional
- (void)segmentView:(OCSegmentTitleView *)segmentView didSelectItemAtIndex:(NSInteger)index;

@end

@interface OCSegmentTitleView : OCSegmentBaseView

//標題
@property (nonatomic, strong) NSArray<NSString *>  *titleArray;

//子標題
@property (nonatomic, strong) NSArray<NSString *>  *subTitleArray;

//指示器的View
@property (nonatomic, strong) UIView               *indicatorView;

//選擇的下標 只讀的
@property (nonatomic, assign, readonly) NSInteger  selectedIndex;

@property (nonatomic, weak)   id<OCSegmentTitleViewDelegate> delegate;

@property (nonatomic, copy)   void(^didSelectItemAtIndexBlock)(NSInteger index);
@property (nonatomic, copy)   void(^didSelectItemAtIndexAndValueBlock)(NSInteger index, NSString *indexValue);

- (void)reloadData;

@end
#import "OCSegmentTitleView.h"
#import "OCSegmentTitleCCell.h"

@interface OCSegmentTitleView ()
<
UICollectionViewDataSource,
UICollectionViewDelegate,
UICollectionViewDelegateFlowLayout
>

@property (nonatomic, strong) UICollectionView  *collectionView;
@property (nonatomic, strong) NSIndexPath       *indexPath;

//YES 第一次出現,NO 不是
@property (nonatomic, assign) BOOL              firstAppear;

@property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout;

@end

@implementation OCSegmentTitleView

- (instancetype)initWithFrame:(CGRect)frame {
    
    if (self = [super initWithFrame:frame])
    {
        self.firstAppear = YES;
        self.indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
        [self addSubview:self.collectionView];
        [self.collectionView addSubview:self.indicatorView];
        [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {

            make.left.top.right.bottom.equalTo(self).offset(0);
        }];
    }
    return self;
}

#pragma mark -- setter

@synthesize defaultIndex = _defaultIndex;

- (void)setDefaultIndex:(NSInteger)defaultIndex {

    _defaultIndex = defaultIndex;
    //默認的indexPath
    self.indexPath = [NSIndexPath indexPathForRow:self.defaultIndex inSection:0];
}

- (NSInteger)selectedIndex {
    
    return self.indexPath.row;
}

#pragma mark -- private Method

- (void)reloadData {
    
    //可以立即獲得到frame
    [self layoutIfNeeded];
    self.firstAppear = YES;
    [self.collectionView reloadData];
    
    //設置邊距 top,left,bottom,right
    self.flowLayout.sectionInset = UIEdgeInsetsMake(0, self.leftPadding, self.indicatorHeight, self.rightPadding);
    
    //線條顏色
    self.indicatorView.backgroundColor = self.indicatorColor;
    
    //剪切圓角
    self.indicatorView.layer.cornerRadius = self.cornerRadius;
    
    if(self.titleArray.count > 0 && self.titleArray.count > self.indexPath.row)
    {
        [self.collectionView scrollToItemAtIndexPath:self.indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
    }
    else
    {   //設置為0 沒有標題,或者 indexPath.row 大于等于 數組的數量
        self.indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    }
    
    [self.collectionView reloadData];
}

#pragma mark -- UICollectionViewDataSource,UICollectionViewDelegate

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    
    return self.titleArray.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    OCSegmentTitleCCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"collectionCell" forIndexPath:indexPath] ;
    cell.titleLabel.text = self.titleArray[indexPath.item];
    
    if(self.subTitleArray.count > 0 && self.subTitleArray.count > indexPath.item)
    {//子標題賦值
        cell.subTitleLabel.text = self.subTitleArray[indexPath.item];
    }
    
    if(self.indexPath == indexPath )
    {//當前點擊的
        cell.titleLabel.font = self.titleSelectedFont;
        cell.titleLabel.textColor = self.titleselectedColor;
        cell.subTitleLabel.font = self.subTitleSelectedFont;
        cell.subTitleLabel.textColor = self.subTitleSelectedColor;
        
        if(self.indicatorShow)
        {//顯示--indicatorView
            [self indicatorViewPosition:cell cellForItemAtIndexPath:indexPath];
        }
    }
    else
    {//其他未點擊的
        cell.titleLabel.font = self.titleFont;
        cell.titleLabel.textColor = self.titleColor;
        cell.subTitleLabel.font = self.subTitleFont;
        cell.subTitleLabel.textColor = self.subTitleColor;
    }
    return cell ;
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    
    if(self.indexPath == indexPath)
    {//點擊已經選中的不做處理了
        return;
    }
    
    self.firstAppear = NO;
    self.indexPath = indexPath;
    [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
    [self.collectionView reloadData];
    
    if([self.delegate respondsToSelector:@selector(segmentView:didSelectItemAtIndex:)])
    {
        [self.delegate segmentView:self didSelectItemAtIndex:indexPath.row];
    }
    if(self.didSelectItemAtIndexBlock)
    {
        self.didSelectItemAtIndexBlock(indexPath.row);
    }
    if(self.didSelectItemAtIndexAndValueBlock)
    {
        self.didSelectItemAtIndexAndValueBlock(indexPath.row, self.titleArray[indexPath.row]);
    }
}

#pragma mark - UICollectionViewDelegateFlowLayout
//返回cell的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    if(self.equalParts == YES)
    {//平均分段
        CGFloat width = (self.frame.size.width - (self.titleArray.count - 1)*self.spacing) /self.titleArray.count;
        return CGSizeMake(width, self.frame.size.height - self.indicatorHeight);
    }
    else
    {
        CGSize size;
        CGSize subSize;
        if(self.indexPath.row == indexPath.row)
        {
            if(self.subTitleArray.count > 0 && self.subTitleArray.count > indexPath.row)
            {
                subSize = [self labelFont:self.subTitleSelectedFont textString:self.subTitleArray[indexPath.row]];
            }
            size = [self labelFont:self.titleSelectedFont textString:self.titleArray[indexPath.row]];
        }
        else
        {
            if(self.subTitleArray.count > 0 && self.subTitleArray.count > indexPath.row)
            {
                subSize = [self labelFont:self.subTitleFont textString:self.subTitleArray[indexPath.row]];
            }
            size = [self labelFont:self.titleFont textString:self.titleArray[indexPath.row]];
        }
        return CGSizeMake(size.width + subSize.width, self.frame.size.height - self.indicatorHeight);
    }
}

//水平方向間距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
    
    return self.spacing;
}
//水平方向間距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
    
    return 0;
}

#pragma mark -- private method

- (CGFloat)labefont:(UIFont *)font textArray:(NSArray *)textArray indexpath:(NSIndexPath *)indexPath {
    
    CGSize size;
    if(textArray.count > 0 && textArray.count > indexPath.item)
    {
        size = [self labelFont:font textString:textArray[indexPath.item]];
    }
    return size.width;
}

/* 根據文字長度返回size */
- (CGSize)labelFont:(UIFont *)font textString:(NSString *)str {
    
    UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, MAXFLOAT, 0)];
    label.text = str;
    label.numberOfLines = 0;
    label.font = font;
    //讓label通過文字設置size
    [label sizeToFit];
    //獲取label 的size
    CGSize size = label.frame.size;
    //返回出去
    return size;
}

/* 指示器View的位置顯示 */
- (void)indicatorViewPosition:(OCSegmentTitleCCell *)cell cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    CGFloat lineWidth;
    __weak typeof(self) weakSelf = self;
    if(self.align == OCSegmentIndicatorViewAlignLeft)
    {//左標題對其
        CGFloat subWidth = [self labefont:self.subTitleSelectedFont textArray:self.subTitleArray indexpath:indexPath];
        lineWidth = self.indicatorWidth > 0 ? self.indicatorWidth : cell.frame.size.width - subWidth;
    }
    else
    {
        lineWidth = self.indicatorWidth > 0 ? self.indicatorWidth : cell.frame.size.width;
    }
    
    if(self.firstAppear == YES)
    {//第一次出現
        self.indicatorView.frame = CGRectMake(0, self.frame.size.height - self.indicatorHeight, lineWidth, self.indicatorHeight);
        
        if(self.align == OCSegmentIndicatorViewAlignLeft)
        {//左標題對其
            CGFloat subWidth = [self labefont:self.subTitleSelectedFont textArray:self.subTitleArray indexpath:indexPath];
            self.indicatorView.center = CGPointMake(cell.center.x - subWidth/2, self.frame.size.height - self.indicatorHeight);
        }
        else
        {
            self.indicatorView.center = CGPointMake(cell.center.x, self.frame.size.height - self.indicatorHeight);
        }
    }
    else
    {
        [UIView animateWithDuration:self.duration animations:^{
        
            weakSelf.indicatorView.frame = CGRectMake(0, weakSelf.frame.size.height - weakSelf.indicatorHeight, lineWidth, weakSelf.indicatorHeight);
            
            if(weakSelf.align == OCSegmentIndicatorViewAlignLeft)
            {//左標題對其
                CGFloat subWidth = [weakSelf labefont:weakSelf.subTitleSelectedFont textArray:weakSelf.subTitleArray indexpath:indexPath];
                weakSelf.indicatorView.center = CGPointMake(cell.center.x - subWidth/2, weakSelf.frame.size.height - weakSelf.indicatorHeight);
            }
            else
            {
                weakSelf.indicatorView.center = CGPointMake(cell.center.x, weakSelf.frame.size.height - weakSelf.indicatorHeight);
            }
        }];
    }
}

#pragma mark -- lazy

- (UICollectionView *)collectionView {
    
    if(_collectionView == nil)
    {
        self.flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
        _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout];
        [_collectionView registerClass:[OCSegmentTitleCCell class] forCellWithReuseIdentifier:@"collectionCell"];
        _collectionView.showsVerticalScrollIndicator = NO;
        _collectionView.delegate = self;
        _collectionView.dataSource = self;
        _collectionView.showsHorizontalScrollIndicator = NO;
        _collectionView.backgroundColor = [UIColor clearColor];
    }
    return _collectionView;
}

- (UICollectionViewFlowLayout *)flowLayout {
    
    if (_flowLayout == nil)
    {
        _flowLayout = [[UICollectionViewFlowLayout alloc] init] ;
    }
    return _flowLayout;
}

- (UIView *)indicatorView {
    
    if(_indicatorView == nil)
    {
        _indicatorView = [[UIView alloc] init];
        _indicatorView.backgroundColor = [UIColor blackColor];
    }
    return _indicatorView;
}

@end

3.cell

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface OCSegmentTitleCCell : UICollectionViewCell

@property (nonatomic, strong) UILabel   *titleLabel;
@property (nonatomic, strong) UILabel   *subTitleLabel;

@end

NS_ASSUME_NONNULL_END
#import "OCSegmentTitleCCell.h"

@interface OCSegmentTitleCCell ()

@property (nonatomic, strong) UIView    *labelView;

@end

@implementation OCSegmentTitleCCell

- (instancetype)initWithFrame:(CGRect)frame {
    
    if(self = [super initWithFrame:frame])
    {
        [self makeConstraints];
    }
    return self;
}


#pragma mark -- 布局

- (void)makeConstraints {
    
    [self.contentView addSubview:self.labelView];
    [self.labelView addSubview:self.titleLabel];
    [self.labelView addSubview:self.subTitleLabel];
    
    [self.labelView mas_makeConstraints:^(MASConstraintMaker *make) {
       
        make.center.equalTo(self.contentView);
        make.left.right.equalTo(self.contentView).offset(0);
    }];
    [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
       
        make.left.top.bottom.equalTo(self.labelView).offset(0);
    }];
    [self.subTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
       
        make.right.top.bottom.equalTo(self.labelView).offset(0);
        make.left.equalTo(self.titleLabel.mas_right).offset(0);
    }];
}

#pragma mark -- lazy

- (UIView *)labelView {
    
    if(_labelView == nil)
    {
        _labelView = [[UIView alloc] init];
    }
    return _labelView;
}
- (UILabel *)titleLabel {
    
    if(_titleLabel == nil)
    {
        _titleLabel = [[UILabel alloc] init];
    }
    return _titleLabel;
}
- (UILabel *)subTitleLabel {
    
    if(_subTitleLabel == nil)
    {
        _subTitleLabel = [[UILabel alloc] init];
    }
    return _subTitleLabel;
}

@end

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370