ASCollectionView.mm 82 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943
  1. //
  2. // ASCollectionView.mm
  3. // AsyncDisplayKit
  4. //
  5. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  6. // This source code is licensed under the BSD-style license found in the
  7. // LICENSE file in the root directory of this source tree. An additional grant
  8. // of patent rights can be found in the PATENTS file in the same directory.
  9. //
  10. #import <AsyncDisplayKit/ASAssert.h>
  11. #import <AsyncDisplayKit/ASAvailability.h>
  12. #import <AsyncDisplayKit/ASBatchFetching.h>
  13. #import <AsyncDisplayKit/ASDelegateProxy.h>
  14. #import <AsyncDisplayKit/ASCellNode+Internal.h>
  15. #import <AsyncDisplayKit/ASCollectionDataController.h>
  16. #import <AsyncDisplayKit/ASCollectionInternal.h>
  17. #import <AsyncDisplayKit/ASCollectionViewLayoutController.h>
  18. #import <AsyncDisplayKit/ASCollectionViewFlowLayoutInspector.h>
  19. #import <AsyncDisplayKit/ASDisplayNodeExtras.h>
  20. #import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
  21. #import <AsyncDisplayKit/ASInternalHelpers.h>
  22. #import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
  23. #import <AsyncDisplayKit/ASRangeController.h>
  24. #import <AsyncDisplayKit/ASCollectionNode.h>
  25. #import <AsyncDisplayKit/_ASCollectionViewCell.h>
  26. #import <AsyncDisplayKit/_ASDisplayLayer.h>
  27. #import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h>
  28. #import <AsyncDisplayKit/ASPagerNode.h>
  29. #import <AsyncDisplayKit/ASSectionContext.h>
  30. #import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
  31. #import <AsyncDisplayKit/_ASHierarchyChangeSet.h>
  32. /**
  33. * A macro to get self.collectionNode and assign it to a local variable, or return
  34. * the given value if nil.
  35. *
  36. * Previously we would set ASCollectionNode's dataSource & delegate to nil
  37. * during dealloc. However, our asyncDelegate & asyncDataSource must be set on the
  38. * main thread, so if the node is deallocated off-main, we won't learn about the change
  39. * until later on. Since our @c collectionNode parameter to delegate methods (e.g.
  40. * collectionNode:didEndDisplayingItemWithNode:) is nonnull, it's important that we never
  41. * unintentionally pass nil (this will crash in Swift, in production). So we can use
  42. * this macro to ensure that our node is still alive before calling out to the user
  43. * on its behalf.
  44. */
  45. #define GET_COLLECTIONNODE_OR_RETURN(__var, __val) \
  46. ASCollectionNode *__var = self.collectionNode; \
  47. if (__var == nil) { \
  48. return __val; \
  49. }
  50. /// What, if any, invalidation should we perform during the next -layoutSubviews.
  51. typedef NS_ENUM(NSUInteger, ASCollectionViewInvalidationStyle) {
  52. /// Perform no invalidation.
  53. ASCollectionViewInvalidationStyleNone,
  54. /// Perform invalidation with animation (use an empty batch update).
  55. ASCollectionViewInvalidationStyleWithoutAnimation,
  56. /// Perform invalidation without animation (use -invalidateLayout).
  57. ASCollectionViewInvalidationStyleWithAnimation,
  58. };
  59. static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone;
  60. /// Used for all cells and supplementaries. UICV keys by supp-kind+reuseID so this is plenty.
  61. static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
  62. #pragma mark -
  63. #pragma mark ASCollectionView.
  64. @interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASCollectionDataControllerSource, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASDataControllerEnvironmentDelegate, ASCALayerExtendedDelegate, UICollectionViewDelegateFlowLayout> {
  65. ASCollectionViewProxy *_proxyDataSource;
  66. ASCollectionViewProxy *_proxyDelegate;
  67. ASCollectionDataController *_dataController;
  68. ASRangeController *_rangeController;
  69. ASCollectionViewLayoutController *_layoutController;
  70. id<ASCollectionViewLayoutInspecting> _defaultLayoutInspector;
  71. __weak id<ASCollectionViewLayoutInspecting> _layoutInspector;
  72. NSMutableSet *_cellsForVisibilityUpdates;
  73. id<ASCollectionViewLayoutFacilitatorProtocol> _layoutFacilitator;
  74. BOOL _performingBatchUpdates;
  75. NSUInteger _superBatchUpdateCount;
  76. NSMutableArray *_batchUpdateBlocks;
  77. BOOL _isDeallocating;
  78. ASBatchContext *_batchContext;
  79. CGSize _lastBoundsSizeUsedForMeasuringNodes;
  80. NSMutableSet *_registeredSupplementaryKinds;
  81. CGPoint _deceleratingVelocity;
  82. BOOL _zeroContentInsets;
  83. ASCollectionViewInvalidationStyle _nextLayoutInvalidationStyle;
  84. /**
  85. * Our layer, retained. Under iOS < 9, when collection views are removed from the hierarchy,
  86. * their layers may be deallocated and become dangling pointers. This puts the collection view
  87. * into a very dangerous state where pretty much any call will crash it. So we manually retain our layer.
  88. *
  89. * You should never access this, and it will be nil under iOS >= 9.
  90. */
  91. CALayer *_retainedLayer;
  92. /**
  93. * If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it.
  94. * Rationale:
  95. * In `reloadData`, a collection view invalidates its data and marks itself as needing reload, and waits until `layoutSubviews` to requery its data source.
  96. * This can lead to data inconsistency problems.
  97. * Say you have an empty collection view. You call `reloadData`, then immediately insert an item into your data source and call `insertItemsAtIndexPaths:[0,0]`.
  98. * You will get an assertion failure saying `Invalid number of items in section 0.
  99. * The number of items after the update (1) must be equal to the number of items before the update (1) plus or minus the items added and removed (1 added, 0 removed).`
  100. * The collection view never queried your data source before the update to see that it actually had 0 items.
  101. */
  102. BOOL _superIsPendingDataLoad;
  103. /**
  104. * It's important that we always check for batch fetching at least once, but also
  105. * that we do not check for batch fetching for empty updates (as that may cause an infinite
  106. * loop of batch fetching, where the batch completes and performBatchUpdates: is called without
  107. * actually making any changes.) So to handle the case where a collection is completely empty
  108. * (0 sections) we always check at least once after each update (initial reload is the first update.)
  109. */
  110. BOOL _hasEverCheckedForBatchFetchingDueToUpdate;
  111. /**
  112. * The change set that we're currently building, if any.
  113. */
  114. _ASHierarchyChangeSet *_changeSet;
  115. /**
  116. * Counter used to keep track of nested batch updates.
  117. */
  118. NSInteger _batchUpdateCount;
  119. struct {
  120. unsigned int scrollViewDidScroll:1;
  121. unsigned int scrollViewWillBeginDragging:1;
  122. unsigned int scrollViewDidEndDragging:1;
  123. unsigned int scrollViewWillEndDragging:1;
  124. unsigned int collectionViewWillDisplayNodeForItem:1;
  125. unsigned int collectionViewWillDisplayNodeForItemDeprecated:1;
  126. unsigned int collectionViewDidEndDisplayingNodeForItem:1;
  127. unsigned int collectionViewShouldSelectItem:1;
  128. unsigned int collectionViewDidSelectItem:1;
  129. unsigned int collectionViewShouldDeselectItem:1;
  130. unsigned int collectionViewDidDeselectItem:1;
  131. unsigned int collectionViewShouldHighlightItem:1;
  132. unsigned int collectionViewDidHighlightItem:1;
  133. unsigned int collectionViewDidUnhighlightItem:1;
  134. unsigned int collectionViewShouldShowMenuForItem:1;
  135. unsigned int collectionViewCanPerformActionForItem:1;
  136. unsigned int collectionViewPerformActionForItem:1;
  137. unsigned int collectionViewWillBeginBatchFetch:1;
  138. unsigned int shouldBatchFetchForCollectionView:1;
  139. unsigned int collectionNodeWillDisplayItem:1;
  140. unsigned int collectionNodeDidEndDisplayingItem:1;
  141. unsigned int collectionNodeShouldSelectItem:1;
  142. unsigned int collectionNodeDidSelectItem:1;
  143. unsigned int collectionNodeShouldDeselectItem:1;
  144. unsigned int collectionNodeDidDeselectItem:1;
  145. unsigned int collectionNodeShouldHighlightItem:1;
  146. unsigned int collectionNodeDidHighlightItem:1;
  147. unsigned int collectionNodeDidUnhighlightItem:1;
  148. unsigned int collectionNodeShouldShowMenuForItem:1;
  149. unsigned int collectionNodeCanPerformActionForItem:1;
  150. unsigned int collectionNodePerformActionForItem:1;
  151. unsigned int collectionNodeWillBeginBatchFetch:1;
  152. unsigned int collectionNodeWillDisplaySupplementaryElement:1;
  153. unsigned int collectionNodeDidEndDisplayingSupplementaryElement:1;
  154. unsigned int shouldBatchFetchForCollectionNode:1;
  155. // Interop flags
  156. unsigned int interop:1;
  157. unsigned int interopWillDisplayCell:1;
  158. unsigned int interopDidEndDisplayingCell:1;
  159. } _asyncDelegateFlags;
  160. struct {
  161. unsigned int collectionViewNodeForItem:1;
  162. unsigned int collectionViewNodeBlockForItem:1;
  163. unsigned int collectionViewNodeForSupplementaryElement:1;
  164. unsigned int numberOfSectionsInCollectionView:1;
  165. unsigned int collectionViewNumberOfItemsInSection:1;
  166. unsigned int collectionNodeNodeForItem:1;
  167. unsigned int collectionNodeNodeBlockForItem:1;
  168. unsigned int collectionNodeNodeForSupplementaryElement:1;
  169. unsigned int collectionNodeSupplementaryElementKindsInSection:1;
  170. unsigned int numberOfSectionsInCollectionNode:1;
  171. unsigned int collectionNodeNumberOfItemsInSection:1;
  172. unsigned int collectionNodeContextForSection:1;
  173. // Whether this data source conforms to ASCollectionDataSourceInterop
  174. unsigned int interop:1;
  175. // Whether this interop data source returns YES from +dequeuesCellsForNodeBackedItems
  176. unsigned int interopAlwaysDequeue:1;
  177. // Whether this interop data source implements viewForSupplementaryElementOfKind:
  178. unsigned int interopViewForSupplementaryElement:1;
  179. } _asyncDataSourceFlags;
  180. struct {
  181. unsigned int didChangeCollectionViewDataSource:1;
  182. unsigned int didChangeCollectionViewDelegate:1;
  183. } _layoutInspectorFlags;
  184. }
  185. @end
  186. @implementation ASCollectionView
  187. {
  188. __weak id<ASCollectionDelegate> _asyncDelegate;
  189. __weak id<ASCollectionDataSource> _asyncDataSource;
  190. }
  191. // Using _ASDisplayLayer ensures things like -layout are properly forwarded to ASCollectionNode.
  192. + (Class)layerClass
  193. {
  194. return [_ASDisplayLayer class];
  195. }
  196. #pragma mark -
  197. #pragma mark Lifecycle.
  198. - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout
  199. {
  200. return [self initWithFrame:CGRectZero collectionViewLayout:layout];
  201. }
  202. - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
  203. {
  204. return [self _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil eventLog:nil];
  205. }
  206. - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator eventLog:(ASEventLog *)eventLog
  207. {
  208. if (!(self = [super initWithFrame:frame collectionViewLayout:layout]))
  209. return nil;
  210. // Disable UICollectionView prefetching.
  211. // Experiments done by Instagram show that this option being YES (default)
  212. // when unused causes a significant hit to scroll performance.
  213. // https://github.com/Instagram/IGListKit/issues/318
  214. if (AS_AT_LEAST_IOS10) {
  215. self.prefetchingEnabled = NO;
  216. }
  217. _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self];
  218. _rangeController = [[ASRangeController alloc] init];
  219. _rangeController.dataSource = self;
  220. _rangeController.delegate = self;
  221. _rangeController.layoutController = _layoutController;
  222. _dataController = [[ASCollectionDataController alloc] initWithDataSource:self eventLog:eventLog];
  223. _dataController.delegate = _rangeController;
  224. _dataController.environmentDelegate = self;
  225. _batchContext = [[ASBatchContext alloc] init];
  226. _leadingScreensForBatching = 2.0;
  227. _performingBatchUpdates = NO;
  228. _batchUpdateBlocks = [NSMutableArray array];
  229. _superIsPendingDataLoad = YES;
  230. _lastBoundsSizeUsedForMeasuringNodes = self.bounds.size;
  231. _layoutFacilitator = layoutFacilitator;
  232. _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
  233. super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
  234. _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
  235. super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
  236. _registeredSupplementaryKinds = [NSMutableSet set];
  237. _cellsForVisibilityUpdates = [NSMutableSet set];
  238. self.backgroundColor = [UIColor whiteColor];
  239. [self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier];
  240. if (!AS_AT_LEAST_IOS9) {
  241. _retainedLayer = self.layer;
  242. }
  243. return self;
  244. }
  245. - (void)dealloc
  246. {
  247. ASDisplayNodeAssertMainThread();
  248. ASDisplayNodeCAssert(_batchUpdateCount == 0, @"ASCollectionView deallocated in the middle of a batch update.");
  249. // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc.
  250. _isDeallocating = YES;
  251. [self setAsyncDelegate:nil];
  252. [self setAsyncDataSource:nil];
  253. // Data controller & range controller may own a ton of nodes, let's deallocate those off-main.
  254. ASPerformBackgroundDeallocation(_dataController);
  255. ASPerformBackgroundDeallocation(_rangeController);
  256. }
  257. #pragma mark -
  258. #pragma mark Overrides.
  259. - (void)reloadDataWithCompletion:(void (^)())completion
  260. {
  261. ASPerformBlockOnMainThread(^{
  262. _superIsPendingDataLoad = YES;
  263. [super reloadData];
  264. });
  265. [_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion];
  266. }
  267. - (void)reloadData
  268. {
  269. [self reloadDataWithCompletion:nil];
  270. }
  271. - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated
  272. {
  273. if ([self validateIndexPath:indexPath]) {
  274. [super scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
  275. }
  276. }
  277. - (void)reloadDataImmediately
  278. {
  279. ASDisplayNodeAssertMainThread();
  280. _superIsPendingDataLoad = YES;
  281. [_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone];
  282. [super reloadData];
  283. }
  284. - (void)relayoutItems
  285. {
  286. [_dataController relayoutAllNodes];
  287. }
  288. - (void)waitUntilAllUpdatesAreCommitted
  289. {
  290. ASDisplayNodeAssertMainThread();
  291. if (_batchUpdateCount > 0) {
  292. // This assertion will be enabled soon.
  293. // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd));
  294. return;
  295. }
  296. [_dataController waitUntilAllUpdatesAreCommitted];
  297. }
  298. - (void)setDataSource:(id<UICollectionViewDataSource>)dataSource
  299. {
  300. // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. We also allow this when we're doing interop.
  301. ASDisplayNodeAssert(_asyncDelegateFlags.interop || dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property.");
  302. }
  303. - (void)setDelegate:(id<UICollectionViewDelegate>)delegate
  304. {
  305. // Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here. We also allow this when we're doing interop.
  306. ASDisplayNodeAssert(_asyncDelegateFlags.interop || delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property.");
  307. }
  308. - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy
  309. {
  310. if (proxy == _proxyDelegate) {
  311. [self setAsyncDelegate:nil];
  312. } else if (proxy == _proxyDataSource) {
  313. [self setAsyncDataSource:nil];
  314. }
  315. }
  316. - (id<ASCollectionDataSource>)asyncDataSource
  317. {
  318. return _asyncDataSource;
  319. }
  320. - (void)setAsyncDataSource:(id<ASCollectionDataSource>)asyncDataSource
  321. {
  322. // Changing super.dataSource will trigger a setNeedsLayout, so this must happen on the main thread.
  323. ASDisplayNodeAssertMainThread();
  324. // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
  325. // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
  326. // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
  327. // reference to the old dataSource in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
  328. NS_VALID_UNTIL_END_OF_SCOPE id oldDataSource = super.dataSource;
  329. if (asyncDataSource == nil) {
  330. _asyncDataSource = nil;
  331. _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
  332. _asyncDataSourceFlags = {};
  333. } else {
  334. _asyncDataSource = asyncDataSource;
  335. _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
  336. _asyncDataSourceFlags.collectionViewNodeForItem = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)];
  337. _asyncDataSourceFlags.collectionViewNodeBlockForItem = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)];
  338. _asyncDataSourceFlags.numberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
  339. _asyncDataSourceFlags.collectionViewNumberOfItemsInSection = [_asyncDataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)];
  340. _asyncDataSourceFlags.collectionViewNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForSupplementaryElementOfKind:atIndexPath:)];
  341. _asyncDataSourceFlags.collectionNodeNodeForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForItemAtIndexPath:)];
  342. _asyncDataSourceFlags.collectionNodeNodeBlockForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForItemAtIndexPath:)];
  343. _asyncDataSourceFlags.numberOfSectionsInCollectionNode = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionNode:)];
  344. _asyncDataSourceFlags.collectionNodeNumberOfItemsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:numberOfItemsInSection:)];
  345. _asyncDataSourceFlags.collectionNodeContextForSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:contextForSection:)];
  346. _asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)];
  347. _asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)];
  348. _asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)];
  349. if (_asyncDataSourceFlags.interop) {
  350. id<ASCollectionDataSourceInterop> interopDataSource = (id<ASCollectionDataSourceInterop>)_asyncDataSource;
  351. _asyncDataSourceFlags.interopAlwaysDequeue = [[interopDataSource class] respondsToSelector:@selector(dequeuesCellsForNodeBackedItems)] && [[interopDataSource class] dequeuesCellsForNodeBackedItems];
  352. _asyncDataSourceFlags.interopViewForSupplementaryElement = [interopDataSource respondsToSelector:@selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:)];
  353. }
  354. ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection || _asyncDataSourceFlags.collectionViewNumberOfItemsInSection, @"Data source must implement collectionNode:numberOfItemsInSection:");
  355. ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNodeBlockForItem
  356. || _asyncDataSourceFlags.collectionNodeNodeForItem
  357. || _asyncDataSourceFlags.collectionViewNodeBlockForItem
  358. || _asyncDataSourceFlags.collectionViewNodeForItem, @"Data source must implement collectionNode:nodeBlockForItemAtIndexPath: or collectionNode:nodeForItemAtIndexPath:");
  359. }
  360. _dataController.validationErrorSource = asyncDataSource;
  361. super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
  362. //Cache results of layoutInspector to ensure flags are up to date if getter lazily loads a new one.
  363. id<ASCollectionViewLayoutInspecting> layoutInspector = self.layoutInspector;
  364. if (_layoutInspectorFlags.didChangeCollectionViewDataSource) {
  365. [layoutInspector didChangeCollectionViewDataSource:asyncDataSource];
  366. }
  367. }
  368. - (id<ASCollectionDelegate>)asyncDelegate
  369. {
  370. return _asyncDelegate;
  371. }
  372. - (void)setAsyncDelegate:(id<ASCollectionDelegate>)asyncDelegate
  373. {
  374. // Changing super.delegate will trigger a setNeedsLayout, so this must happen on the main thread.
  375. ASDisplayNodeAssertMainThread();
  376. // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
  377. // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
  378. // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
  379. // reference to the old delegate in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
  380. NS_VALID_UNTIL_END_OF_SCOPE id oldDelegate = super.delegate;
  381. if (asyncDelegate == nil) {
  382. _asyncDelegate = nil;
  383. _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
  384. _asyncDelegateFlags = {};
  385. } else {
  386. _asyncDelegate = asyncDelegate;
  387. _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
  388. _asyncDelegateFlags.scrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)];
  389. _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)];
  390. _asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)];
  391. _asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)];
  392. _asyncDelegateFlags.collectionViewWillDisplayNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNode:forItemAtIndexPath:)];
  393. if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem == NO) {
  394. _asyncDelegateFlags.collectionViewWillDisplayNodeForItemDeprecated = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)];
  395. }
  396. _asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)];
  397. _asyncDelegateFlags.collectionViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)];
  398. _asyncDelegateFlags.shouldBatchFetchForCollectionView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)];
  399. _asyncDelegateFlags.collectionViewShouldSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldSelectItemAtIndexPath:)];
  400. _asyncDelegateFlags.collectionViewDidSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)];
  401. _asyncDelegateFlags.collectionViewShouldDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldDeselectItemAtIndexPath:)];
  402. _asyncDelegateFlags.collectionViewDidDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didDeselectItemAtIndexPath:)];
  403. _asyncDelegateFlags.collectionViewShouldHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldHighlightItemAtIndexPath:)];
  404. _asyncDelegateFlags.collectionViewDidHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didHighlightItemAtIndexPath:)];
  405. _asyncDelegateFlags.collectionViewDidUnhighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didUnhighlightItemAtIndexPath:)];
  406. _asyncDelegateFlags.collectionViewShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldShowMenuForItemAtIndexPath:)];
  407. _asyncDelegateFlags.collectionViewCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:canPerformAction:forItemAtIndexPath:withSender:)];
  408. _asyncDelegateFlags.collectionViewPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:performAction:forItemAtIndexPath:withSender:)];
  409. _asyncDelegateFlags.collectionNodeWillDisplayItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:willDisplayItemWithNode:)];
  410. _asyncDelegateFlags.collectionNodeDidEndDisplayingItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didEndDisplayingItemWithNode:)];
  411. _asyncDelegateFlags.collectionNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(collectionNode:willBeginBatchFetchWithContext:)];
  412. _asyncDelegateFlags.shouldBatchFetchForCollectionNode = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionNode:)];
  413. _asyncDelegateFlags.collectionNodeShouldSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldSelectItemAtIndexPath:)];
  414. _asyncDelegateFlags.collectionNodeDidSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didSelectItemAtIndexPath:)];
  415. _asyncDelegateFlags.collectionNodeShouldDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldDeselectItemAtIndexPath:)];
  416. _asyncDelegateFlags.collectionNodeDidDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didDeselectItemAtIndexPath:)];
  417. _asyncDelegateFlags.collectionNodeShouldHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldHighlightItemAtIndexPath:)];
  418. _asyncDelegateFlags.collectionNodeDidHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didHighlightItemAtIndexPath:)];
  419. _asyncDelegateFlags.collectionNodeDidUnhighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didUnhighlightItemAtIndexPath:)];
  420. _asyncDelegateFlags.collectionNodeShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldShowMenuForItemAtIndexPath:)];
  421. _asyncDelegateFlags.collectionNodeCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:canPerformAction:forItemAtIndexPath:sender:)];
  422. _asyncDelegateFlags.collectionNodePerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:performAction:forItemAtIndexPath:sender:)];
  423. _asyncDelegateFlags.interop = [_asyncDelegate conformsToProtocol:@protocol(ASCollectionDelegateInterop)];
  424. if (_asyncDelegateFlags.interop) {
  425. id<ASCollectionDelegateInterop> interopDelegate = (id<ASCollectionDelegateInterop>)_asyncDelegate;
  426. _asyncDelegateFlags.interopWillDisplayCell = [interopDelegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)];
  427. _asyncDelegateFlags.interopDidEndDisplayingCell = [interopDelegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)];
  428. }
  429. }
  430. super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
  431. //Cache results of layoutInspector to ensure flags are up to date if getter lazily loads a new one.
  432. id<ASCollectionViewLayoutInspecting> layoutInspector = self.layoutInspector;
  433. if (_layoutInspectorFlags.didChangeCollectionViewDelegate) {
  434. [layoutInspector didChangeCollectionViewDelegate:asyncDelegate];
  435. }
  436. }
  437. - (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout
  438. {
  439. [super setCollectionViewLayout:collectionViewLayout];
  440. // Trigger recreation of layout inspector with new collection view layout
  441. if (_layoutInspector != nil) {
  442. _layoutInspector = nil;
  443. [self layoutInspector];
  444. }
  445. }
  446. - (id<ASCollectionViewLayoutInspecting>)layoutInspector
  447. {
  448. if (_layoutInspector == nil) {
  449. UICollectionViewLayout *layout = self.collectionViewLayout;
  450. if (layout == nil) {
  451. // Layout hasn't been set yet, we're still init'ing
  452. return nil;
  453. }
  454. _defaultLayoutInspector = [layout asdk_layoutInspector];
  455. ASDisplayNodeAssertNotNil(_defaultLayoutInspector, @"You must not return nil from -asdk_layoutInspector. Return [super asdk_layoutInspector] if you have to! Layout: %@", layout);
  456. // Explicitly call the setter to wire up the _layoutInspectorFlags
  457. self.layoutInspector = _defaultLayoutInspector;
  458. }
  459. return _layoutInspector;
  460. }
  461. - (void)setLayoutInspector:(id<ASCollectionViewLayoutInspecting>)layoutInspector
  462. {
  463. _layoutInspector = layoutInspector;
  464. _layoutInspectorFlags.didChangeCollectionViewDataSource = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDataSource:)];
  465. _layoutInspectorFlags.didChangeCollectionViewDelegate = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDelegate:)];
  466. if (_layoutInspectorFlags.didChangeCollectionViewDataSource) {
  467. [_layoutInspector didChangeCollectionViewDataSource:self.asyncDataSource];
  468. }
  469. if (_layoutInspectorFlags.didChangeCollectionViewDelegate) {
  470. [_layoutInspector didChangeCollectionViewDelegate:self.asyncDelegate];
  471. }
  472. }
  473. - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
  474. {
  475. [_rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
  476. }
  477. - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
  478. {
  479. return [_rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
  480. }
  481. - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
  482. {
  483. [_rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType];
  484. }
  485. - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
  486. {
  487. return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType];
  488. }
  489. - (void)setZeroContentInsets:(BOOL)zeroContentInsets
  490. {
  491. _zeroContentInsets = zeroContentInsets;
  492. }
  493. - (BOOL)zeroContentInsets
  494. {
  495. return _zeroContentInsets;
  496. }
  497. - (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
  498. {
  499. return [[self nodeForItemAtIndexPath:indexPath] calculatedSize];
  500. }
  501. - (NSArray<NSArray <ASCellNode *> *> *)completedNodes
  502. {
  503. return [_dataController completedNodes];
  504. }
  505. - (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath
  506. {
  507. return [_dataController nodeAtCompletedIndexPath:indexPath];
  508. }
  509. - (NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait
  510. {
  511. if (indexPath == nil) {
  512. return nil;
  513. }
  514. // If this is a section index path, we don't currently have a method
  515. // to do a mapping.
  516. if (indexPath.item == NSNotFound) {
  517. return indexPath;
  518. } else {
  519. ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
  520. NSIndexPath *viewIndexPath = [self indexPathForNode:node];
  521. if (viewIndexPath == nil && wait) {
  522. [self waitUntilAllUpdatesAreCommitted];
  523. viewIndexPath = [self indexPathForNode:node];
  524. }
  525. return viewIndexPath;
  526. }
  527. }
  528. /**
  529. * Asserts that the index path is a valid view-index-path, and returns it if so, nil otherwise.
  530. */
  531. - (nullable NSIndexPath *)validateIndexPath:(nullable NSIndexPath *)indexPath
  532. {
  533. if (indexPath == nil) {
  534. return nil;
  535. }
  536. NSInteger section = indexPath.section;
  537. if (section >= self.numberOfSections) {
  538. ASDisplayNodeFailAssert(@"Collection view index path has invalid section %lu, section count = %lu", (unsigned long)section, (unsigned long)self.numberOfSections);
  539. return nil;
  540. }
  541. NSInteger item = indexPath.item;
  542. // item == NSNotFound means e.g. "scroll to this section" and is acceptable
  543. if (item != NSNotFound && item >= [self numberOfItemsInSection:section]) {
  544. ASDisplayNodeFailAssert(@"Collection view index path has invalid item %lu in section %lu, item count = %lu", (unsigned long)indexPath.item, (unsigned long)section, (unsigned long)[self numberOfItemsInSection:section]);
  545. return nil;
  546. }
  547. return indexPath;
  548. }
  549. - (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath
  550. {
  551. if ([self validateIndexPath:indexPath] == nil) {
  552. return nil;
  553. }
  554. // If this is a section index path, we don't currently have a method
  555. // to do a mapping.
  556. if (indexPath.item == NSNotFound) {
  557. return indexPath;
  558. } else {
  559. ASCellNode *node = [self nodeForItemAtIndexPath:indexPath];
  560. return [_dataController indexPathForNode:node];
  561. }
  562. }
  563. - (NSArray<NSIndexPath *> *)convertIndexPathsToCollectionNode:(NSArray<NSIndexPath *> *)indexPaths
  564. {
  565. if (indexPaths == nil) {
  566. return nil;
  567. }
  568. NSMutableArray<NSIndexPath *> *indexPathsArray = [NSMutableArray arrayWithCapacity:indexPaths.count];
  569. for (NSIndexPath *indexPathInView in indexPaths) {
  570. NSIndexPath *indexPath = [self convertIndexPathToCollectionNode:indexPathInView];
  571. if (indexPath != nil) {
  572. [indexPathsArray addObject:indexPath];
  573. }
  574. }
  575. return indexPathsArray;
  576. }
  577. - (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
  578. {
  579. return [_dataController supplementaryNodeOfKind:elementKind atIndexPath:indexPath];
  580. }
  581. - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
  582. {
  583. return [self validateIndexPath:[_dataController completedIndexPathForNode:cellNode]];
  584. }
  585. - (NSArray *)visibleNodes
  586. {
  587. NSArray *indexPaths = [self indexPathsForVisibleItems];
  588. NSMutableArray *visibleNodes = [[NSMutableArray alloc] init];
  589. for (NSIndexPath *indexPath in indexPaths) {
  590. ASCellNode *node = [self nodeForItemAtIndexPath:indexPath];
  591. if (node) {
  592. // It is possible for UICollectionView to return indexPaths before the node is completed.
  593. [visibleNodes addObject:node];
  594. }
  595. }
  596. return visibleNodes;
  597. }
  598. #pragma mark Internal
  599. /**
  600. Performing nested batch updates with super (e.g. resizing a cell node & updating collection view during same frame)
  601. can cause super to throw data integrity exceptions because it checks the data source counts before
  602. the update is complete.
  603. Always call [self _superPerform:] rather than [super performBatch:] so that we can keep our `superPerformingBatchUpdates` flag updated.
  604. */
  605. - (void)_superPerformBatchUpdates:(void(^)())updates completion:(void(^)(BOOL finished))completion
  606. {
  607. ASDisplayNodeAssertMainThread();
  608. _superBatchUpdateCount++;
  609. [super performBatchUpdates:updates completion:completion];
  610. _superBatchUpdateCount--;
  611. }
  612. #pragma mark Assertions.
  613. - (ASDataController *)dataController
  614. {
  615. return _dataController;
  616. }
  617. - (void)beginUpdates
  618. {
  619. ASDisplayNodeAssertMainThread();
  620. // _changeSet must be available during batch update
  621. ASDisplayNodeAssertTrue((_batchUpdateCount > 0) == (_changeSet != nil));
  622. if (_batchUpdateCount == 0) {
  623. _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]];
  624. }
  625. _batchUpdateCount++;
  626. }
  627. - (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion
  628. {
  629. ASDisplayNodeAssertMainThread();
  630. ASDisplayNodeAssertNotNil(_changeSet, @"_changeSet must be available when batch update ends");
  631. _batchUpdateCount--;
  632. // Prevent calling endUpdatesAnimated:completion: in an unbalanced way
  633. NSAssert(_batchUpdateCount >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call");
  634. [_changeSet addCompletionHandler:completion];
  635. if (_batchUpdateCount == 0) {
  636. [_dataController updateWithChangeSet:_changeSet animated:animated];
  637. _changeSet = nil;
  638. }
  639. }
  640. - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion
  641. {
  642. ASDisplayNodeAssertMainThread();
  643. [self beginUpdates];
  644. if (updates) {
  645. updates();
  646. }
  647. [self endUpdatesAnimated:animated completion:completion];
  648. }
  649. - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion
  650. {
  651. // We capture the current state of whether animations are enabled if they don't provide us with one.
  652. [self performBatchAnimated:[UIView areAnimationsEnabled] updates:updates completion:completion];
  653. }
  654. - (void)registerSupplementaryNodeOfKind:(NSString *)elementKind
  655. {
  656. ASDisplayNodeAssert(elementKind != nil, @"A kind is needed for supplementary node registration");
  657. [_registeredSupplementaryKinds addObject:elementKind];
  658. [self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier];
  659. }
  660. - (void)insertSections:(NSIndexSet *)sections
  661. {
  662. ASDisplayNodeAssertMainThread();
  663. if (sections.count == 0) { return; }
  664. [self performBatchUpdates:^{
  665. [_changeSet insertSections:sections animationOptions:kASCollectionViewAnimationNone];
  666. } completion:nil];
  667. }
  668. - (void)deleteSections:(NSIndexSet *)sections
  669. {
  670. ASDisplayNodeAssertMainThread();
  671. if (sections.count == 0) { return; }
  672. [self performBatchUpdates:^{
  673. [_changeSet deleteSections:sections animationOptions:kASCollectionViewAnimationNone];
  674. } completion:nil];
  675. }
  676. - (void)reloadSections:(NSIndexSet *)sections
  677. {
  678. ASDisplayNodeAssertMainThread();
  679. if (sections.count == 0) { return; }
  680. [self performBatchUpdates:^{
  681. [_changeSet reloadSections:sections animationOptions:kASCollectionViewAnimationNone];
  682. } completion:nil];
  683. }
  684. - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
  685. {
  686. ASDisplayNodeAssertMainThread();
  687. [self performBatchUpdates:^{
  688. [_changeSet moveSection:section toSection:newSection animationOptions:kASCollectionViewAnimationNone];
  689. } completion:nil];
  690. }
  691. - (id<ASSectionContext>)contextForSection:(NSInteger)section
  692. {
  693. ASDisplayNodeAssertMainThread();
  694. return [_dataController contextForSection:section];
  695. }
  696. - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
  697. {
  698. ASDisplayNodeAssertMainThread();
  699. if (indexPaths.count == 0) { return; }
  700. [self performBatchUpdates:^{
  701. [_changeSet insertItems:indexPaths animationOptions:kASCollectionViewAnimationNone];
  702. } completion:nil];
  703. }
  704. - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths
  705. {
  706. ASDisplayNodeAssertMainThread();
  707. if (indexPaths.count == 0) { return; }
  708. [self performBatchUpdates:^{
  709. [_changeSet deleteItems:indexPaths animationOptions:kASCollectionViewAnimationNone];
  710. } completion:nil];
  711. }
  712. - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths
  713. {
  714. ASDisplayNodeAssertMainThread();
  715. if (indexPaths.count == 0) { return; }
  716. [self performBatchUpdates:^{
  717. [_changeSet reloadItems:indexPaths animationOptions:kASCollectionViewAnimationNone];
  718. } completion:nil];
  719. }
  720. - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
  721. {
  722. ASDisplayNodeAssertMainThread();
  723. [self performBatchUpdates:^{
  724. [_changeSet moveItemAtIndexPath:indexPath toIndexPath:newIndexPath animationOptions:kASCollectionViewAnimationNone];
  725. } completion:nil];
  726. }
  727. #pragma mark -
  728. #pragma mark Intercepted selectors.
  729. - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
  730. {
  731. _superIsPendingDataLoad = NO;
  732. return [_dataController completedNumberOfSections];
  733. }
  734. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  735. {
  736. return [_dataController completedNumberOfRowsInSection:section];
  737. }
  738. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
  739. {
  740. return [[self nodeForItemAtIndexPath:indexPath] calculatedSize];
  741. }
  742. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForHeaderInSection:(NSInteger)section
  743. {
  744. ASCellNode *cell = [self supplementaryNodeForElementKind:UICollectionElementKindSectionHeader
  745. atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
  746. if (cell.shouldUseUIKitCell && _asyncDelegateFlags.interop) {
  747. if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) {
  748. return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForHeaderInSection:section];
  749. }
  750. }
  751. return cell.calculatedSize;
  752. }
  753. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForFooterInSection:(NSInteger)section
  754. {
  755. ASCellNode *cell = [self supplementaryNodeForElementKind:UICollectionElementKindSectionFooter
  756. atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
  757. if (cell.shouldUseUIKitCell && _asyncDelegateFlags.interop) {
  758. if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]) {
  759. return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForFooterInSection:section];
  760. }
  761. }
  762. return cell.calculatedSize;
  763. }
  764. - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
  765. {
  766. if ([_registeredSupplementaryKinds containsObject:kind] == NO) {
  767. [self registerSupplementaryNodeOfKind:kind];
  768. }
  769. UICollectionReusableView *view = nil;
  770. ASCellNode *node = [_dataController supplementaryNodeOfKind:kind atIndexPath:indexPath];
  771. BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopViewForSupplementaryElement && (_asyncDataSourceFlags.interopAlwaysDequeue || node.shouldUseUIKitCell);
  772. if (shouldDequeueExternally) {
  773. view = [(id<ASCollectionDataSourceInterop>)_asyncDataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath];
  774. } else {
  775. view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
  776. }
  777. if (!node.shouldUseUIKitCell) {
  778. ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self);
  779. }
  780. if (node) {
  781. [_rangeController configureContentView:view forCellNode:node];
  782. }
  783. return view;
  784. }
  785. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  786. {
  787. UICollectionViewCell *cell = nil;
  788. ASCellNode *node = [self nodeForItemAtIndexPath:indexPath];
  789. BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interop && node.shouldUseUIKitCell);
  790. if (shouldDequeueExternally) {
  791. cell = [(id<ASCollectionDataSourceInterop>)_asyncDataSource collectionView:collectionView cellForItemAtIndexPath:indexPath];
  792. } else {
  793. cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
  794. }
  795. ASDisplayNodeAssert(node != nil, @"Cell node should exist. indexPath = %@, collectionDataSource = %@", indexPath, self);
  796. if (_ASCollectionViewCell *asCell = ASDynamicCast(cell, _ASCollectionViewCell)) {
  797. asCell.node = node;
  798. asCell.selectedBackgroundView = node.selectedBackgroundView;
  799. [_rangeController configureContentView:cell.contentView forCellNode:node];
  800. }
  801. return cell;
  802. }
  803. - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
  804. {
  805. if (_asyncDelegateFlags.interopWillDisplayCell) {
  806. [(id <ASCollectionDelegateInterop>)_asyncDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
  807. }
  808. // Since _ASCollectionViewCell is not available for subclassing, this is faster than isKindOfClass:
  809. // We must exit early here, because only _ASCollectionViewCell implements the -node accessor method.
  810. if ([cell class] != [_ASCollectionViewCell class]) {
  811. [_rangeController setNeedsUpdate];
  812. return;
  813. }
  814. ASCellNode *cellNode = [cell node];
  815. cellNode.scrollView = collectionView;
  816. // Under iOS 10+, cells may be removed/re-added to the collection view without
  817. // receiving prepareForReuse/applyLayoutAttributes, as an optimization for e.g.
  818. // if the user is scrolling back and forth across a small set of items.
  819. // In this case, we have to fetch the layout attributes manually.
  820. // This may be possible under iOS < 10 but it has not been observed yet.
  821. if (cell.layoutAttributes == nil) {
  822. cell.layoutAttributes = [collectionView layoutAttributesForItemAtIndexPath:indexPath];
  823. }
  824. ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath);
  825. if (_asyncDelegateFlags.collectionNodeWillDisplayItem && self.collectionNode != nil) {
  826. [_asyncDelegate collectionNode:self.collectionNode willDisplayItemWithNode:cellNode];
  827. } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem) {
  828. #pragma clang diagnostic push
  829. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  830. [_asyncDelegate collectionView:self willDisplayNode:cellNode forItemAtIndexPath:indexPath];
  831. } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItemDeprecated) {
  832. [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
  833. }
  834. #pragma clang diagnostic pop
  835. [_rangeController setNeedsUpdate];
  836. if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) {
  837. [_cellsForVisibilityUpdates addObject:cell];
  838. }
  839. }
  840. - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
  841. {
  842. if (_asyncDelegateFlags.interopDidEndDisplayingCell) {
  843. [(id <ASCollectionDelegateInterop>)_asyncDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath];
  844. }
  845. // Since _ASCollectionViewCell is not available for subclassing, this is faster than isKindOfClass:
  846. // We must exit early here, because only _ASCollectionViewCell implements the -node accessor method.
  847. if ([cell class] != [_ASCollectionViewCell class]) {
  848. [_rangeController setNeedsUpdate];
  849. return;
  850. }
  851. ASCellNode *cellNode = [cell node];
  852. ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
  853. if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) {
  854. if (ASCollectionNode *collectionNode = self.collectionNode) {
  855. [_asyncDelegate collectionNode:collectionNode didEndDisplayingItemWithNode:cellNode];
  856. }
  857. } else if (_asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem) {
  858. #pragma clang diagnostic push
  859. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  860. [_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath];
  861. #pragma clang diagnostic pop
  862. }
  863. [_rangeController setNeedsUpdate];
  864. [_cellsForVisibilityUpdates removeObject:cell];
  865. cellNode.scrollView = nil;
  866. cell.layoutAttributes = nil;
  867. }
  868. - (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
  869. {
  870. if (_asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement) {
  871. GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
  872. ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath];
  873. ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind);
  874. [_asyncDelegate collectionNode:collectionNode willDisplaySupplementaryElementWithNode:node];
  875. }
  876. }
  877. - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
  878. {
  879. if (_asyncDelegateFlags.collectionNodeDidEndDisplayingSupplementaryElement) {
  880. GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
  881. ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath];
  882. ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind);
  883. [_asyncDelegate collectionNode:collectionNode didEndDisplayingSupplementaryElementWithNode:node];
  884. }
  885. }
  886. - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
  887. {
  888. if (_asyncDelegateFlags.collectionNodeShouldSelectItem) {
  889. GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
  890. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  891. if (indexPath != nil) {
  892. return [_asyncDelegate collectionNode:collectionNode shouldSelectItemAtIndexPath:indexPath];
  893. }
  894. } else if (_asyncDelegateFlags.collectionViewShouldSelectItem) {
  895. #pragma clang diagnostic push
  896. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  897. return [_asyncDelegate collectionView:self shouldSelectItemAtIndexPath:indexPath];
  898. #pragma clang diagnostic pop
  899. }
  900. return YES;
  901. }
  902. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(nonnull NSIndexPath *)indexPath
  903. {
  904. if (_asyncDelegateFlags.collectionNodeDidSelectItem) {
  905. GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
  906. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  907. if (indexPath != nil) {
  908. [_asyncDelegate collectionNode:collectionNode didSelectItemAtIndexPath:indexPath];
  909. }
  910. } else if (_asyncDelegateFlags.collectionViewDidSelectItem) {
  911. #pragma clang diagnostic push
  912. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  913. [_asyncDelegate collectionView:self didSelectItemAtIndexPath:indexPath];
  914. #pragma clang diagnostic pop
  915. }
  916. }
  917. - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
  918. {
  919. if (_asyncDelegateFlags.collectionNodeShouldDeselectItem) {
  920. GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
  921. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  922. if (indexPath != nil) {
  923. return [_asyncDelegate collectionNode:collectionNode shouldDeselectItemAtIndexPath:indexPath];
  924. }
  925. } else if (_asyncDelegateFlags.collectionViewShouldDeselectItem) {
  926. #pragma clang diagnostic push
  927. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  928. return [_asyncDelegate collectionView:self shouldDeselectItemAtIndexPath:indexPath];
  929. #pragma clang diagnostic pop
  930. }
  931. return YES;
  932. }
  933. - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(nonnull NSIndexPath *)indexPath
  934. {
  935. if (_asyncDelegateFlags.collectionNodeDidDeselectItem) {
  936. GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
  937. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  938. if (indexPath != nil) {
  939. [_asyncDelegate collectionNode:collectionNode didDeselectItemAtIndexPath:indexPath];
  940. }
  941. } else if (_asyncDelegateFlags.collectionViewDidDeselectItem) {
  942. #pragma clang diagnostic push
  943. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  944. [_asyncDelegate collectionView:self didDeselectItemAtIndexPath:indexPath];
  945. #pragma clang diagnostic pop
  946. }
  947. }
  948. - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
  949. {
  950. if (_asyncDelegateFlags.collectionNodeShouldHighlightItem) {
  951. GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
  952. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  953. if (indexPath != nil) {
  954. return [_asyncDelegate collectionNode:collectionNode shouldHighlightItemAtIndexPath:indexPath];
  955. } else {
  956. return YES;
  957. }
  958. } else if (_asyncDelegateFlags.collectionViewShouldHighlightItem) {
  959. #pragma clang diagnostic push
  960. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  961. return [_asyncDelegate collectionView:self shouldHighlightItemAtIndexPath:indexPath];
  962. #pragma clang diagnostic pop
  963. }
  964. return YES;
  965. }
  966. - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath
  967. {
  968. if (_asyncDelegateFlags.collectionNodeDidHighlightItem) {
  969. GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
  970. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  971. if (indexPath != nil) {
  972. [_asyncDelegate collectionNode:collectionNode didHighlightItemAtIndexPath:indexPath];
  973. }
  974. } else if (_asyncDelegateFlags.collectionViewDidHighlightItem) {
  975. #pragma clang diagnostic push
  976. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  977. [_asyncDelegate collectionView:self didHighlightItemAtIndexPath:indexPath];
  978. #pragma clang diagnostic pop
  979. }
  980. }
  981. - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath
  982. {
  983. if (_asyncDelegateFlags.collectionNodeDidUnhighlightItem) {
  984. GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
  985. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  986. if (indexPath != nil) {
  987. [_asyncDelegate collectionNode:collectionNode didUnhighlightItemAtIndexPath:indexPath];
  988. }
  989. } else if (_asyncDelegateFlags.collectionViewDidUnhighlightItem) {
  990. #pragma clang diagnostic push
  991. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  992. [_asyncDelegate collectionView:self didUnhighlightItemAtIndexPath:indexPath];
  993. #pragma clang diagnostic pop
  994. }
  995. }
  996. - (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
  997. {
  998. if (_asyncDelegateFlags.collectionNodeShouldShowMenuForItem) {
  999. GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
  1000. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  1001. if (indexPath != nil) {
  1002. return [_asyncDelegate collectionNode:collectionNode shouldShowMenuForItemAtIndexPath:indexPath];
  1003. }
  1004. } else if (_asyncDelegateFlags.collectionViewShouldShowMenuForItem) {
  1005. #pragma clang diagnostic push
  1006. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1007. return [_asyncDelegate collectionView:self shouldShowMenuForItemAtIndexPath:indexPath];
  1008. #pragma clang diagnostic pop
  1009. }
  1010. return NO;
  1011. }
  1012. - (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender
  1013. {
  1014. if (_asyncDelegateFlags.collectionNodeCanPerformActionForItem) {
  1015. GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
  1016. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  1017. if (indexPath != nil) {
  1018. return [_asyncDelegate collectionNode:collectionNode canPerformAction:action forItemAtIndexPath:indexPath sender:sender];
  1019. }
  1020. } else if (_asyncDelegateFlags.collectionViewCanPerformActionForItem) {
  1021. #pragma clang diagnostic push
  1022. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1023. return [_asyncDelegate collectionView:self canPerformAction:action forItemAtIndexPath:indexPath withSender:sender];
  1024. #pragma clang diagnostic pop
  1025. }
  1026. return NO;
  1027. }
  1028. - (void)collectionView:(UICollectionView *)collectionView performAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender
  1029. {
  1030. if (_asyncDelegateFlags.collectionNodePerformActionForItem) {
  1031. GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
  1032. indexPath = [self convertIndexPathToCollectionNode:indexPath];
  1033. if (indexPath != nil) {
  1034. [_asyncDelegate collectionNode:collectionNode performAction:action forItemAtIndexPath:indexPath sender:sender];
  1035. }
  1036. } else if (_asyncDelegateFlags.collectionViewPerformActionForItem) {
  1037. #pragma clang diagnostic push
  1038. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1039. [_asyncDelegate collectionView:self performAction:action forItemAtIndexPath:indexPath withSender:sender];
  1040. #pragma clang diagnostic pop
  1041. }
  1042. }
  1043. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  1044. {
  1045. // If a scroll happenes the current range mode needs to go to full
  1046. ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
  1047. if (ASInterfaceStateIncludesVisible(interfaceState)) {
  1048. [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
  1049. [self _checkForBatchFetching];
  1050. }
  1051. for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) {
  1052. // Only nodes that respond to the selector are added to _cellsForVisibilityUpdates
  1053. [[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged
  1054. inScrollView:scrollView
  1055. withCellFrame:collectionCell.frame];
  1056. }
  1057. if (_asyncDelegateFlags.scrollViewDidScroll) {
  1058. [_asyncDelegate scrollViewDidScroll:scrollView];
  1059. }
  1060. }
  1061. - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
  1062. {
  1063. CGPoint contentOffset = scrollView.contentOffset;
  1064. _deceleratingVelocity = CGPointMake(
  1065. contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0),
  1066. contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0)
  1067. );
  1068. if (targetContentOffset != NULL) {
  1069. ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
  1070. [self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset];
  1071. }
  1072. if (_asyncDelegateFlags.scrollViewWillEndDragging) {
  1073. [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:(targetContentOffset ? : &contentOffset)];
  1074. }
  1075. }
  1076. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
  1077. {
  1078. for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) {
  1079. [[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging
  1080. inScrollView:scrollView
  1081. withCellFrame:collectionCell.frame];
  1082. }
  1083. if (_asyncDelegateFlags.scrollViewWillBeginDragging) {
  1084. [_asyncDelegate scrollViewWillBeginDragging:scrollView];
  1085. }
  1086. }
  1087. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
  1088. {
  1089. for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) {
  1090. [[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidEndDragging
  1091. inScrollView:scrollView
  1092. withCellFrame:collectionCell.frame];
  1093. }
  1094. if (_asyncDelegateFlags.scrollViewDidEndDragging) {
  1095. [_asyncDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
  1096. }
  1097. }
  1098. #pragma mark - Scroll Direction.
  1099. - (ASScrollDirection)scrollDirection
  1100. {
  1101. CGPoint scrollVelocity;
  1102. if (self.isTracking) {
  1103. scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview];
  1104. } else {
  1105. scrollVelocity = _deceleratingVelocity;
  1106. }
  1107. ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity];
  1108. return ASScrollDirectionApplyTransform(scrollDirection, self.transform);
  1109. }
  1110. - (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity
  1111. {
  1112. ASScrollDirection direction = ASScrollDirectionNone;
  1113. ASScrollDirection scrollableDirections = [self scrollableDirections];
  1114. if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally.
  1115. if (scrollVelocity.x < 0.0) {
  1116. direction |= ASScrollDirectionRight;
  1117. } else if (scrollVelocity.x > 0.0) {
  1118. direction |= ASScrollDirectionLeft;
  1119. }
  1120. }
  1121. if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically.
  1122. if (scrollVelocity.y < 0.0) {
  1123. direction |= ASScrollDirectionDown;
  1124. } else if (scrollVelocity.y > 0.0) {
  1125. direction |= ASScrollDirectionUp;
  1126. }
  1127. }
  1128. return direction;
  1129. }
  1130. - (ASScrollDirection)scrollableDirections
  1131. {
  1132. ASDisplayNodeAssertNotNil(self.layoutInspector, @"Layout inspector should be assigned.");
  1133. return [self.layoutInspector scrollableDirections];
  1134. }
  1135. - (ASScrollDirection)flowLayoutScrollableDirections:(UICollectionViewFlowLayout *)flowLayout {
  1136. return (flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections;
  1137. }
  1138. - (void)layoutSubviews
  1139. {
  1140. // Flush any pending invalidation action if needed.
  1141. ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle;
  1142. _nextLayoutInvalidationStyle = ASCollectionViewInvalidationStyleNone;
  1143. switch (invalidationStyle) {
  1144. case ASCollectionViewInvalidationStyleWithAnimation:
  1145. if (0 == _superBatchUpdateCount) {
  1146. [self _superPerformBatchUpdates:^{ } completion:nil];
  1147. }
  1148. break;
  1149. case ASCollectionViewInvalidationStyleWithoutAnimation:
  1150. [self.collectionViewLayout invalidateLayout];
  1151. break;
  1152. default:
  1153. break;
  1154. }
  1155. // To ensure _maxSizeForNodesConstrainedSize is up-to-date for every usage, this call to super must be done last
  1156. [super layoutSubviews];
  1157. if (_zeroContentInsets) {
  1158. self.contentInset = UIEdgeInsetsZero;
  1159. }
  1160. // Update range controller immediately if possible & needed.
  1161. // Calling -updateIfNeeded in here with self.window == nil (early in the collection view's life)
  1162. // may cause UICollectionView data related crashes. We'll update in -didMoveToWindow anyway.
  1163. if (self.window != nil) {
  1164. [_rangeController updateIfNeeded];
  1165. }
  1166. }
  1167. #pragma mark - Batch Fetching
  1168. - (ASBatchContext *)batchContext
  1169. {
  1170. return _batchContext;
  1171. }
  1172. - (BOOL)canBatchFetch
  1173. {
  1174. // if the delegate does not respond to this method, there is no point in starting to fetch
  1175. BOOL canFetch = _asyncDelegateFlags.collectionNodeWillBeginBatchFetch || _asyncDelegateFlags.collectionViewWillBeginBatchFetch;
  1176. if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionNode) {
  1177. GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
  1178. return [_asyncDelegate shouldBatchFetchForCollectionNode:collectionNode];
  1179. } else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionView) {
  1180. #pragma clang diagnostic push
  1181. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1182. return [_asyncDelegate shouldBatchFetchForCollectionView:self];
  1183. #pragma clang diagnostic pop
  1184. } else {
  1185. return canFetch;
  1186. }
  1187. }
  1188. - (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes
  1189. {
  1190. // Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided
  1191. if (changes == 0 && _hasEverCheckedForBatchFetchingDueToUpdate) {
  1192. return;
  1193. }
  1194. _hasEverCheckedForBatchFetchingDueToUpdate = YES;
  1195. // Push this to the next runloop to be sure the scroll view has the right content size
  1196. dispatch_async(dispatch_get_main_queue(), ^{
  1197. [self _checkForBatchFetching];
  1198. });
  1199. }
  1200. - (void)_checkForBatchFetching
  1201. {
  1202. // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset:
  1203. if (self.isDragging || self.isTracking) {
  1204. return;
  1205. }
  1206. [self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset];
  1207. }
  1208. - (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset
  1209. {
  1210. if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, self.scrollableDirections, contentOffset)) {
  1211. [self _beginBatchFetching];
  1212. }
  1213. }
  1214. - (void)_beginBatchFetching
  1215. {
  1216. [_batchContext beginBatchFetching];
  1217. if (_asyncDelegateFlags.collectionNodeWillBeginBatchFetch) {
  1218. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  1219. GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
  1220. [_asyncDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:_batchContext];
  1221. });
  1222. } else if (_asyncDelegateFlags.collectionViewWillBeginBatchFetch) {
  1223. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  1224. #pragma clang diagnostic push
  1225. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1226. [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext];
  1227. #pragma clang diagnostic pop
  1228. });
  1229. }
  1230. }
  1231. #pragma mark - ASDataControllerSource
  1232. - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath
  1233. {
  1234. ASCellNodeBlock block = nil;
  1235. ASCellNode *cell = nil;
  1236. if (_asyncDataSourceFlags.collectionNodeNodeBlockForItem) {
  1237. GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; });
  1238. block = [_asyncDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath];
  1239. } else if (_asyncDataSourceFlags.collectionNodeNodeForItem) {
  1240. GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; });
  1241. cell = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath];
  1242. #pragma clang diagnostic push
  1243. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1244. } else if (_asyncDataSourceFlags.collectionViewNodeBlockForItem) {
  1245. block = [_asyncDataSource collectionView:self nodeBlockForItemAtIndexPath:indexPath];
  1246. } else if (_asyncDataSourceFlags.collectionViewNodeForItem) {
  1247. cell = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
  1248. }
  1249. #pragma clang diagnostic pop
  1250. // Handle nil node block or cell
  1251. if (cell && [cell isKindOfClass:[ASCellNode class]]) {
  1252. block = ^{
  1253. return cell;
  1254. };
  1255. }
  1256. if (block == nil) {
  1257. if (_asyncDataSourceFlags.interop) {
  1258. block = ^{
  1259. ASCellNode *cell = [[ASCellNode alloc] init];
  1260. cell.shouldUseUIKitCell = YES;
  1261. cell.style.preferredSize = CGSizeZero;
  1262. return cell;
  1263. };
  1264. } else {
  1265. ASDisplayNodeFailAssert(@"ASCollection could not get a node block for row at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the <ASCollectionDataSourceInterop> protocol!", indexPath, cell, block);
  1266. block = ^{
  1267. return [[ASCellNode alloc] init];
  1268. };
  1269. }
  1270. }
  1271. // Wrap the node block
  1272. __weak __typeof__(self) weakSelf = self;
  1273. return ^{
  1274. __typeof__(self) strongSelf = weakSelf;
  1275. ASCellNode *node = (block != nil ? block() : [[ASCellNode alloc] init]);
  1276. [node enterHierarchyState:ASHierarchyStateRangeManaged];
  1277. if (node.interactionDelegate == nil) {
  1278. node.interactionDelegate = strongSelf;
  1279. }
  1280. if (_inverted) {
  1281. node.transform = CATransform3DMakeScale(1, -1, 1) ;
  1282. }
  1283. return node;
  1284. };
  1285. return block;
  1286. }
  1287. - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
  1288. {
  1289. return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
  1290. }
  1291. - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
  1292. {
  1293. if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) {
  1294. GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0);
  1295. return [_asyncDataSource collectionNode:collectionNode numberOfItemsInSection:section];
  1296. } else if (_asyncDataSourceFlags.collectionViewNumberOfItemsInSection) {
  1297. #pragma clang diagnostic push
  1298. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1299. return [_asyncDataSource collectionView:self numberOfItemsInSection:section];
  1300. #pragma clang diagnostic pop
  1301. } else {
  1302. return 0;
  1303. }
  1304. }
  1305. - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController {
  1306. if (_asyncDataSourceFlags.numberOfSectionsInCollectionNode) {
  1307. GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0);
  1308. return [_asyncDataSource numberOfSectionsInCollectionNode:collectionNode];
  1309. } else if (_asyncDataSourceFlags.numberOfSectionsInCollectionView) {
  1310. #pragma clang diagnostic push
  1311. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1312. return [_asyncDataSource numberOfSectionsInCollectionView:self];
  1313. #pragma clang diagnostic pop
  1314. } else {
  1315. return 1;
  1316. }
  1317. }
  1318. - (id<ASTraitEnvironment>)dataControllerEnvironment
  1319. {
  1320. return self.collectionNode;
  1321. }
  1322. #pragma mark - ASCollectionViewDataControllerSource
  1323. - (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
  1324. {
  1325. ASCellNode *node = nil;
  1326. if (_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement) {
  1327. GET_COLLECTIONNODE_OR_RETURN(collectionNode, [[ASCellNode alloc] init] );
  1328. node = [_asyncDataSource collectionNode:collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath];
  1329. } else if (_asyncDataSourceFlags.collectionViewNodeForSupplementaryElement) {
  1330. #pragma clang diagnostic push
  1331. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1332. node = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath];
  1333. #pragma clang diagnostic pop
  1334. }
  1335. if (node == nil && _asyncDataSourceFlags.interop) {
  1336. node = [[ASCellNode alloc] init];
  1337. node.shouldUseUIKitCell = YES;
  1338. }
  1339. ASDisplayNodeAssert(node != nil, @"A node must be returned for supplementary element of kind '%@' at index path '%@'", kind, indexPath);
  1340. return node;
  1341. }
  1342. - (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController sections:(NSIndexSet *)sections
  1343. {
  1344. if (_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection) {
  1345. NSMutableSet *kinds = [NSMutableSet set];
  1346. GET_COLLECTIONNODE_OR_RETURN(collectionNode, @[]);
  1347. [sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) {
  1348. NSArray *kindsForSection = [_asyncDataSource collectionNode:collectionNode supplementaryElementKindsInSection:section];
  1349. [kinds addObjectsFromArray:kindsForSection];
  1350. }];
  1351. return [kinds allObjects];
  1352. } else {
  1353. // TODO: Lock this
  1354. return [_registeredSupplementaryKinds allObjects];
  1355. }
  1356. }
  1357. - (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
  1358. {
  1359. return [self.layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath];
  1360. }
  1361. - (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
  1362. {
  1363. if (_asyncDataSource == nil) {
  1364. return 0;
  1365. }
  1366. return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section];
  1367. }
  1368. - (id<ASSectionContext>)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section
  1369. {
  1370. ASDisplayNodeAssertMainThread();
  1371. id<ASSectionContext> context = nil;
  1372. if (_asyncDataSourceFlags.collectionNodeContextForSection) {
  1373. GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil);
  1374. context = [_asyncDataSource collectionNode:collectionNode contextForSection:section];
  1375. }
  1376. if (context != nil) {
  1377. context.collectionView = self;
  1378. }
  1379. return context;
  1380. }
  1381. #pragma mark - ASRangeControllerDataSource
  1382. - (ASRangeController *)rangeController
  1383. {
  1384. return _rangeController;
  1385. }
  1386. - (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController
  1387. {
  1388. ASDisplayNodeAssertMainThread();
  1389. // Calling -indexPathsForVisibleItems will trigger UIKit to call reloadData if it never has, which can result
  1390. // in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast.
  1391. BOOL isZeroSized = CGSizeEqualToSize(self.bounds.size, CGSizeZero);
  1392. return isZeroSized ? @[] : [self indexPathsForVisibleItems];
  1393. }
  1394. - (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController
  1395. {
  1396. return self.scrollDirection;
  1397. }
  1398. - (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController
  1399. {
  1400. ASDisplayNodeAssertMainThread();
  1401. return self.bounds.size;
  1402. }
  1403. - (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController
  1404. {
  1405. return ASInterfaceStateForDisplayNode(self.collectionNode, self.window);
  1406. }
  1407. - (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath
  1408. {
  1409. return [self nodeForItemAtIndexPath:indexPath];
  1410. }
  1411. - (NSString *)nameForRangeControllerDataSource
  1412. {
  1413. return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]);
  1414. }
  1415. #pragma mark - ASRangeControllerDelegate
  1416. - (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController
  1417. {
  1418. ASDisplayNodeAssertMainThread();
  1419. _performingBatchUpdates = YES;
  1420. }
  1421. - (void)rangeController:(ASRangeController *)rangeController didEndUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
  1422. {
  1423. ASDisplayNodeAssertMainThread();
  1424. if (!self.asyncDataSource || _superIsPendingDataLoad) {
  1425. if (completion) {
  1426. completion(NO);
  1427. }
  1428. _performingBatchUpdates = NO;
  1429. return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
  1430. }
  1431. ASPerformBlockWithoutAnimation(!animated, ^{
  1432. NSUInteger numberOfUpdateBlocks = _batchUpdateBlocks.count;
  1433. [_layoutFacilitator collectionViewWillPerformBatchUpdates];
  1434. [self _superPerformBatchUpdates:^{
  1435. for (dispatch_block_t block in _batchUpdateBlocks) {
  1436. block();
  1437. }
  1438. } completion:^(BOOL finished){
  1439. // Flush any range changes that happened as part of the update animations ending.
  1440. [_rangeController updateIfNeeded];
  1441. [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdateBlocks];
  1442. if (completion) { completion(finished); }
  1443. }];
  1444. // Flush any range changes that happened as part of submitting the update.
  1445. [_rangeController updateIfNeeded];
  1446. });
  1447. [_batchUpdateBlocks removeAllObjects];
  1448. _performingBatchUpdates = NO;
  1449. }
  1450. - (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
  1451. {
  1452. ASDisplayNodeAssertMainThread();
  1453. if (!self.asyncDataSource || _superIsPendingDataLoad) {
  1454. return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
  1455. }
  1456. [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates];
  1457. if (_performingBatchUpdates) {
  1458. [_batchUpdateBlocks addObject:^{
  1459. [super insertItemsAtIndexPaths:indexPaths];
  1460. }];
  1461. } else {
  1462. [UIView performWithoutAnimation:^{
  1463. [super insertItemsAtIndexPaths:indexPaths];
  1464. // Flush any range changes that happened as part of submitting the update.
  1465. [_rangeController updateIfNeeded];
  1466. [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count];
  1467. }];
  1468. }
  1469. }
  1470. - (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
  1471. {
  1472. ASDisplayNodeAssertMainThread();
  1473. if (!self.asyncDataSource || _superIsPendingDataLoad) {
  1474. return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
  1475. }
  1476. [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates];
  1477. if (_performingBatchUpdates) {
  1478. [_batchUpdateBlocks addObject:^{
  1479. [super deleteItemsAtIndexPaths:indexPaths];
  1480. }];
  1481. } else {
  1482. [UIView performWithoutAnimation:^{
  1483. [super deleteItemsAtIndexPaths:indexPaths];
  1484. // Flush any range changes that happened as part of submitting the update.
  1485. [_rangeController updateIfNeeded];
  1486. [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count];
  1487. }];
  1488. }
  1489. }
  1490. - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
  1491. {
  1492. ASDisplayNodeAssertMainThread();
  1493. if (!self.asyncDataSource || _superIsPendingDataLoad) {
  1494. return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
  1495. }
  1496. [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates];
  1497. if (_performingBatchUpdates) {
  1498. [_batchUpdateBlocks addObject:^{
  1499. [super insertSections:indexSet];
  1500. }];
  1501. } else {
  1502. [UIView performWithoutAnimation:^{
  1503. [super insertSections:indexSet];
  1504. // Flush any range changes that happened as part of submitting the update.
  1505. [_rangeController updateIfNeeded];
  1506. [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count];
  1507. }];
  1508. }
  1509. }
  1510. - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
  1511. {
  1512. ASDisplayNodeAssertMainThread();
  1513. if (!self.asyncDataSource || _superIsPendingDataLoad) {
  1514. return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
  1515. }
  1516. [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates];
  1517. if (_performingBatchUpdates) {
  1518. [_batchUpdateBlocks addObject:^{
  1519. [super deleteSections:indexSet];
  1520. }];
  1521. } else {
  1522. [UIView performWithoutAnimation:^{
  1523. [super deleteSections:indexSet];
  1524. // Flush any range changes that happened as part of submitting the update.
  1525. [_rangeController updateIfNeeded];
  1526. [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count];
  1527. }];
  1528. }
  1529. }
  1530. #pragma mark - ASCellNodeDelegate
  1531. - (void)nodeSelectedStateDidChange:(ASCellNode *)node
  1532. {
  1533. NSIndexPath *indexPath = [self indexPathForNode:node];
  1534. if (indexPath) {
  1535. if (node.isSelected) {
  1536. [super selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
  1537. } else {
  1538. [super deselectItemAtIndexPath:indexPath animated:NO];
  1539. }
  1540. }
  1541. }
  1542. - (void)nodeHighlightedStateDidChange:(ASCellNode *)node
  1543. {
  1544. NSIndexPath *indexPath = [self indexPathForNode:node];
  1545. if (indexPath) {
  1546. [self cellForItemAtIndexPath:indexPath].highlighted = node.isHighlighted;
  1547. }
  1548. }
  1549. - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged
  1550. {
  1551. ASDisplayNodeAssertMainThread();
  1552. if (!sizeChanged) {
  1553. return;
  1554. }
  1555. NSIndexPath *uikitIndexPath = [self indexPathForNode:node];
  1556. if (uikitIndexPath == nil) {
  1557. return;
  1558. }
  1559. [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[ uikitIndexPath ] batched:NO];
  1560. ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle;
  1561. if (invalidationStyle == ASCollectionViewInvalidationStyleNone) {
  1562. [self setNeedsLayout];
  1563. invalidationStyle = ASCollectionViewInvalidationStyleWithAnimation;
  1564. }
  1565. // If we think we're going to animate, check if this node will prevent it.
  1566. if (invalidationStyle == ASCollectionViewInvalidationStyleWithAnimation) {
  1567. // TODO: Incorporate `shouldAnimateSizeChanges` into ASEnvironmentState for performance benefit.
  1568. static dispatch_once_t onceToken;
  1569. static BOOL (^shouldNotAnimateBlock)(ASDisplayNode *);
  1570. dispatch_once(&onceToken, ^{
  1571. shouldNotAnimateBlock = ^BOOL(ASDisplayNode * _Nonnull node) {
  1572. return (node.shouldAnimateSizeChanges == NO);
  1573. };
  1574. });
  1575. if (ASDisplayNodeFindFirstNode(node, shouldNotAnimateBlock) != nil) {
  1576. // One single non-animated node causes the whole layout update to be non-animated
  1577. invalidationStyle = ASCollectionViewInvalidationStyleWithoutAnimation;
  1578. }
  1579. }
  1580. _nextLayoutInvalidationStyle = invalidationStyle;
  1581. }
  1582. #pragma mark - _ASDisplayView behavior substitutions
  1583. // Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element.
  1584. // Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView.
  1585. - (void)willMoveToWindow:(UIWindow *)newWindow
  1586. {
  1587. BOOL visible = (newWindow != nil);
  1588. ASDisplayNode *node = self.collectionNode;
  1589. if (visible && !node.inHierarchy) {
  1590. [node __enterHierarchy];
  1591. }
  1592. }
  1593. - (void)didMoveToWindow
  1594. {
  1595. BOOL visible = (self.window != nil);
  1596. ASDisplayNode *node = self.collectionNode;
  1597. if (!visible && node.inHierarchy) {
  1598. [node __exitHierarchy];
  1599. }
  1600. // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their
  1601. // their update in the layout pass
  1602. if (![node supportsRangeManagedInterfaceState]) {
  1603. [_rangeController setNeedsUpdate];
  1604. [_rangeController updateIfNeeded];
  1605. }
  1606. // When we aren't visible, we will only fetch up to the visible area. Now that we are visible,
  1607. // we will fetch visible area + leading screens, so we need to check.
  1608. if (visible) {
  1609. [self _checkForBatchFetching];
  1610. }
  1611. }
  1612. #pragma mark ASCALayerExtendedDelegate
  1613. /**
  1614. * UICollectionView inadvertently triggers a -prepareLayout call to its layout object
  1615. * between [super setFrame:] and [self layoutSubviews] during size changes. So we need
  1616. * to get in there and re-measure our nodes before that -prepareLayout call.
  1617. * We can't wait until -layoutSubviews or the end of -setFrame:.
  1618. *
  1619. * @see @p testThatNodeCalculatedSizesAreUpdatedBeforeFirstPrepareLayoutAfterRotation
  1620. */
  1621. - (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds newValue:(CGRect)newBounds
  1622. {
  1623. if (self.collectionViewLayout == nil) {
  1624. return;
  1625. }
  1626. CGSize lastUsedSize = _lastBoundsSizeUsedForMeasuringNodes;
  1627. if (CGSizeEqualToSize(lastUsedSize, newBounds.size)) {
  1628. return;
  1629. }
  1630. _lastBoundsSizeUsedForMeasuringNodes = newBounds.size;
  1631. // Laying out all nodes is expensive.
  1632. // We only need to do this if the bounds changed in the non-scrollable direction.
  1633. // If, for example, a vertical flow layout has its height changed due to a status bar
  1634. // appearance update, we do not need to relayout all nodes.
  1635. // For a more permanent fix to the unsafety mentioned above, see https://github.com/facebook/AsyncDisplayKit/pull/2182
  1636. ASScrollDirection scrollDirection = self.scrollableDirections;
  1637. BOOL fixedVertically = (ASScrollDirectionContainsVerticalDirection(scrollDirection) == NO);
  1638. BOOL fixedHorizontally = (ASScrollDirectionContainsHorizontalDirection(scrollDirection) == NO);
  1639. BOOL changedInNonScrollingDirection = (fixedHorizontally && newBounds.size.width != lastUsedSize.width) || (fixedVertically && newBounds.size.height != lastUsedSize.height);
  1640. if (changedInNonScrollingDirection) {
  1641. [_dataController relayoutAllNodes];
  1642. [_dataController waitUntilAllUpdatesAreCommitted];
  1643. // We need to ensure the size requery is done before we update our layout.
  1644. [self.collectionViewLayout invalidateLayout];
  1645. }
  1646. }
  1647. #pragma mark - UICollectionView dead-end intercepts
  1648. #if ASDISPLAYNODE_ASSERTIONS_ENABLED // Remove implementations entirely for efficiency if not asserting.
  1649. // intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage)
  1650. - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0)
  1651. {
  1652. ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd));
  1653. return NO;
  1654. }
  1655. - (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0)
  1656. {
  1657. ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd));
  1658. }
  1659. #endif
  1660. @end