TYTabPagerBar.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. //
  2. // TYTabPagerBar.m
  3. // TYPagerControllerDemo
  4. //
  5. // Created by tany on 2017/7/13.
  6. // Copyright © 2017年 tany. All rights reserved.
  7. //
  8. #import "TYTabPagerBar.h"
  9. @interface TYTabPagerBar ()<UICollectionViewDelegateFlowLayout, UICollectionViewDataSource> {
  10. struct {
  11. unsigned int transitionFromeCellAnimated :1;
  12. unsigned int transitionFromeCellProgress :1;
  13. unsigned int widthForItemAtIndex :1;
  14. }_delegateFlags;
  15. TYTabPagerBarLayout *_layout;
  16. }
  17. // UI
  18. @property (nonatomic, weak) UICollectionView *collectionView;
  19. // Data
  20. @property (nonatomic, assign) NSInteger countOfItems;
  21. @property (nonatomic, assign) NSInteger curIndex;
  22. @property (nonatomic, assign) BOOL isFirstLayout;
  23. @property (nonatomic, assign) BOOL didLayoutSubViews;
  24. @end
  25. @implementation TYTabPagerBar
  26. - (instancetype)initWithFrame:(CGRect)frame {
  27. if (self = [super initWithFrame:frame]) {
  28. _isFirstLayout = YES;
  29. _didLayoutSubViews = NO;
  30. _autoScrollItemToCenter = YES;
  31. self.backgroundColor = [UIColor clearColor];
  32. [self addFixAutoAdjustInsetScrollView];
  33. [self addCollectionView];
  34. [self addUnderLineView];
  35. }
  36. return self;
  37. }
  38. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  39. if (self = [super initWithCoder:aDecoder]) {
  40. _isFirstLayout = YES;
  41. _didLayoutSubViews = NO;
  42. _autoScrollItemToCenter = YES;
  43. self.backgroundColor = [UIColor clearColor];
  44. [self addFixAutoAdjustInsetScrollView];
  45. [self addCollectionView];
  46. [self addUnderLineView];
  47. }
  48. return self;
  49. }
  50. - (void)addFixAutoAdjustInsetScrollView {
  51. UIView *view = [[UIView alloc]init];
  52. [self addSubview:view];
  53. }
  54. - (void)addCollectionView {
  55. UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
  56. layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
  57. UICollectionView *collectionView = [[UICollectionView alloc]initWithFrame:UIEdgeInsetsInsetRect(self.bounds, _contentInset) collectionViewLayout:layout];
  58. collectionView.backgroundColor = [UIColor clearColor];
  59. collectionView.showsHorizontalScrollIndicator = NO;
  60. collectionView.showsVerticalScrollIndicator = NO;
  61. if ([collectionView respondsToSelector:@selector(setPrefetchingEnabled:)]) {
  62. collectionView.prefetchingEnabled = NO;
  63. }
  64. collectionView.delegate = self;
  65. collectionView.dataSource = self;
  66. [self addSubview:collectionView];
  67. _collectionView = collectionView;
  68. }
  69. - (void)addUnderLineView {
  70. UIView *progressView = [[UIView alloc]init];
  71. progressView.backgroundColor = [UIColor whiteColor];
  72. [_collectionView addSubview:progressView];
  73. _progressView = progressView;
  74. }
  75. #pragma mark - getter setter
  76. - (void)setProgressView:(UIView *)progressView {
  77. if (_progressView == progressView) {
  78. return;
  79. }
  80. if (_progressView) {
  81. [_progressView removeFromSuperview];
  82. }
  83. if (_layout && _layout.barStyle == TYPagerBarStyleCoverView) {
  84. progressView.layer.zPosition = -1;
  85. [_collectionView insertSubview: progressView atIndex:0];
  86. }else {
  87. [_collectionView addSubview:progressView];
  88. }
  89. if (_layout && self.superview) {
  90. [_layout layoutSubViews];
  91. }
  92. }
  93. - (void)setBackgroundView:(UIView *)backgroundView {
  94. if (_backgroundView) {
  95. [_backgroundView removeFromSuperview];
  96. }
  97. _backgroundView = backgroundView;
  98. backgroundView.frame = self.bounds;
  99. [self insertSubview:backgroundView atIndex:0];
  100. }
  101. - (void)setDelegate:(id<TYTabPagerBarDelegate>)delegate {
  102. _delegate = delegate;
  103. _delegateFlags.transitionFromeCellAnimated = [delegate respondsToSelector:@selector(pagerTabBar:transitionFromeCell:toCell:animated:)];
  104. _delegateFlags.transitionFromeCellProgress = [delegate respondsToSelector:@selector(pagerTabBar:transitionFromeCell:toCell:progress:)];
  105. _delegateFlags.widthForItemAtIndex = [delegate respondsToSelector:@selector(pagerTabBar:widthForItemAtIndex:)];
  106. }
  107. - (void)setLayout:(TYTabPagerBarLayout *)layout {
  108. BOOL updateLayout = _layout && _layout != layout;
  109. _layout = layout;
  110. if (updateLayout) {
  111. [self reloadData];
  112. }
  113. }
  114. - (TYTabPagerBarLayout *)layout {
  115. if (!_layout) {
  116. _layout = [[TYTabPagerBarLayout alloc]initWithPagerTabBar:self];
  117. }
  118. return _layout;
  119. }
  120. #pragma mark - public
  121. - (void)reloadData {
  122. _countOfItems = [_dataSource numberOfItemsInPagerTabBar];
  123. if (_curIndex >= _countOfItems) {
  124. _curIndex = _countOfItems - 1;
  125. }
  126. if ([_delegate respondsToSelector:@selector(pagerTabBar:configureLayout:)]) {
  127. [_delegate pagerTabBar:self configureLayout:self.layout];
  128. }
  129. [self.layout layoutIfNeed];
  130. [_collectionView reloadData];
  131. [self.layout adjustContentCellsCenterInBar];
  132. [self.layout layoutSubViews];
  133. }
  134. - (void)registerClass:(Class)Class forCellWithReuseIdentifier:(NSString *)identifier {
  135. [_collectionView registerClass:Class forCellWithReuseIdentifier:identifier];
  136. }
  137. - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier {
  138. [_collectionView registerNib:nib forCellWithReuseIdentifier:identifier];
  139. }
  140. - (__kindof UICollectionViewCell<TYTabPagerBarCellProtocol> *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index {
  141. UICollectionViewCell<TYTabPagerBarCellProtocol> *cell = [_collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
  142. return cell;
  143. }
  144. - (CGRect)cellFrameWithIndex:(NSInteger)index
  145. {
  146. if (index < 0) {
  147. return CGRectZero;
  148. }
  149. if (index >= _countOfItems) {
  150. return CGRectZero;
  151. }
  152. UICollectionViewLayoutAttributes * cellAttrs = [_collectionView layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
  153. if (!cellAttrs) {
  154. return CGRectZero;
  155. }
  156. return cellAttrs.frame;
  157. }
  158. - (UICollectionViewCell<TYTabPagerBarCellProtocol> *)cellForIndex:(NSInteger)index
  159. {
  160. if (index >= _countOfItems) {
  161. return nil;
  162. }
  163. return (UICollectionViewCell<TYTabPagerBarCellProtocol> *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
  164. }
  165. - (void)scrollToItemFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex animate:(BOOL)animate {
  166. if (toIndex < _countOfItems && toIndex >= 0 && fromIndex < _countOfItems && fromIndex >= 0) {
  167. _curIndex = toIndex;
  168. [self transitionFromIndex:fromIndex toIndex:toIndex animated:animate];
  169. if (_autoScrollItemToCenter) {
  170. if (!_didLayoutSubViews) {
  171. dispatch_async(dispatch_get_main_queue(), ^{
  172. [self scrollToItemAtIndex:toIndex atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:animate];
  173. });
  174. }else {
  175. [self scrollToItemAtIndex:toIndex atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:animate];
  176. }
  177. }
  178. }
  179. }
  180. - (void)scrollToItemFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex progress:(CGFloat)progress {
  181. if (toIndex < _countOfItems && toIndex >= 0 && fromIndex < _countOfItems && fromIndex >= 0) {
  182. [self transitionFromIndex:fromIndex toIndex:toIndex progress:progress];
  183. }
  184. }
  185. - (void)scrollToItemAtIndex:(NSInteger)index atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated {
  186. [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0] atScrollPosition:scrollPosition animated:animated];
  187. }
  188. - (CGFloat)cellWidthForTitle:(NSString *)title {
  189. if (!title) {
  190. return CGSizeZero.width;
  191. }
  192. //iOS 7
  193. CGRect frame = [title boundingRectWithSize:CGSizeMake(1000, 1000) options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{ NSFontAttributeName:self.layout.selectedTextFont} context:nil];
  194. return CGSizeMake(ceil(frame.size.width), ceil(frame.size.height) + 1).width;
  195. }
  196. #pragma mark - UICollectionViewDataSource
  197. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  198. {
  199. _countOfItems = [_dataSource numberOfItemsInPagerTabBar];
  200. return _countOfItems;
  201. }
  202. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  203. {
  204. UICollectionViewCell<TYTabPagerBarCellProtocol> *cell = [_dataSource pagerTabBar:self cellForItemAtIndex:indexPath.item];
  205. [self.layout transitionFromCell:(indexPath.item == _curIndex ? nil : cell) toCell:(indexPath.item == _curIndex ? cell : nil) animate:NO];
  206. return cell;
  207. }
  208. #pragma mark - UICollectionViewDelegateFlowLayout
  209. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
  210. {
  211. if ([_delegate respondsToSelector:@selector(pagerTabBar:didSelectItemAtIndex:)]) {
  212. [_delegate pagerTabBar:self didSelectItemAtIndex:indexPath.item];
  213. }
  214. }
  215. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
  216. {
  217. if (self.layout.cellWidth > 0) {
  218. return CGSizeMake(self.layout.cellWidth+self.layout.cellEdging*2, CGRectGetHeight(_collectionView.frame));
  219. }else if(_delegateFlags.widthForItemAtIndex){
  220. CGFloat width = [_delegate pagerTabBar:self widthForItemAtIndex:indexPath.item]+self.layout.cellEdging*2;
  221. return CGSizeMake(width, CGRectGetHeight(_collectionView.frame));
  222. }else {
  223. NSAssert(NO, @"you must return cell width!");
  224. }
  225. return CGSizeZero;
  226. }
  227. #pragma mark - transition cell
  228. - (void)transitionFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex animated:(BOOL)animated
  229. {
  230. UICollectionViewCell<TYTabPagerBarCellProtocol> *fromCell = [self cellForIndex:fromIndex];
  231. UICollectionViewCell<TYTabPagerBarCellProtocol> *toCell = [self cellForIndex:toIndex];
  232. if (_delegateFlags.transitionFromeCellAnimated) {
  233. [_delegate pagerTabBar:self transitionFromeCell:fromCell toCell:toCell animated:animated];
  234. }else {
  235. [self.layout transitionFromCell:fromCell toCell:toCell animate:animated];
  236. }
  237. [self.layout setUnderLineFrameWithIndex:toIndex animated:fromCell && animated ? animated: NO];
  238. }
  239. - (void)transitionFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex progress:(CGFloat)progress
  240. {
  241. UICollectionViewCell<TYTabPagerBarCellProtocol> *fromCell = [self cellForIndex:fromIndex];
  242. UICollectionViewCell<TYTabPagerBarCellProtocol> *toCell = [self cellForIndex:toIndex];
  243. if (_delegateFlags.transitionFromeCellProgress) {
  244. [_delegate pagerTabBar:self transitionFromeCell:fromCell toCell:toCell progress:progress];
  245. }else {
  246. [self.layout transitionFromCell:fromCell toCell:toCell progress:progress];
  247. }
  248. [self.layout setUnderLineFrameWithfromIndex:fromIndex toIndex:toIndex progress:progress];
  249. }
  250. -(void)layoutSubviews {
  251. [super layoutSubviews];
  252. _backgroundView.frame = self.bounds;
  253. CGRect frame = UIEdgeInsetsInsetRect(self.bounds, _contentInset);
  254. BOOL needUpdateLayout = (frame.size.height > 0 && _collectionView.frame.size.height != frame.size.height) || (frame.size.width > 0 && _collectionView.frame.size.width != frame.size.width);
  255. _collectionView.frame = frame;
  256. if (!_didLayoutSubViews && !CGRectIsEmpty(_collectionView.frame)) {
  257. _didLayoutSubViews = YES;
  258. }
  259. if (needUpdateLayout) {
  260. [_layout invalidateLayout];
  261. }
  262. if (frame.size.height > 0 && frame.size.width > 0) {
  263. [_layout adjustContentCellsCenterInBar];
  264. }
  265. _isFirstLayout = NO;
  266. [_layout layoutSubViews];
  267. }
  268. - (void)dealloc {
  269. _collectionView.dataSource = nil;
  270. _collectionView.delegate = nil;
  271. }
  272. @end