實現(xiàn)自定義分段控件
- 相關屬性聲明
@interface HXSegmentControl()
//標題數(shù)組
@property (nonatomic , strong)NSArray *titleArray;
//按鈕數(shù)組
@property (nonatomic , strong)NSMutableArray *buttonArray;
//選中的按鈕的index
@property (nonatomic , assign)NSInteger selectedIndex;
@property (nonatomic , assign)CGFloat buttonWidth;
//按鈕底部的選中標志
@property (nonatomic , weak)UIView *buttonIndicator;
//分割線 (陰影效果)
@property (nonatomic , weak)UIView *separatorLine;
@end
- 封裝初始化類方法。調(diào)用初始化方法傳入?yún)?shù):需要設定的整個控件的frame 以及 每個分段標簽上的標題數(shù)組。
+ (instancetype)segmentControlWithFrame:(CGRect)frame titleArray:(NSArray *)titleArray{
HXSegmentControl *segCtrl = [[HXSegmentControl alloc]initWithFrame:frame titleArray:titleArray];
return segCtrl;
}
- (instancetype)initWithFrame:(CGRect)frame titleArray:(NSArray *)titleArray{
if (self = [super initWithFrame:frame]) {
//控件背景顏色默認為白色,按鈕不設置背景顏色(默認透明)
self.backgroundColor = [UIColor whiteColor];
self.titleArray = titleArray;
self.buttonArray = [NSMutableArray arrayWithCapacity:titleArray.count];
[self setupButtonArray];
[self setupSeparatorLine];
}
return self;
}
- 自定義分段控件,用按鈕來實現(xiàn)每一個分段,底部的分段選中標識用一個uiview實現(xiàn)。用在初始化方法里面?zhèn)鬟M來的標題數(shù)組來設置分段按鈕上的文字,其余的比如字號、顏色等屬性則在代碼中給定默認值。
//把btn添加上
- (void)setupButtonArray{
NSInteger titleNumber = self.titleArray.count;
self.buttonWidth = self.frame.size.width / titleNumber;
//根據(jù)標題數(shù)組中的元素個數(shù)創(chuàng)建對應按鈕
for (int i = 0; i < titleNumber ; i ++) {
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(i * self.buttonWidth, 0, self.buttonWidth, self.frame.size.height)];
btn.tag = i + 100;//tag從100之后開始設置
[btn setTitle:self.titleArray[i] forState:UIControlStateNormal];
[btn setTitle:self.titleArray[i] forState:UIControlStateSelected];
//按鈕選中、非選中 默認顏色:黑色 紅色
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateSelected];
//按鈕默認字號 14
btn.titleLabel.font = [UIFont systemFontOfSize:16.0];
[btn addTarget:self action:@selector(selectedChange:) forControlEvents:UIControlEventTouchUpInside];
[self.buttonArray addObject:btn];
[self addSubview:btn];
//默認選中第一個&創(chuàng)建按鈕底部選中標志
if (i == 0) {
UIView *buttonIndicator = [[UIView alloc]initWithFrame:CGRectMake(0, self.frame.size.height - 2.5, self.buttonWidth, 2)];//底部分割線高0.5
//選中標志默認顏色為紅色
buttonIndicator.backgroundColor = [UIColor redColor];
_buttonIndicator = buttonIndicator;
[self addSubview:_buttonIndicator];
[btn setSelected:YES];//默認選中第一個
self.selectedIndex = 0;
}
}
}
//底部分割線
- (void)setupSeparatorLine{
UIView *separatorLine = [[UIView alloc]initWithFrame:CGRectMake(0, self.frame.size.height - 0.5, self.frame.size.width, 0.5)];
_separatorLine = separatorLine;
_separatorLine.backgroundColor = [UIColor colorWithRed:0.78 green:0.78 blue:0.8 alpha:1.0];
[self addSubview:_separatorLine];
}
給按鈕動作綁定方法。每個分段在選中、非選中情況下外觀樣式的變化可以通過按鈕自身實現(xiàn)(設置不同state下title\titlecolor等),但按鈕底部的分段選中標識則需要通過按鈕方法實現(xiàn),改變選中標識的frame來移動標識的位置。
分段控件下方一般有對應的內(nèi)容視圖(一般是一個scrollview),滑動scrollView切換頁面時也需要切換分段按鈕,這里把按鈕的綁定方法分為兩個方法,第一個方法在主動點擊按鈕時觸發(fā),第二個方法被第一個方法調(diào)用,也可在scrollview切換頁面時調(diào)用。
//點擊按鈕觸發(fā),切換按鈕時候的按鈕動畫
- (void)selectedChange:(UIButton *)selectedBtn{
[self selectBtn:(selectedBtn.tag-100)];
}
- (void)selectBtn:(NSInteger)newIndex{
//先判斷點擊的按鈕和上次點擊的是不是同一個
if (self.selectedIndex == newIndex) {
return;
}
UIButton *lastBtn = (UIButton *)self.buttonArray[self.selectedIndex];
lastBtn.selected = NO;
UIButton *newBtn = (UIButton *)self.buttonArray[newIndex];
newBtn.selected = YES;
[UIView animateWithDuration:0.2 animations:^{
[self.buttonIndicator setFrame:CGRectMake(newIndex * self.buttonWidth,self.frame.size.height-2.5, self.buttonWidth, 2)];
}];
self.selectedIndex = newIndex;
[self.delegate selectedIndexChange:self selectedIndex:self.selectedIndex];
}
由于自定義分段控件中,只會在自身各按鈕之間切換,不會引起scrollview的內(nèi)容視圖切換。這里定義協(xié)議,讓添加分段控件的控制器vc實現(xiàn)協(xié)議方法,從而切換scrollview內(nèi)容視圖。
@class HXSegmentControl;
@protocol HXSegmentControlDelegate< NSObject>
@required
//點擊按鈕 切換頁面
-(void)selectedIndexChange:(HXSegmentControl*)SegmentControl selectedIndex:(NSInteger)index;
@end
- 設置自定義分段控件外觀屬性的方法。在初始化方法中已經(jīng)設置過默認的控件外觀樣式,如果不想使用默認樣式,可以用這個方法設置非選中、選中時的按鈕文字顏色、文字字號大小、底部選中標識的顏色以及整個控件的背景顏色。每個參數(shù)傳入為空的則依舊使用對應屬性的默認設置。
- (void)titleColor:(UIColor *)titleColor selectedTitleColor:(UIColor *)selectedTitleColor titleFontSize:(CGFloat)fontSize indicatorColor:(UIColor *)indicatorColor segBackgroundColor:(UIColor *)backgroundColor{
for (UIButton *btn in self.buttonArray) {
if (titleColor != nil) {
[btn setTitleColor:titleColor forState:UIControlStateNormal];
}
if (selectedTitleColor != nil) {
[btn setTitleColor:selectedTitleColor forState:UIControlStateSelected];
}
if (fontSize > 0) {
btn.titleLabel.font = [UIFont systemFontOfSize:fontSize];
}
}
if (indicatorColor != nil) {
self.buttonIndicator.backgroundColor = indicatorColor;
}
if (backgroundColor != nil) {
self.backgroundColor = backgroundColor;
}
}
- 在滑動分段控件下方的scrollview內(nèi)容視圖時,按鈕底部的選中標識 隨界面的拖拽而滑動,這樣就不會在scrollview滑動停止之后底部選中標識的位置才突然改變。
- (void)animateIndicator:(CGFloat)rate{
CGRect newFrame = self.buttonIndicator.frame;
newFrame.origin.x = self.selectedIndex * self.buttonWidth + rate * self.buttonWidth;
self.buttonIndicator.frame = newFrame;
}
使用
視圖控制器中使用。
#import "Masonry.h"
#import "HXSegmentControl.h"
#define screenWidth CGRectGetWidth([UIScreen mainScreen].bounds)
#define screenHeight CGRectGetHeight([UIScreen mainScreen].bounds)
@interface ProductDetailPageTwo ()<UIScrollViewDelegate,HXSegmentControlDelegate>
@property (nonatomic ,weak)HXSegmentControl *subSegmentCtrl;
@property (nonatomic ,weak)UIScrollView *scrollView;
@property (nonatomic ,assign)CGFloat segmentHeight;
@property (nonatomic ,strong)NSArray *controllersArray;//子控制器數(shù)組
@property (nonatomic ,strong)NSArray *titleArray; //標題數(shù)組
@property (nonatomic ,assign)CGFloat lastOffsetX;//scrollview內(nèi)容切換后的x偏移
@end
- 控制器初始化方法
//初始化方法。參數(shù)1:子控制器數(shù)組 參數(shù)2:自定義分段控件的分段標題文字數(shù)組
+ (instancetype)initWithControllersArray:(NSArray *)controllersArray titleArray:(NSArray *)titleArray{
return [[self alloc]initWithControllersArray:controllersArray titleArray:(NSArray *)titleArray];
}
- (instancetype)initWithControllersArray:(NSArray *)controllersArray titleArray:(NSArray *)titleArray{
if(self = [super init]){
self.controllersArray = controllersArray;
self.titleArray = titleArray;
}
return self;
}
對于scrollview每個“頁面”對應的視圖,這里使用childViewController子控制器來組織,每個“頁面”對應一個子控制器,再把vc.view添加到scrollview。這樣做比使用addsubview來把每個“頁面”上的控件都全部直接添加到scrollview上要好。原因:
蘋果新的API增加了addChildViewController方法,并且希望我們在使用addSubview時,同時調(diào)用[self addChildViewController:child]方法將sub view對應的viewController也加到當前ViewController的管理中。
對于那些當前暫時不需要顯示的subview,只通過addChildViewController把subViewController加進去;需要顯示時再調(diào)用transitionFromViewController方法。將其添加進入底層的ViewController中。
這樣做的好處:
1.無疑,對頁面中的邏輯更加分明了。相應的View對應相應的ViewController。
2.當某個子View沒有顯示時,將不會被Load,減少了內(nèi)存的使用。
3.當內(nèi)存緊張時,沒有Load的View將被首先釋放,優(yōu)化了程序的內(nèi)存釋放機制。
//初始化子控制器
- (void)setupChildViewControllers{
for (UIViewController *vc in self.controllersArray) {
[self addChildViewController:vc];
}
}
- 添加自定義分段控件、scrollview
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self initViews];
[self setupChildViewControllers];
}
- (void)initViews{
[self setupHXSegment];
[self setupScrollView];
}
//創(chuàng)建自定義分段控件
- (void)setupHXSegment{
self.segmentHeight = 40;
//創(chuàng)建自定義分段控件
HXSegmentControl *subSegmentCtrl = [HXSegmentControl segmentControlWithFrame:CGRectMake(0, 0, 180, self.segmentHeight) titleArray:self.titleArray];
[subSegmentCtrl titleColor:colorYellow selectedTitleColor:colorBrown titleFontSize:18.0 indicatorColor:colorBrown segBackgroundColor:[UIColor whiteColor]];
_subSegmentCtrl = subSegmentCtrl;
CGPoint newCenter = self.subSegmentCtrl.center;
newCenter.x = self.view.center.x;
self.subSegmentCtrl.center = newCenter;
_subSegmentCtrl.delegate = self;
[self.view addSubview:_subSegmentCtrl];
UIView *separatorLine = [[UIView alloc]init];//因為我在這里設置的自定義分段控件的寬度不等于屏寬,需要補一條底部分割線給它
separatorLine.backgroundColor = [UIColor colorWithRed:0.78 green:0.78 blue:0.8 alpha:1.0];
[self.view addSubview:separatorLine];
__weak typeof(self)weakself = self;
[separatorLine mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(weakself.view.frame.size.width, 0.5));
make.centerX.equalTo(weakself.view.mas_centerX);
make.bottom.equalTo(subSegmentCtrl.mas_bottom);
}];
}
//創(chuàng)建scrollview
- (void)setupScrollView {
UIScrollView *scrollView = [[UIScrollView alloc]init];
_scrollView = scrollView;
_scrollView.bounces = NO;
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.showsVerticalScrollIndicator = NO;
_scrollView.pagingEnabled = YES;
_scrollView.contentSize = CGSizeMake(screenWidth * 2, 0);
_scrollView.delegate = self;
[self.view addSubview:_scrollView];
__weak typeof(self)weakself = self;
[_scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(weakself.view);
make.top.equalTo(_subSegmentCtrl.mas_bottom);
}];
[self.view updateConstraintsIfNeeded];
[self.view layoutIfNeeded];
//push進來默認選中第一個 添加第一個子控制器的view
UIViewController *pageIntroductionVC = self.controllersArray[0];
pageIntroductionVC.view.frame = CGRectMake(0, 0, _scrollView.frame.size.width, _scrollView.frame.size.height);
[_scrollView addSubview:pageIntroductionVC.view];
self.lastOffsetX = _scrollView.contentOffset.x;
}
- 一些代理方法
#pragma mark HXSegmentControlDelegate
//切換頁面。點擊分段按鈕切換頁面or滑動scrollview切換都會調(diào)用到這個方法
-(void)selectedIndexChange:(HXSegmentControl*)SegmentControl selectedIndex:(NSInteger)index{
CGPoint offset = self.scrollView.contentOffset;
offset.x = index * self.scrollView.frame.size.width;
[self.scrollView setContentOffset:offset animated:YES];
self.lastOffsetX = offset.x;
}
#pragma mark UIScrollViewDelegate
//計算此次scrollview滑動的距離deltaOffsetx,然后與“一頁”寬度算出“比率”,自定義分段控件中的底部選中標識也要對應滑動相同“比率”距離。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat deltaOffsetx = scrollView.contentOffset.x - self.lastOffsetX;
CGFloat rate = deltaOffsetx / self.scrollView.frame.size.width;
[self.subSegmentCtrl animateIndicator:rate];
}
//滾動完畢就會調(diào)用,如果不是人為拖拽scrollView導致滾動完畢,才會調(diào)用這個方法.由setContentOffset:animated: 或者 scrollRectToVisible:animated: 方法觸發(fā)
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
int index = scrollView.contentOffset.x / _scrollView.frame.size.width;
UIViewController *willShowChildVc = self.controllersArray[index];
// 如果這個子控制器的view已經(jīng)添加過了,就直接返回
if (willShowChildVc.isViewLoaded) return;
willShowChildVc.view.frame = CGRectMake(scrollView.contentOffset.x, 0, _scrollView.frame.size.width, _scrollView.frame.size.height);
[scrollView addSubview:willShowChildVc.view];
}
//滾動完畢就會調(diào)用.如果是人為拖拽scrollView導致滾動完畢,才會調(diào)用這個方法
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
self.lastOffsetX = scrollView.contentOffset.x;
NSInteger pageNum = scrollView.contentOffset.x / _scrollView.frame.size.width;
//拖拽滑動切換頁面 切換至segment對應的某項
[self.subSegmentCtrl selectBtn:pageNum];
// 添加子控制器的view
[self scrollViewDidEndScrollingAnimation:scrollView];
}
在這里,只有第一個子控制器是一開始(scrollview創(chuàng)建的時候)就添加到scrollview中顯示出來的。其余的子控制器,在對應得“頁面”滑動出來之后才加載顯示出來。
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
方法
滾動完畢就會調(diào)用,如果不是人為拖拽scrollView導致滾動完畢,才會調(diào)用這個方法,也即是由setContentOffset:animated: 或者 scrollRectToVisible:animated: 方法觸發(fā)。
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
滾動完畢就會調(diào)用.如果是人為拖拽scrollView導致滾動完畢,才會調(diào)用這個方法。
使用demo:
NSMutableArray *testArray = [NSMutableArray array];
for (int i = 0; i < 2; i++) {
ProductIntroduction *vc = [[ProductIntroduction alloc]init];//一個隨機創(chuàng)建背景顏色的vc
[testArray addObject:vc];
}
ProductDetailPageTwo *test2 = [ProductDetailPageTwo initWithControllersArray:testArray titleArray:@[@"商品簡介",@"參數(shù)表"]];
ps:使用alloc init一個控制器不會調(diào)用到它的viewDidLoad方法,viewDidLoad在視圖顯示加載的時候才會調(diào)用。
實現(xiàn)效果
更新:初始化部分的改進。參考HMSegmentedControl。http://www.lxweimin.com/p/229056b55f44
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self commonInit];
}
return self;
}
- (instancetype)initWithTitleArray:(NSArray *)titleArray{
if (self = [super initWithFrame:CGRectZero]) {
[self commonInit];
self.titleArray = titleArray;
}
return self;
}
- (void)commonInit{
self.backgroundColor = [UIColor whiteColor];
UIView *separatorLine = [[UIView alloc]init];
_separatorLine = separatorLine;
_separatorLine.backgroundColor = [UIColor colorWithRed:0.78 green:0.78 blue:0.8 alpha:1.0];
[self addSubview:_separatorLine];
UIView *buttonIndicator = [[UIView alloc]init];
buttonIndicator.backgroundColor = [UIColor redColor];
_buttonIndicator = buttonIndicator;
[self addSubview:_buttonIndicator];
}
- (void)setTitleArray:(NSArray *)titleArray{
_titleArray = titleArray;
NSInteger titleNumber = _titleArray.count;
self.buttonArray = [NSMutableArray arrayWithCapacity:titleNumber];
for (int i = 0; i < titleNumber ; i ++){
UIButton *btn = [[UIButton alloc]init];
btn.tag = i + 100;
[btn setTitle:self.titleArray[i] forState:UIControlStateNormal];
[btn setTitle:self.titleArray[i] forState:UIControlStateSelected];
//按鈕選中、非選中 默認顏色:黑色 紅色
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateSelected];
btn.titleLabel.font = fontSize16;
[btn addTarget:self action:@selector(selectedChange:) forControlEvents:UIControlEventTouchUpInside];
[self.buttonArray addObject:btn];
[self addSubview:btn];
}
[self setNeedsLayout];
}
- (void)setFrame:(CGRect)frame {
[super setFrame:frame];
[self updateSeg];
}
- (void)layoutSubviews{
[self updateSeg];
}
- (void)updateSeg{
self.separatorLine.frame = CGRectMake(0, self.frame.size.height - 0.5, self.frame.size.width, 0.5);
NSInteger titleNumber = self.titleArray.count;
self.buttonWidth = self.frame.size.width / titleNumber;
for (int i = 0; i < titleNumber ; i ++){
[self.buttonArray[i] setFrame:CGRectMake(i * self.buttonWidth, 0, self.buttonWidth, self.frame.size.height)];
CGFloat stringWidth = [self sizeWithString:(NSString *)self.titleArray[i] font:fontSize16 maxSize:CGSizeMake(MAXFLOAT, MAXFLOAT)].width;
self.buttonWidth = MAX(stringWidth, self.buttonWidth);
}
[self.buttonArray[0] setSelected:YES];
self.buttonIndicator.frame = CGRectMake(0, self.frame.size.height - 2.5, self.buttonWidth, 2);
self.selectedIndex = 0;
}
- (CGSize)sizeWithString:(NSString *)str font:(UIFont *)font maxSize:(CGSize)maxSize
{
NSDictionary *dict = @{NSFontAttributeName : font};
CGSize size = [str boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:dict context:nil].size;
return size;
}