接下来是segmentView
这个黑色可部分是collectionView,因为他自由度比较高,简单易用~
segmentView将item和itemContent结合在一起:
以下是初始化以及布局:
///接口部分:
@interface YCSegmentView : UIView
///非选中颜色
@property (nonatomic,strong) UIColor *normalColor;
///选中颜色
@property (nonatomic,strong) UIColor *highlightColor;
///字体
@property (nonatomic,strong) UIFont *font;
- (instancetype)initWithFrame:(CGRect)frame titleHeight:(CGFloat)height viewControllers:(NSArray *)viewControllers;
@end
@implementation YCSegmentView
……
- (instancetype)initWithFrame:(CGRect)frame titleHeight:(CGFloat)height viewControllers:(NSArray *)viewControllers {
if (self = [super initWithFrame:frame]) {
_titleHeight = height;
_viewControllers = viewControllers;
[self setupAllViews];
}
return self;
}
/*
因为我们想让用户更加方便使用,
所以希望用户只需要传入视图控制器,
就可以完成控件的初始化,
控制器有`title`属性。
我们就可以直接取到控制器的标题来初始化`itemContent`
*/
- (void)setupAllViews {
NSMutableArray *titles = [NSMutableArray arrayWithCapacity:_viewControllers.count];
for (UIViewController *vc in _viewControllers) {
[titles addObject:vc.title];
}
self.titleView = [[YCSegmentItemsContentView alloc] initWithFrame:CGRectZero titles:titles];
self.titleView.delegate = self;
[self addSubview:self.titleView];
self.cLayout = [[UICollectionViewFlowLayout alloc] init];
///设置collectionView的滚动方式为水平滚动
self.cLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView = [[UICollectionView alloc] initWithFrame:(CGRectZero) collectionViewLayout:self.cLayout];
///这是collectionView按页滚动
self.collectionView.pagingEnabled = YES;
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
///注意!这里为`collectionView`注册`cell` (YCSegmentViewUnit),下文提到。
[self.collectionView registerClass:[YCSegmentViewUnit class] forCellWithReuseIdentifier:@"YCSegmentViewUnit"];
///注意!这里为`collectionView`添加观察者,观察属性`contentOffset`的变化,来获得页数,控制`itemContent`选择哪一个`item`
[self.collectionView addObserver:self forKeyPath:@"contentOffset" options:(NSKeyValueObservingOptionNew) context:nil];
[self addSubview:self.collectionView];
}
- (void)layoutSubviews {
self.titleView.frame = CGRectMake(0, 0, self.frame.size.width, _titleHeight);
self.collectionView.frame = CGRectMake(0, _titleHeight, self.frame.size.width, self.frame.size.height - _titleHeight);
}
……
@end
还有重写KVO发现属性变化后会调用的方法:
……
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
CGPoint offset = self.collectionView.contentOffset;
///这里` + 0.5` 是为了当滚动到页面一半的时候,就已经算作下一页或者上一页
CGFloat pageFloat = offset.x / self.collectionView.bounds.size.width + 0.5;
if (pageFloat _viewControllers.count) {
pageFloat = _viewControllers.count;
}
NSInteger page = (NSInteger)pageFloat;
self.titleView.page = page;
}
……
在类的延展中有这样一些属性和变量:
@interface YCSegmentView ()
{
NSArray *_viewControllers;//用来保存所有的控制器
CGFloat _titleHeight;//用来保存`itemContent`的高度
}
@property (nonatomic,strong) UICollectionViewFlowLayout *cLayout;
@property (nonatomic,strong) UICollectionView *collectionView;
@property (nonatomic,strong) YCSegmentItemsContentView *titleView;
@end
剩下的就是实现collectionView的协议方法,以及itemContent的协议方法:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return _viewControllers.count;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
YCSegmentViewUnit * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"YCSegmentViewUnit" forIndexPath:indexPath];
UIViewController *vc = _viewControllers[indexPath.section];
if (!vc.isViewLoaded) {
[vc loadViewIfNeeded];
}
cell.view = vc.view;
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return collectionView.bounds.size;
}
- (void)didSelectedButtonAtIndex:(NSInteger)index {
CGFloat width = self.collectionView.bounds.size.width;
[self.collectionView setContentOffset:(CGPointMake(width * index, 0)) animated:YES];
}
接下来我们看一下YCSegmentViewUnit类的代码:
@interface YCSegmentViewUnit : UICollectionViewCell
@property (nonatomic,strong) UIView *view;
@end
@implementation YCSegmentViewUnit
- (void)setView:(UIView *)view {
if (_view) {
[_view removeFromSuperview];
}
[self.contentView addSubview:view];
[self setNeedsLayout];
_view = view;
}
- (void)layoutSubviews {
self.view.frame = self.contentView.bounds;
}
@end
当我们为cell的view属性赋值的时候,需要考虑当前的_view是否存在,如果存在就从contentView上移除,然后将新传入的view添加到contentView上,然后设置view的位置和大小贴合contentView。
最后千万不要忘了,_view = view,因为我们需要实力变量保存当前的view!
这样这个控件的封装就搞定啦~
不!还有非常重要的一点!
我们使用了KVO对collectionView进行了属性观察,但是如果观察着被释放了,肯定会出现问题,所以我们需要在YCSegmentView中重写- (void)dealloc方法,在dealloc中移除对collectionView的观察:
- (void)dealloc {
[self.collectionView removeObserver:self forKeyPath:@"contentOffset"];
}
恩,这样子就大功告成了,我们实例化一个看一看~~
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
HomeViewController *vc1 = [[HomeViewController alloc] init];
vc1.title = @"首页";
RankViewController *vc2 = [[RankViewController alloc] init];
vc2.title = @"榜单";
MasterViewController *vc3 = [[MasterViewController alloc] init];
vc3.title = @"达人";
ChoicenessViewController *vc4 = [[ChoicenessViewController alloc] init];
vc4.title = @"每周精选";
YCSegmentView *view = [[YCSegmentView alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, self.view.bounds.size.height) titleHeight:44 viewControllers:@[vc1, vc2, vc3, vc4]];
view.highlightColor = [UIColor orangeColor];
[self.view addSubview:view];
}
没错,就是这个样子~
源码:https://github.com/ilakeYC/YCSegment