分段控制器
程序员文章站
2024-03-09 18:01:47
...
写在前面
项目中经常用到分段控制器,网上找的几个用着不是太顺手,细读了两份源码,其实也不难,二来各个项目特色不一样,觉得造个*还是有些必要的。
沙场点兵
所谓的分段控制器无非是顶部选择器跟下面的视图控制器联动。
那么怎么实现这种联动呢。
顶部的选择器可以用collectionView
实现,有一下几点好处
选中某一个通过代理方法可以方便的执行一些操作
把当前选中的item居中动画,直接通过collectionView自带的方法实现
底部视图控制器可以用一个自定义containerViewController实现,内部添加对应的childViewController
我们让containerViewController跟它的childViewController建立关系,管理childViewController的大小和位置,方便子控制器之间的切换
将childViewController添加到容器后,容器会自动将相关的消息转发给childViewController,彼此又相互独立,子控制器管理自己的事务。
自定义containerViewController官方文档
Talk is cheap Show me your code
// SDCursorView.h
#import <UIKit/UIKit.h>
@protocol SDCursorViewDelegate <NSObject>
- (void)sdCursorViewDelegateSelectIndex:(NSInteger )index;
@end
@interface SDCursorView : UIView
@property (nonatomic, weak)id <SDCursorViewDelegate> delegate;
/**
* 标题和显示的页面,数量必须相等。
*/
@property (nonatomic, copy) NSArray *titles;
/**
* 所要添加的viewController的实例。绘制界面时,会将viewController的view添加在scrollView上
*/
@property (nonatomic, copy) NSArray *controllers;
/**
* 底部标示线颜色,默认 redColor
*/
@property (nonatomic, strong) UIColor *indicatorColor;
/**
* 当前页面所在的controller,必须赋值
*/
@property (nonatomic, strong) UIViewController *parentViewController;
/**
* 底部切换页面的容器高度
*/
@property (nonatomic, assign) CGFloat contentViewHeight;
/**
* 默认都是14号字体
*/
@property (nonatomic, strong) UIFont *selectedFont;
@property (nonatomic, strong) UIFont *normalFont;
/**
* 默认分别是 [UIColor redColor],[UIColor whiteColor]
*/
@property (nonatomic, strong) UIColor *selectedColor;
@property (nonatomic, strong) UIColor *normalColor;
/**
* 当前选中的index。可以设置当前的index
*/
@property (nonatomic, assign) NSInteger currentIndex;
/**
* 分割线位置调整。总是居中显示 默认(0,3,2,3)
分割线默认高度为3, left可调整宽度,top可调整高度,bottom可调整lineView的y值
*/
@property (nonatomic, assign) UIEdgeInsets lineEdgeInsets;
/**
* 调用此方法来绘制界面,required
*/
-(void)reloadPages;
@end
// SDCursorView.m
#import "SDCursorView.h"
#import "SDSelectorCell.h"
@interface SDCursorView ()<UICollectionViewDelegateFlowLayout,UICollectionViewDataSource>
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) UICollectionViewFlowLayout *layout;
@property (nonatomic, strong) UIScrollView *rootScrollView;
@property (nonatomic, strong) UIView *lineView;
@end
static NSString *const cellIdentifier = @"selectorCell";
@implementation SDCursorView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor blackColor];
_normalFont = _selectedFont = [UIFont systemFontOfSize:14];
_normalColor = [UIColor whiteColor];
_selectedColor = [UIColor redColor];
_indicatorColor = [UIColor redColor];
_currentIndex = 0;
_lineEdgeInsets = UIEdgeInsetsMake(CGRectGetHeight(frame)-3, 0, 0, 0);
[self addSubview:self.collectionView];
[self.collectionView addSubview:self.lineView];
}
return self;
}
/**
* 主动设置cursor取消选中item
*
* @param index index
*/
- (void)deselectItemAtIndex:(NSInteger)index {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
SDSelectorCell *cell = (SDSelectorCell*)[self.collectionView cellForItemAtIndexPath:indexPath];
cell.selected = NO;
}
/**
* 主动设置cursor选中item
*
* @param index index
*/
- (void)selectItemAtIndex:(NSInteger)index {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:_currentIndex inSection:0];
[self.collectionView selectItemAtIndexPath:indexPath
animated:YES
scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
SDSelectorCell *cell = (SDSelectorCell*)[self.collectionView cellForItemAtIndexPath:indexPath];
cell.selected = YES;
CGRect rect = cell.frame;
if (!cell) {
UICollectionViewLayoutAttributes *attributes = [self.collectionView layoutAttributesForItemAtIndexPath:indexPath];
rect = attributes.frame;
}
[self resizeLineViewWihtCellFrame:rect animated:YES];
[self addChildViewController];
}
/**
* 设置标识线的frame
*
* @param frame cellFrame
*/
- (void)resizeLineViewWihtCellFrame:(CGRect)frame animated:(BOOL)animated {
CGRect rect = CGRectMake(CGRectGetMinX(frame)+_lineEdgeInsets.left,
_lineEdgeInsets.top,
CGRectGetWidth(frame)-_lineEdgeInsets.left-_lineEdgeInsets.right,
CGRectGetHeight(self.collectionView.frame)-_lineEdgeInsets.top-_lineEdgeInsets.bottom);
if (animated) {
[UIView animateWithDuration:0.2f animations:^{
self.lineView.frame = rect;
}];
}else{
self.lineView.frame = rect;
}
}
/**
* 设置collectionView的偏移量,使得选中的项目居中
*
* @param frame cellFrame
*/
- (void)setContentOffsetWithCellFrame:(CGRect)frame {
CGFloat width = CGRectGetWidth(self.collectionView.frame)/2;
CGFloat contentW = self.collectionView.contentSize.width;
CGFloat offsetX = 0;
if (CGRectGetMidX(frame) <= width || contentW <= width*2) {//当前选中的cell位于偏左,没有移动空间 || frame没有填满
offsetX = 0;
}else if (CGRectGetMidX(frame) + width >= contentW) {//当前选中cell偏右,没有移动空间
offsetX = contentW - width*2;
}else{//当前选中cell有左右移动空间
offsetX = CGRectGetMidX(frame)-width;
}
[self.collectionView setContentOffset:CGPointMake(offsetX, 0) animated:YES];
}
/**
* 将子viewController添加到scrollView上
*/
- (void)addChildViewController {
//viewController
UIViewController *controller = _controllers[_currentIndex];
CGFloat startX = CGRectGetWidth(self.rootScrollView.bounds)*_currentIndex;
if (!controller.parentViewController) {
[self.parentViewController addChildViewController:controller];
CGRect rect = self.rootScrollView.bounds;
rect.origin.x = startX;
controller.view.frame = rect;
[self.rootScrollView addSubview:controller.view];
[controller didMoveToParentViewController:self.parentViewController];
}
[self.rootScrollView setContentOffset:CGPointMake(startX, 0) animated:NO];
if (self.delegate && [self.delegate respondsToSelector:@selector(sdCursorViewDelegateSelectIndex:)]) {
[self.delegate sdCursorViewDelegateSelectIndex:_currentIndex];
}
}
- (void)reloadPages {
NSAssert(_titles.count == _controllers.count, @"titles' count is not equal to controllerNames' count");
[self.collectionView reloadData];
[self addChildViewController];
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if ([self.rootScrollView isEqual:scrollView]) {
CGFloat offsetX = scrollView.contentOffset.x;
if (offsetX >= 0) {
NSInteger index = offsetX / CGRectGetWidth(self.bounds);
if (self.currentIndex != index) {
self.currentIndex = index;
[self selectItemAtIndex:self.currentIndex];
}
}
}
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _titles.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
SDSelectorCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
NSDictionary *dic = _titles[indexPath.item];
[cell.button setImage:[UIImage imageNamed:dic[@"nomal"]] forState:UIControlStateNormal];
[cell.button setImage:[UIImage imageNamed:dic[@"nomal"]] forState:UIControlStateNormal|UIControlStateHighlighted];
[cell.button setImage:[UIImage imageNamed:dic[@"select"]] forState:UIControlStateSelected];
[cell.button setImage:[UIImage imageNamed:dic[@"select"]] forState:UIControlStateSelected|UIControlStateHighlighted];
if (indexPath.row == 0) {
cell.shuline.hidden = YES;
}else {
cell.shuline.hidden = NO;
}
if (indexPath.item == _currentIndex) {
[collectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone];
cell.selected = YES;
[self resizeLineViewWihtCellFrame:cell.frame animated:NO];
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (_currentIndex == indexPath.item) {
return;
}
self.currentIndex = indexPath.item;
[self selectItemAtIndex:indexPath.item];
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
SDSelectorCell *cell = (SDSelectorCell*)[collectionView cellForItemAtIndexPath:indexPath];
cell.selected = NO;
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat viewWidth = SCREEN_WIDTH - 28 - 7;
CGFloat viewHeight = 77 - 7;
return CGSizeMake(viewWidth/4, viewHeight);
}
#pragma mark - setter&getter
- (void)setIndicatorColor:(UIColor *)indicatorColor {
_indicatorColor = indicatorColor;
self.lineView.backgroundColor = indicatorColor;
}
- (void)setTitles:(NSArray *)titles {
_titles = titles;
self.rootScrollView.contentSize = CGSizeMake(CGRectGetWidth(self.bounds)*self.titles.count, 100);
}
- (UIScrollView*)rootScrollView {
if (!_rootScrollView) {
_rootScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, CGRectGetHeight(self.bounds) + 7 + NavigationBarHeight, CGRectGetWidth(self.bounds), self.contentViewHeight)];
_rootScrollView.backgroundColor = [UIColor whiteColor];
_rootScrollView.pagingEnabled = YES;
_rootScrollView.delegate = self;
_rootScrollView.alwaysBounceHorizontal = YES;
_rootScrollView.showsVerticalScrollIndicator = NO;
_rootScrollView.showsHorizontalScrollIndicator = NO;
_rootScrollView.scrollsToTop = NO;
_rootScrollView.bounces = YES;
[self.parentViewController.view addSubview:_rootScrollView];
NSAssert(self.parentViewController, @"self.parentViewController must has a value");
}
return _rootScrollView;
}
- (UIView*)lineView {
if (!_lineView) {
_lineView = [[UIView alloc]init];
_lineView.backgroundColor = _indicatorColor;
}
return _lineView;
}
- (UICollectionView*)collectionView {
if (!_collectionView) {
_layout = [[UICollectionViewFlowLayout alloc]init];
_layout.minimumLineSpacing = 0;
_layout.minimumInteritemSpacing = 0;
_layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(17.5, 0, SCREEN_WIDTH - 35, 70) collectionViewLayout:_layout];
_collectionView.showsVerticalScrollIndicator = NO;
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.scrollsToTop = NO;
_collectionView.backgroundColor = [UIColor clearColor];
_collectionView.delegate = self;
_collectionView.dataSource = self;
[_collectionView registerClass:[SDSelectorCell class]
forCellWithReuseIdentifier:cellIdentifier];
}
return _collectionView;
}
@end
// SDSelectorCell.h
#import <UIKit/UIKit.h>
@interface SDSelectorCell : UICollectionViewCell
@property (nonatomic, strong)UIButton *button;
@property (nonatomic, strong) UIView *shuline;
@end
// SDSelectorCell.m
#import "SDSelectorCell.h"
@implementation SDSelectorCell
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self.contentView addSubview:self.button];
_shuline = [[UIView alloc] init];
_shuline.layer.backgroundColor = HEXCOLOR(0xF2F2F2).CGColor;
[self.contentView addSubview:_shuline];
}
return self;
}
- (void)setSelected:(BOOL)selected {
super.selected = selected;
self.button.selected = selected;
}
- (UIButton *)button {
if (!_button) {
_button = [UIButton buttonWithType:UIButtonTypeCustom];
_button.userInteractionEnabled = NO;
[_button setBackgroundImage:[BaseMethod createImageWithColor:[UIColor whiteColor]] forState:UIControlStateNormal];
[_button setBackgroundImage:[BaseMethod createImageWithColor:[UIColor yellowColor]] forState:UIControlStateNormal|UIControlStateHighlighted];
[_button setBackgroundImage:[BaseMethod createImageWithColor:HEXCOLOR(0xd9effe)] forState:UIControlStateSelected];
[_button setBackgroundImage:[BaseMethod createImageWithColor:[UIColor blueColor]] forState:UIControlStateSelected|UIControlStateHighlighted];
}
return _button;
}
- (void)layoutSubviews {
[super layoutSubviews];
_shuline.frame = CGRectMake(0, 8, 1, 52);
_button.frame = self.bounds;
}
@end
在外部调用
NSDictionary *dic3 = [NSDictionary dictionaryWithObjectsAndKeys:@"home_top_nomal_1",@"nomal",@"home_top_select_1",@"select", nil];
NSDictionary *dic2 = [NSDictionary dictionaryWithObjectsAndKeys:@"home_top_nomal_2",@"nomal",@"home_top_select_2",@"select", nil];
NSDictionary *dic4 = [NSDictionary dictionaryWithObjectsAndKeys:@"home_top_nomal_3",@"nomal",@"home_top_select_3",@"select", nil];
NSDictionary *dic1 = [NSDictionary dictionaryWithObjectsAndKeys:@"home_top_nomal_4",@"nomal",@"home_top_select_4",@"select", nil];
UIViewController *controller1 = [[ExampleBaseViewController alloc] initWithTitle:@"1" viewClass:nil];
UIViewController *controller2 = [[ExampleBaseViewController alloc] initWithTitle:@"2" viewClass:nil];
UIViewController *controller3 = [[ExampleBaseViewController alloc] initWithTitle:@"3" viewClass:nil];
UIViewController *controller4 = [[ExampleBaseViewController alloc] initWithTitle:@"4" viewClass:nil];
_cursorView.titles = @[dic1, dic2,dic3];
_cursorView.controllers = @[controller1,controller2,controller3];
_cursorView.currentIndex = 0;
[_cursorView reloadPages];