ASPagerNode.m 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. //
  2. // ASPagerNode.m
  3. // AsyncDisplayKit
  4. //
  5. // Created by Levi McCallum on 12/7/15.
  6. //
  7. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  8. // This source code is licensed under the BSD-style license found in the
  9. // LICENSE file in the root directory of this source tree. An additional grant
  10. // of patent rights can be found in the PATENTS file in the same directory.
  11. //
  12. #import <AsyncDisplayKit/ASPagerNode.h>
  13. #import <AsyncDisplayKit/ASDelegateProxy.h>
  14. #import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
  15. #import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
  16. #import <AsyncDisplayKit/ASPagerFlowLayout.h>
  17. #import <AsyncDisplayKit/ASAssert.h>
  18. #import <AsyncDisplayKit/ASCellNode.h>
  19. #import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
  20. #import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h>
  21. @interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionDelegateFlowLayout, ASDelegateProxyInterceptor>
  22. {
  23. ASPagerFlowLayout *_flowLayout;
  24. __weak id <ASPagerDataSource> _pagerDataSource;
  25. ASPagerNodeProxy *_proxyDataSource;
  26. struct {
  27. unsigned nodeBlockAtIndex:1;
  28. unsigned nodeAtIndex:1;
  29. } _pagerDataSourceFlags;
  30. __weak id <ASPagerDelegate> _pagerDelegate;
  31. struct {
  32. unsigned constrainedSizeForNode:1;
  33. } _pagerDelegateFlags;
  34. ASPagerNodeProxy *_proxyDelegate;
  35. }
  36. @end
  37. @implementation ASPagerNode
  38. @dynamic view, delegate, dataSource;
  39. #pragma mark - Lifecycle
  40. - (instancetype)init
  41. {
  42. ASPagerFlowLayout *flowLayout = [[ASPagerFlowLayout alloc] init];
  43. flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
  44. flowLayout.minimumInteritemSpacing = 0;
  45. flowLayout.minimumLineSpacing = 0;
  46. return [self initWithCollectionViewLayout:flowLayout];
  47. }
  48. - (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout;
  49. {
  50. ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout.");
  51. self = [super initWithCollectionViewLayout:flowLayout];
  52. if (self != nil) {
  53. _flowLayout = flowLayout;
  54. }
  55. return self;
  56. }
  57. #pragma mark - ASDisplayNode
  58. - (void)didLoad
  59. {
  60. [super didLoad];
  61. ASCollectionView *cv = self.view;
  62. cv.asyncDataSource = (id<ASCollectionDataSource>)_proxyDataSource ?: self;
  63. cv.asyncDelegate = (id<ASCollectionDelegate>)_proxyDelegate ?: self;
  64. #if TARGET_OS_IOS
  65. cv.pagingEnabled = YES;
  66. cv.scrollsToTop = NO;
  67. #endif
  68. cv.allowsSelection = NO;
  69. cv.showsVerticalScrollIndicator = NO;
  70. cv.showsHorizontalScrollIndicator = NO;
  71. ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 };
  72. ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 };
  73. [self setTuningParameters:minimumRenderParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay];
  74. [self setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload];
  75. ASRangeTuningParameters fullRenderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 };
  76. ASRangeTuningParameters fullPreloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 };
  77. [self setTuningParameters:fullRenderParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay];
  78. [self setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypePreload];
  79. }
  80. #pragma mark - Getters / Setters
  81. - (NSInteger)currentPageIndex
  82. {
  83. return (self.view.contentOffset.x / CGRectGetWidth(self.view.bounds));
  84. }
  85. #pragma mark - Helpers
  86. - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated
  87. {
  88. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
  89. [self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated];
  90. }
  91. - (ASCellNode *)nodeForPageAtIndex:(NSInteger)index
  92. {
  93. return [self nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
  94. }
  95. - (NSInteger)indexOfPageWithNode:(ASCellNode *)node
  96. {
  97. NSIndexPath *indexPath = [self indexPathForNode:node];
  98. if (!indexPath) {
  99. return NSNotFound;
  100. }
  101. return indexPath.row;
  102. }
  103. #pragma mark - ASCollectionDataSource
  104. - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
  105. {
  106. if (_pagerDataSourceFlags.nodeBlockAtIndex) {
  107. return [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item];
  108. } else if (_pagerDataSourceFlags.nodeAtIndex) {
  109. ASCellNode *node = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item];
  110. return ^{ return node; };
  111. } else {
  112. ASDisplayNodeFailAssert(@"Pager data source must implement either %@ or %@. Data source: %@", NSStringFromSelector(@selector(pagerNode:nodeBlockAtIndex:)), NSStringFromSelector(@selector(pagerNode:nodeAtIndex:)), _pagerDataSource);
  113. return ^{
  114. return [[ASCellNode alloc] init];
  115. };
  116. }
  117. }
  118. - (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section
  119. {
  120. ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display");
  121. return [_pagerDataSource numberOfPagesInPagerNode:self];
  122. }
  123. #pragma mark - ASCollectionDelegate
  124. - (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath
  125. {
  126. #pragma clang diagnostic push
  127. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  128. if (_pagerDelegateFlags.constrainedSizeForNode) {
  129. return [_pagerDelegate pagerNode:self constrainedSizeForNodeAtIndex:indexPath.item];
  130. }
  131. #pragma clang diagnostic pop
  132. return ASSizeRangeMake(self.bounds.size);
  133. }
  134. #pragma mark - Data Source Proxy
  135. - (id <ASPagerDataSource>)dataSource
  136. {
  137. return _pagerDataSource;
  138. }
  139. - (void)setDataSource:(id <ASPagerDataSource>)dataSource
  140. {
  141. if (dataSource != _pagerDataSource) {
  142. _pagerDataSource = dataSource;
  143. if (dataSource == nil) {
  144. memset(&_pagerDataSourceFlags, 0, sizeof(_pagerDataSourceFlags));
  145. } else {
  146. _pagerDataSourceFlags.nodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)];
  147. _pagerDataSourceFlags.nodeAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)];
  148. }
  149. _proxyDataSource = dataSource ? [[ASPagerNodeProxy alloc] initWithTarget:dataSource interceptor:self] : nil;
  150. super.dataSource = (id <ASCollectionDataSource>)_proxyDataSource;
  151. }
  152. }
  153. - (void)setDelegate:(id<ASPagerDelegate>)delegate
  154. {
  155. if (delegate != _pagerDelegate) {
  156. _pagerDelegate = delegate;
  157. if (delegate == nil) {
  158. memset(&_pagerDelegateFlags, 0, sizeof(_pagerDelegateFlags));
  159. } else {
  160. _pagerDelegateFlags.constrainedSizeForNode = [_pagerDelegate respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndex:)];
  161. }
  162. _proxyDelegate = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil;
  163. super.delegate = (id <ASCollectionDelegate>)_proxyDelegate;
  164. }
  165. }
  166. - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy
  167. {
  168. [self setDataSource:nil];
  169. [self setDelegate:nil];
  170. }
  171. - (void)didEnterVisibleState
  172. {
  173. [super didEnterVisibleState];
  174. // Check that our view controller does not automatically set our content insets
  175. // It would be better to have a -didEnterHierarchy hook to put this in, but
  176. // such a hook doesn't currently exist, and in every use case I can imagine,
  177. // the pager is not hosted inside a range-managed node.
  178. if (_allowsAutomaticInsetsAdjustment == NO) {
  179. UIViewController *vc = [self.view asdk_associatedViewController];
  180. if (vc.automaticallyAdjustsScrollViewInsets) {
  181. NSLog(@"AsyncDisplayKit: ASPagerNode is setting automaticallyAdjustsScrollViewInsets=NO on its owning view controller %@. This automatic behavior will be disabled in the future. Set allowsAutomaticInsetsAdjustment=YES on the pager node to suppress this behavior.", vc);
  182. vc.automaticallyAdjustsScrollViewInsets = NO;
  183. }
  184. }
  185. }
  186. @end