TYPagerViewLayout.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. //
  2. // TYPagerViewLayout.m
  3. // TYPagerControllerDemo
  4. //
  5. // Created by tanyang on 2017/7/9.
  6. // Copyright © 2017年 tany. All rights reserved.
  7. //
  8. #import "TYPagerViewLayout.h"
  9. #import <objc/runtime.h>
  10. @interface TYAutoPurgeCache : NSCache
  11. @end
  12. @implementation TYAutoPurgeCache
  13. - (nonnull instancetype)init {
  14. if (self = [super init]) {
  15. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  16. }
  17. return self;
  18. }
  19. - (void)dealloc {
  20. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  21. }
  22. @end
  23. static char ty_pagerReuseIdentifyKey;
  24. @implementation NSObject (TY_PagerReuseIdentify)
  25. - (NSString *)ty_pagerReuseIdentify {
  26. return objc_getAssociatedObject(self, &ty_pagerReuseIdentifyKey);
  27. }
  28. - (void)setTy_pagerReuseIdentify:(NSString *)ty_pagerReuseIdentify {
  29. objc_setAssociatedObject(self, &ty_pagerReuseIdentifyKey, ty_pagerReuseIdentify, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  30. }
  31. @end
  32. typedef NS_ENUM(NSUInteger, TYPagerScrollingDirection) {
  33. TYPagerScrollingLeft,
  34. TYPagerScrollingRight,
  35. };
  36. NS_INLINE CGRect frameForItemAtIndex(NSInteger index, CGRect frame)
  37. {
  38. return CGRectMake(index * CGRectGetWidth(frame), 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
  39. }
  40. // caculate visilble range in offset
  41. NS_INLINE NSRange visibleRangWithOffset(CGFloat offset,CGFloat width, NSInteger maxIndex)
  42. {
  43. if (width <= 0) {
  44. return NSMakeRange(0, 0);
  45. }
  46. NSInteger startIndex = offset/width;
  47. NSInteger endIndex = ceil((offset + width)/width);
  48. if (startIndex < 0) {
  49. startIndex = 0;
  50. } else if (startIndex > maxIndex) {
  51. startIndex = maxIndex;
  52. }
  53. if (endIndex > maxIndex) {
  54. endIndex = maxIndex;
  55. }
  56. NSUInteger length = endIndex - startIndex;
  57. if (length > 5) {
  58. length = 5;
  59. }
  60. return NSMakeRange(startIndex, length);
  61. }
  62. NS_INLINE NSRange prefetchRangeWithVisibleRange(NSRange visibleRange,NSInteger prefetchItemCount, NSInteger countOfPagerItems) {
  63. if (prefetchItemCount <= 0) {
  64. return NSMakeRange(0, 0);
  65. }
  66. NSInteger leftIndex = MAX((NSInteger)visibleRange.location - prefetchItemCount, 0);
  67. NSInteger rightIndex = MIN(visibleRange.location+visibleRange.length+prefetchItemCount, countOfPagerItems);
  68. return NSMakeRange(leftIndex, rightIndex - leftIndex);
  69. }
  70. static const NSInteger kMemoryCountLimit = 16;
  71. @interface TYPagerViewLayout<ItemType> ()<UIScrollViewDelegate> {
  72. // Private
  73. BOOL _needLayoutContent;
  74. BOOL _scrollAnimated;
  75. BOOL _isTapScrollMoved;
  76. CGFloat _preOffsetX;
  77. NSInteger _firstScrollToIndex;
  78. BOOL _didReloadData;
  79. BOOL _didLayoutSubViews;
  80. struct {
  81. unsigned int addVisibleItem :1;
  82. unsigned int removeInVisibleItem :1;
  83. }_dataSourceFlags;
  84. struct {
  85. unsigned int transitionFromIndexToIndex :1;
  86. unsigned int transitionFromIndexToIndexProgress :1;
  87. unsigned int pagerViewLayoutDidScroll: 1;
  88. }_delegateFlags;
  89. }
  90. // UI
  91. @property (nonatomic, strong) UIScrollView *scrollView;
  92. // Data
  93. @property (nonatomic, assign) NSInteger countOfPagerItems;
  94. @property (nonatomic, assign) NSInteger curIndex;
  95. @property (nonatomic, strong) NSCache<NSNumber *,ItemType> *memoryCache;
  96. @property (nonatomic, assign) NSRange visibleRange;
  97. @property (nonatomic, assign) NSRange prefetchRange;
  98. @property (nonatomic, strong) NSDictionary<NSNumber *,ItemType> *visibleIndexItems;
  99. @property (nonatomic, strong) NSDictionary<NSNumber *,ItemType> *prefetchIndexItems;
  100. //reuse Class and nib
  101. @property (nonatomic, strong) NSMutableDictionary *reuseIdentifyClassOrNib;
  102. // reuse items
  103. @property (nonatomic, strong) NSMutableDictionary *reuseIdentifyItems;
  104. @end
  105. static NSString * kScrollViewFrameObserverKey = @"scrollView.frame";
  106. @implementation TYPagerViewLayout
  107. #pragma mark - init
  108. - (instancetype)initWithScrollView:(UIScrollView *)scrollView {
  109. if (self = [super init]) {
  110. NSParameterAssert(scrollView!=nil);
  111. _scrollView = scrollView;
  112. [self configurePropertys];
  113. [self configureScrollView];
  114. [self addScrollViewObservers];
  115. }
  116. return self;
  117. }
  118. #pragma mark - configure
  119. - (void)configurePropertys {
  120. _curIndex = -1;
  121. _preOffsetX = 0;
  122. _changeIndexWhenScrollProgress = 0.5;
  123. _didReloadData = NO;
  124. _didLayoutSubViews = NO;
  125. _firstScrollToIndex = 0;
  126. _prefetchItemWillAddToSuperView = NO;
  127. _addVisibleItemOnlyWhenScrollAnimatedEnd = NO;
  128. _progressAnimateEnabel = YES;
  129. _adjustScrollViewInset = YES;
  130. _scrollAnimated = YES;
  131. _autoMemoryCache = YES;
  132. }
  133. - (void)configureScrollView {
  134. _scrollView.showsHorizontalScrollIndicator = NO;
  135. _scrollView.showsVerticalScrollIndicator = NO;
  136. _scrollView.pagingEnabled = YES;
  137. _scrollView.delegate = self;
  138. }
  139. - (void)resetPropertys {
  140. [self clearMemoryCache];
  141. [self removeVisibleItems];
  142. _scrollAnimated = NO;
  143. _curIndex = -1;
  144. _preOffsetX = 0;
  145. }
  146. #pragma mark - getter setter
  147. - (NSArray *)visibleItems {
  148. return _visibleIndexItems.allValues;
  149. }
  150. - (NSArray *)visibleIndexs {
  151. return _visibleIndexItems.allKeys;
  152. }
  153. - (NSMutableDictionary *)reuseIdentifyItems {
  154. if (!_reuseIdentifyItems) {
  155. _reuseIdentifyItems = [NSMutableDictionary dictionary];
  156. }
  157. return _reuseIdentifyItems;
  158. }
  159. - (NSMutableDictionary *)reuseIdentifyClassOrNib {
  160. if (!_reuseIdentifyClassOrNib) {
  161. _reuseIdentifyClassOrNib = [NSMutableDictionary dictionary];
  162. }
  163. return _reuseIdentifyClassOrNib;
  164. }
  165. - (NSCache *)memoryCache {
  166. if (!_memoryCache) {
  167. _memoryCache = [[TYAutoPurgeCache alloc]init];
  168. _memoryCache.countLimit = kMemoryCountLimit;
  169. }
  170. return _memoryCache;
  171. }
  172. - (void)setAutoMemoryCache:(BOOL)autoMemoryCache {
  173. _autoMemoryCache = autoMemoryCache;
  174. if(!_autoMemoryCache && _memoryCache){
  175. [_memoryCache removeAllObjects];
  176. _memoryCache = nil;
  177. }
  178. }
  179. - (void)setPrefetchItemCount:(NSInteger)prefetchItemCount {
  180. _prefetchItemCount = prefetchItemCount;
  181. if (prefetchItemCount <= 0 && _prefetchIndexItems) {
  182. _prefetchIndexItems = nil;
  183. }
  184. }
  185. - (void)setDataSource:(id<TYPagerViewLayoutDataSource>)dataSource {
  186. _dataSource = dataSource;
  187. _dataSourceFlags.addVisibleItem = [dataSource respondsToSelector:@selector(pagerViewLayout:addVisibleItem:atIndex:)];
  188. _dataSourceFlags.removeInVisibleItem = [dataSource respondsToSelector:@selector(pagerViewLayout:removeInVisibleItem:atIndex:)];
  189. }
  190. - (void)setDelegate:(id<TYPagerViewLayoutDelegate>)delegate {
  191. _delegate = delegate;
  192. _delegateFlags.transitionFromIndexToIndex = [delegate respondsToSelector:@selector(pagerViewLayout:transitionFromIndex:toIndex:animated:)];
  193. _delegateFlags.transitionFromIndexToIndexProgress = [delegate respondsToSelector:@selector(pagerViewLayout:transitionFromIndex:toIndex:progress:)];
  194. _delegateFlags.pagerViewLayoutDidScroll = [delegate respondsToSelector:@selector(pagerViewLayoutDidScroll:)];
  195. }
  196. #pragma mark - public
  197. - (void)reloadData {
  198. [self resetPropertys];
  199. [self updateData];
  200. }
  201. // update don't reset propertys(curIndex)
  202. - (void)updateData {
  203. [self clearMemoryCache];
  204. _didReloadData = YES;
  205. _countOfPagerItems = [_dataSource numberOfItemsInPagerViewLayout];
  206. [self setNeedLayout];
  207. }
  208. /**
  209. scroll to item at index
  210. */
  211. - (void)scrollToItemAtIndex:(NSInteger)index animate:(BOOL)animate {
  212. if (index < 0 || index >= _countOfPagerItems) {
  213. if (!_didReloadData && index >= 0) {
  214. _firstScrollToIndex = index;
  215. }
  216. return;
  217. }
  218. if (!_didLayoutSubViews && CGRectIsEmpty(_scrollView.frame)) {
  219. _firstScrollToIndex = index;
  220. }
  221. [self scrollViewWillScrollToView:_scrollView animate:animate];
  222. [_scrollView setContentOffset:CGPointMake(index * CGRectGetWidth(_scrollView.frame),0) animated:NO];
  223. [self scrollViewDidScrollToView:_scrollView animate:animate];
  224. }
  225. - (id)itemForIndex:(NSInteger)idx {
  226. NSNumber *index = @(idx);
  227. // 1.from visibleViews
  228. id visibleItem = [_visibleIndexItems objectForKey:index];
  229. if (!visibleItem && _prefetchItemCount > 0) {
  230. // 2.from prefetch
  231. visibleItem = [_prefetchIndexItems objectForKey:index];
  232. }
  233. if (!visibleItem) {
  234. // 3.from cache
  235. visibleItem = [self cacheItemForKey:index];
  236. }
  237. return visibleItem;
  238. }
  239. - (UIView *)viewForItem:(id)item atIndex:(NSInteger)index {
  240. UIView *view = [_dataSource pagerViewLayout:self viewForItem:item atIndex:index];
  241. return view;
  242. }
  243. - (UIViewController *)viewControllerForItem:(id)item atIndex:(NSInteger)index {
  244. if ([_dataSource respondsToSelector:@selector(pagerViewLayout:viewControllerForItem:atIndex:)]) {
  245. return [_dataSource pagerViewLayout:self viewControllerForItem:item atIndex:index];
  246. }
  247. return nil;
  248. }
  249. - (CGRect)frameForItemAtIndex:(NSInteger)index {
  250. CGRect frame = frameForItemAtIndex(index, _scrollView.frame);
  251. if (_adjustScrollViewInset) {
  252. frame.size.height -= _scrollView.contentInset.top;
  253. }
  254. return frame;
  255. }
  256. #pragma mark - register && dequeue
  257. - (void)registerClass:(Class)Class forItemWithReuseIdentifier:(NSString *)identifier {
  258. [self.reuseIdentifyClassOrNib setObject:Class forKey:identifier];
  259. }
  260. - (void)registerNib:(UINib *)nib forItemWithReuseIdentifier:(NSString *)identifier {
  261. [self.reuseIdentifyClassOrNib setObject:nib forKey:identifier];
  262. }
  263. - (id)dequeueReusableItemWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index {
  264. NSAssert(_reuseIdentifyClassOrNib.count != 0, @"you don't register any identifiers!");
  265. NSObject *item = [self.reuseIdentifyItems objectForKey:identifier];
  266. if (item) {
  267. [self.reuseIdentifyItems removeObjectForKey:identifier];
  268. return item;
  269. }
  270. id itemClassOrNib = [self.reuseIdentifyClassOrNib objectForKey:identifier];
  271. if (!itemClassOrNib) {
  272. NSString *error = [NSString stringWithFormat:@"you don't register this identifier->%@",identifier];
  273. NSAssert(NO, error);
  274. NSLog(@"%@", error);
  275. return nil;
  276. }
  277. if (class_isMetaClass(object_getClass(itemClassOrNib))) {
  278. // is class
  279. item = [[((Class)itemClassOrNib) alloc]init];
  280. }else if ([itemClassOrNib isKindOfClass:[UINib class]]) {
  281. // is nib
  282. item =[((UINib *)itemClassOrNib)instantiateWithOwner:nil options:nil].firstObject;
  283. }
  284. if (!item){
  285. NSString *error = [NSString stringWithFormat:@"you register identifier->%@ is not class or nib!",identifier];
  286. NSAssert(NO, error);
  287. NSLog(@"%@", error);
  288. return nil;
  289. }
  290. [item setTy_pagerReuseIdentify:identifier];
  291. UIView *view = [_dataSource pagerViewLayout:self viewForItem:item atIndex:index];
  292. view.frame = [self frameForItemAtIndex:index];
  293. return item;
  294. }
  295. - (void)enqueueReusableItem:(NSObject *)reuseItem prefetchRange:(NSRange)prefetchRange atIndex:(NSInteger)index{
  296. if (reuseItem.ty_pagerReuseIdentify.length == 0 || NSLocationInRange(index, prefetchRange)) {
  297. return;
  298. }
  299. [self.reuseIdentifyItems setObject:reuseItem forKey:reuseItem.ty_pagerReuseIdentify];
  300. }
  301. #pragma mark - layout content
  302. - (void)setNeedLayout {
  303. // 1. get count Of pager Items
  304. if (_countOfPagerItems <= 0) {
  305. _countOfPagerItems = [_dataSource numberOfItemsInPagerViewLayout];
  306. }
  307. _needLayoutContent = YES;
  308. if (_curIndex >= _countOfPagerItems) {
  309. _curIndex = _countOfPagerItems - 1;
  310. }
  311. BOOL needLayoutSubViews = NO;
  312. if (!_didLayoutSubViews && !CGRectIsEmpty(_scrollView.frame) && _firstScrollToIndex < _countOfPagerItems) {
  313. _didLayoutSubViews = YES;
  314. needLayoutSubViews = YES;
  315. }
  316. // 2.set contentSize and offset
  317. CGFloat contentWidth = CGRectGetWidth(_scrollView.frame);
  318. _scrollView.contentSize = CGSizeMake(_countOfPagerItems * contentWidth, 0);
  319. _scrollView.contentOffset = CGPointMake(MAX(needLayoutSubViews ? _firstScrollToIndex : _curIndex, 0)*contentWidth, _scrollView.contentOffset.y);
  320. // 3.layout content
  321. if (_curIndex < 0 || needLayoutSubViews) {
  322. [self scrollViewDidScroll:_scrollView];
  323. }else {
  324. [self layoutIfNeed];
  325. }
  326. }
  327. - (void)layoutIfNeed {
  328. if (CGRectIsEmpty(_scrollView.frame)) {
  329. return;
  330. }
  331. // 1.caculate visible range
  332. CGFloat offsetX = _scrollView.contentOffset.x;
  333. NSRange visibleRange = visibleRangWithOffset(offsetX, CGRectGetWidth(_scrollView.frame), _countOfPagerItems);
  334. if (NSEqualRanges(_visibleRange, visibleRange) && !_needLayoutContent) {
  335. // visible range not change
  336. return;
  337. }
  338. _visibleRange = visibleRange;
  339. _needLayoutContent = NO;
  340. BOOL afterPrefetchIfNoVisibleItems = !_visibleIndexItems;
  341. if (!afterPrefetchIfNoVisibleItems) {
  342. // 2.prefetch left and right Items
  343. [self addPrefetchItemsOutOfVisibleRange:_visibleRange];
  344. }
  345. // 3.remove invisible Items
  346. [self removeVisibleItemsOutOfVisibleRange:_visibleRange];
  347. // 4.add visiible Items
  348. [self addVisibleItemsInVisibleRange:_visibleRange];
  349. if (afterPrefetchIfNoVisibleItems) {
  350. [self addPrefetchItemsOutOfVisibleRange:_visibleRange];
  351. }
  352. }
  353. #pragma mark - remove && add visibleViews
  354. - (void)removeVisibleItems {
  355. [_scrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
  356. _visibleIndexItems = nil;
  357. _prefetchIndexItems = nil;
  358. if (_reuseIdentifyItems) {
  359. [_reuseIdentifyItems removeAllObjects];
  360. }
  361. }
  362. - (void)removeVisibleItemsOutOfVisibleRange:(NSRange)visibleRange {
  363. [_visibleIndexItems enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id item, BOOL * stop) {
  364. NSInteger index = [key integerValue];
  365. if (!NSLocationInRange(index, visibleRange)) {
  366. // out of visible
  367. [self removeInvisibleItem:item atIndex:index];
  368. }
  369. }];
  370. }
  371. - (void)removeInvisibleItem:(id)invisibleItem atIndex:(NSInteger)index{
  372. UIView *invisibleView = [self viewForItem:invisibleItem atIndex:index];
  373. if (!invisibleView.superview) {
  374. return;
  375. }
  376. if (_dataSourceFlags.removeInVisibleItem) {
  377. [_dataSource pagerViewLayout:self removeInVisibleItem:invisibleItem atIndex:index];
  378. }else {
  379. NSAssert(NO, @"must implememt datasource pagerViewLayout:removeInVisibleItem:atIndex:!");
  380. }
  381. NSObject *reuseItem = invisibleItem;
  382. if (_reuseIdentifyClassOrNib.count > 0 && reuseItem.ty_pagerReuseIdentify.length > 0) {
  383. // reuse
  384. [self enqueueReusableItem:reuseItem prefetchRange:_prefetchRange atIndex:index];
  385. }else {
  386. [self cacheItem:invisibleItem forKey:@(index)];
  387. }
  388. }
  389. - (void)addVisibleItemsInVisibleRange:(NSRange)visibleRange {
  390. NSMutableDictionary *visibleIndexItems = [NSMutableDictionary dictionary];
  391. // add visible views
  392. for (NSInteger idx = visibleRange.location ; idx < visibleRange.location + visibleRange.length; ++idx) {
  393. // from visibleViews,prefetch,cache
  394. id visibleItem = [self itemForIndex:idx];
  395. if (!visibleItem && (!_addVisibleItemOnlyWhenScrollAnimatedEnd || visibleRange.length == 1)) {
  396. // ↑↑↑ if _addVisibleItemOnlyWhenScrollAnimatedEnd is NO ,scroll visible range change will add item from dataSource, else is YES only scroll animate end(visibleRange.length == 1) will add item from dataSource
  397. visibleItem = [_dataSource pagerViewLayout:self itemForIndex:idx prefetching:NO];
  398. }
  399. if (visibleItem) {
  400. [self addVisibleItem:visibleItem atIndex:idx];
  401. visibleIndexItems[@(idx)] = visibleItem;
  402. }
  403. }
  404. if (visibleIndexItems.count > 0) {
  405. _visibleIndexItems = [visibleIndexItems copy];
  406. }else {
  407. _visibleIndexItems = nil;
  408. }
  409. }
  410. - (void)addVisibleItem:(id)visibleItem atIndex:(NSInteger)index{
  411. if (!visibleItem) {
  412. NSAssert(visibleItem != nil, @"visibleView must not nil!");
  413. return;
  414. }
  415. UIView *view = [self viewForItem:visibleItem atIndex:index];
  416. if (view.superview && view.superview != _scrollView) {
  417. [view removeFromSuperview];
  418. }
  419. CGRect frame = [self frameForItemAtIndex:index];
  420. if (!CGRectEqualToRect(view.frame, frame)) {
  421. view.frame = frame;
  422. }
  423. if (!_prefetchItemWillAddToSuperView && view.superview) {
  424. return;
  425. }
  426. if (_prefetchItemWillAddToSuperView && view.superview) {
  427. UIViewController *viewController = [self viewControllerForItem:visibleItem atIndex:index];
  428. if (!viewController || viewController.parentViewController) {
  429. return;
  430. }
  431. }
  432. if (_dataSourceFlags.addVisibleItem) {
  433. [_dataSource pagerViewLayout:self addVisibleItem:visibleItem atIndex:index];
  434. }else {
  435. NSAssert(NO, @"must implement datasource pagerViewLayout:addVisibleItem:frame:atIndex:!");
  436. }
  437. }
  438. - (void)addPrefetchItemsOutOfVisibleRange:(NSRange)visibleRange{
  439. if (_prefetchItemCount <= 0) {
  440. return;
  441. }
  442. NSRange prefetchRange = prefetchRangeWithVisibleRange(visibleRange, _prefetchItemCount, _countOfPagerItems);
  443. if (visibleRange.length == 1) {
  444. // ↑↑↑mean: scroll animate end
  445. NSMutableDictionary *prefetchIndexItems = [NSMutableDictionary dictionary];
  446. // add prefetch items
  447. for (NSInteger index = prefetchRange.location; index < NSMaxRange(prefetchRange); ++index) {
  448. id prefetchItem = nil;
  449. if (NSLocationInRange(index, visibleRange)) {
  450. prefetchItem = [_visibleIndexItems objectForKey:@(index)];
  451. }else {
  452. prefetchItem = [self prefetchInvisibleItemAtIndex:index];
  453. }
  454. if (prefetchItem) {
  455. [prefetchIndexItems setObject:prefetchItem forKey:@(index)];
  456. }
  457. }
  458. BOOL haveReuseIdentifyClassOrNib = _reuseIdentifyClassOrNib.count > 0;
  459. if (haveReuseIdentifyClassOrNib || _prefetchItemWillAddToSuperView) {
  460. [_prefetchIndexItems enumerateKeysAndObjectsUsingBlock:^(NSNumber * key, id obj, BOOL * stop) {
  461. NSInteger index = [key integerValue];
  462. if (haveReuseIdentifyClassOrNib) {
  463. // resuse item
  464. [self enqueueReusableItem:obj prefetchRange:prefetchRange atIndex:index];
  465. }
  466. if (_prefetchItemWillAddToSuperView && !NSLocationInRange(index, prefetchRange)) {
  467. // remove prefetch item to superView
  468. UIView *view = [self viewForItem:obj atIndex:index];
  469. if (view.superview == _scrollView && ![_visibleIndexItems objectForKey:key]) {
  470. [view removeFromSuperview];
  471. }
  472. }
  473. }];
  474. }
  475. if (prefetchIndexItems.count > 0) {
  476. _prefetchRange = prefetchRange;
  477. _prefetchIndexItems = [prefetchIndexItems copy];
  478. }else {
  479. _prefetchRange = NSMakeRange(0, 0);
  480. _prefetchIndexItems = nil;
  481. }
  482. }else if (NSIntersectionRange(visibleRange, _prefetchRange).length == 0) {
  483. // visible and prefetch intersection, remove all prefetchItems
  484. if (_prefetchItemWillAddToSuperView) {
  485. [_prefetchIndexItems enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {
  486. UIView *view = [self viewForItem:obj atIndex:[key integerValue]];
  487. if (view.superview == _scrollView && ![_visibleIndexItems objectForKey:key]) {
  488. [view removeFromSuperview];
  489. }
  490. }];
  491. }
  492. _prefetchRange = NSMakeRange(0, 0);
  493. _prefetchIndexItems = nil;
  494. }
  495. }
  496. - (UIView *)prefetchInvisibleItemAtIndex:(NSInteger)index {
  497. id prefetchItem = [_prefetchIndexItems objectForKey:@(index)];
  498. if (!prefetchItem) {
  499. prefetchItem = [_visibleIndexItems objectForKey:@(index)];
  500. }
  501. if (!prefetchItem) {
  502. prefetchItem = [self cacheItemForKey:@(index)];
  503. }
  504. if (!prefetchItem) {
  505. prefetchItem = [_dataSource pagerViewLayout:self itemForIndex:index prefetching:YES];
  506. UIView *view = [self viewForItem:prefetchItem atIndex:index];
  507. CGRect frame = [self frameForItemAtIndex:index];
  508. if (!CGRectEqualToRect(view.frame, frame)) {
  509. view.frame = frame;
  510. }
  511. if (_prefetchItemWillAddToSuperView && view.superview != _scrollView) {
  512. [_scrollView addSubview:view];
  513. }
  514. }
  515. return prefetchItem;
  516. }
  517. #pragma mark - caculate index
  518. - (void)caculateIndexWithOffsetX:(CGFloat)offsetX direction:(TYPagerScrollingDirection)direction{
  519. if (CGRectIsEmpty(_scrollView.frame)) {
  520. return;
  521. }
  522. if (_countOfPagerItems <= 0) {
  523. _curIndex = -1;
  524. return;
  525. }
  526. // scrollView width
  527. CGFloat width = CGRectGetWidth(_scrollView.frame);
  528. NSInteger index = 0;
  529. // when scroll to progress(changeIndexWhenScrollProgress) will change index
  530. double percentChangeIndex = _changeIndexWhenScrollProgress;
  531. if (_changeIndexWhenScrollProgress >= 1.0 || [self progressCaculateEnable]) {
  532. percentChangeIndex = 0.999999999;
  533. }
  534. // caculate cur index
  535. if (direction == TYPagerScrollingLeft) {
  536. index = ceil(offsetX/width-percentChangeIndex);
  537. }else {
  538. index = floor(offsetX/width+percentChangeIndex);
  539. }
  540. if (index < 0) {
  541. index = 0;
  542. }else if (index >= _countOfPagerItems) {
  543. index = _countOfPagerItems-1;
  544. }
  545. if (index == _curIndex) {
  546. // if index not same,change index
  547. return;
  548. }
  549. NSInteger fromIndex = MAX(_curIndex, 0);
  550. _curIndex = index;
  551. if (_delegateFlags.transitionFromIndexToIndex /*&& ![self progressCaculateEnable]*/) {
  552. [_delegate pagerViewLayout:self transitionFromIndex:fromIndex toIndex:_curIndex animated:_scrollAnimated];
  553. }
  554. _scrollAnimated = YES;
  555. }
  556. - (void)caculateIndexByProgressWithOffsetX:(CGFloat)offsetX direction:(TYPagerScrollingDirection)direction{
  557. if (CGRectIsEmpty(_scrollView.frame)) {
  558. return;
  559. }
  560. if (_countOfPagerItems <= 0) {
  561. _curIndex = -1;
  562. return;
  563. }
  564. CGFloat width = CGRectGetWidth(_scrollView.frame);
  565. CGFloat floadIndex = offsetX/width;
  566. NSInteger floorIndex = floor(floadIndex);
  567. if (floorIndex < 0 || floorIndex >= _countOfPagerItems || floadIndex > _countOfPagerItems-1) {
  568. return;
  569. }
  570. CGFloat progress = offsetX/width-floorIndex;
  571. NSInteger fromIndex = 0, toIndex = 0;
  572. if (direction == TYPagerScrollingLeft) {
  573. fromIndex = floorIndex;
  574. toIndex = MIN(_countOfPagerItems -1, fromIndex + 1);
  575. if (fromIndex == toIndex && toIndex == _countOfPagerItems-1) {
  576. fromIndex = _countOfPagerItems-2;
  577. progress = 1.0;
  578. }
  579. }else {
  580. toIndex = floorIndex;
  581. fromIndex = MIN(_countOfPagerItems-1, toIndex +1);
  582. progress = 1.0 - progress;
  583. }
  584. if (_delegateFlags.transitionFromIndexToIndexProgress) {
  585. [_delegate pagerViewLayout:self transitionFromIndex:fromIndex toIndex:toIndex progress:progress];
  586. }
  587. }
  588. - (BOOL)progressCaculateEnable {
  589. return _delegateFlags.transitionFromIndexToIndexProgress && _progressAnimateEnabel && !_isTapScrollMoved;
  590. }
  591. #pragma mark - memoryCache
  592. - (void)clearMemoryCache {
  593. if (_autoMemoryCache && _memoryCache) {
  594. [_memoryCache removeAllObjects];
  595. }
  596. }
  597. - (void)cacheItem:(id)item forKey:(NSNumber *)key {
  598. if (_autoMemoryCache && key) {
  599. UIView *cacheItem = [self.memoryCache objectForKey:key];
  600. if (cacheItem && cacheItem == item) {
  601. return;
  602. }
  603. [self.memoryCache setObject:item forKey:key];
  604. }
  605. }
  606. - (id)cacheItemForKey:(NSNumber *)key {
  607. if (_autoMemoryCache && _memoryCache && key) {
  608. return [_memoryCache objectForKey:key];
  609. }
  610. return nil;
  611. }
  612. #pragma mark - UIScrollViewDelegate
  613. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  614. if (!scrollView.superview) {
  615. return;
  616. }
  617. // get scrolling direction
  618. CGFloat offsetX = scrollView.contentOffset.x;
  619. TYPagerScrollingDirection direction = offsetX >= _preOffsetX ? TYPagerScrollingLeft : TYPagerScrollingRight;
  620. // caculate index and progress
  621. if ([self progressCaculateEnable]) {
  622. [self caculateIndexByProgressWithOffsetX:offsetX direction:direction];
  623. }
  624. [self caculateIndexWithOffsetX:offsetX direction:direction];
  625. // layout items
  626. [self layoutIfNeed];
  627. _isTapScrollMoved = NO;
  628. if (_delegateFlags.pagerViewLayoutDidScroll) {
  629. [_delegate pagerViewLayoutDidScroll:self];
  630. }
  631. }
  632. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
  633. {
  634. _preOffsetX = scrollView.contentOffset.x;
  635. if ([_delegate respondsToSelector:@selector(pagerViewLayoutWillBeginDragging:)]) {
  636. [_delegate pagerViewLayoutWillBeginDragging:self];
  637. }
  638. }
  639. - (void)scrollViewWillScrollToView:(UIScrollView *)scrollView animate:(BOOL)animate {
  640. _preOffsetX = scrollView.contentOffset.x;
  641. _isTapScrollMoved = YES;
  642. _scrollAnimated = animate;
  643. if ([_delegate respondsToSelector:@selector(pagerViewLayoutWillBeginScrollToView:animate:)]) {
  644. [_delegate pagerViewLayoutWillBeginScrollToView:self animate:animate];
  645. }
  646. }
  647. - (void)scrollViewDidScrollToView:(UIScrollView *)scrollView animate:(BOOL)animate {
  648. if ([_delegate respondsToSelector:@selector(pagerViewLayoutDidEndScrollToView:animate:)]) {
  649. [_delegate pagerViewLayoutDidEndScrollToView:self animate:animate];
  650. }
  651. }
  652. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  653. if ([_delegate respondsToSelector:@selector(pagerViewLayoutDidEndDragging:willDecelerate:)]) {
  654. [_delegate pagerViewLayoutDidEndDragging:self willDecelerate:decelerate];
  655. }
  656. }
  657. - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
  658. if ([_delegate respondsToSelector:@selector(pagerViewLayoutWillBeginDecelerating:)]) {
  659. [_delegate pagerViewLayoutWillBeginDecelerating:self];
  660. }
  661. }
  662. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  663. if ([_delegate respondsToSelector:@selector(pagerViewLayoutDidEndDecelerating:)]) {
  664. [_delegate pagerViewLayoutDidEndDecelerating:self];
  665. }
  666. }
  667. - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
  668. if ([_delegate respondsToSelector:@selector(pagerViewLayoutDidEndScrollingAnimation:)]) {
  669. [_delegate pagerViewLayoutDidEndScrollingAnimation:self];
  670. }
  671. }
  672. #pragma mark - Observer
  673. - (void)addScrollViewObservers {
  674. [self addObserver:self forKeyPath:kScrollViewFrameObserverKey options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
  675. }
  676. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
  677. if ([keyPath isEqualToString:kScrollViewFrameObserverKey]) {
  678. CGRect newFrame = [[change objectForKey:NSKeyValueChangeNewKey]CGRectValue];
  679. CGRect oldFrame = [[change objectForKey:NSKeyValueChangeOldKey]CGRectValue];
  680. BOOL needLayoutContent = !CGRectEqualToRect(newFrame, oldFrame);
  681. if (needLayoutContent) {
  682. [self setNeedLayout];
  683. }
  684. }
  685. }
  686. - (void)removeScrollViewObservers {
  687. [self removeObserver:self forKeyPath:kScrollViewFrameObserverKey context:nil];
  688. }
  689. - (void)dealloc {
  690. [self removeScrollViewObservers];
  691. _scrollView.delegate = nil;
  692. _scrollView = nil;
  693. if (_reuseIdentifyItems) {
  694. [_reuseIdentifyItems removeAllObjects];
  695. }
  696. if (_reuseIdentifyClassOrNib) {
  697. [_reuseIdentifyClassOrNib removeAllObjects];
  698. }
  699. [self clearMemoryCache];
  700. }
  701. @end