| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613 |
- //
- // ASCollectionNode.mm
- // AsyncDisplayKit
- //
- // Created by Scott Goodson on 9/5/15.
- //
- // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
- // This source code is licensed under the BSD-style license found in the
- // LICENSE file in the root directory of this source tree. An additional grant
- // of patent rights can be found in the PATENTS file in the same directory.
- //
- #import <AsyncDisplayKit/ASCollectionNode.h>
- #import <AsyncDisplayKit/ASCollectionInternal.h>
- #import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h>
- #import <AsyncDisplayKit/ASDisplayNode+Beta.h>
- #import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
- #import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
- #import <AsyncDisplayKit/ASInternalHelpers.h>
- #import <AsyncDisplayKit/ASCellNode+Internal.h>
- #import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
- #import <AsyncDisplayKit/ASSectionContext.h>
- #import <AsyncDisplayKit/ASCollectionDataController.h>
- #import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
- #import <AsyncDisplayKit/ASThread.h>
- #pragma mark - _ASCollectionPendingState
- @interface _ASCollectionPendingState : NSObject
- @property (weak, nonatomic) id <ASCollectionDelegate> delegate;
- @property (weak, nonatomic) id <ASCollectionDataSource> dataSource;
- @property (nonatomic, assign) ASLayoutRangeMode rangeMode;
- @property (nonatomic, assign) BOOL allowsSelection; // default is YES
- @property (nonatomic, assign) BOOL allowsMultipleSelection; // default is NO
- @property (nonatomic, assign) BOOL inverted; //default is NO
- @end
- @implementation _ASCollectionPendingState
- - (instancetype)init
- {
- self = [super init];
- if (self) {
- _rangeMode = ASLayoutRangeModeCount;
- _allowsSelection = YES;
- _allowsMultipleSelection = NO;
- _inverted = NO;
- }
- return self;
- }
- @end
- // TODO: Add support for tuning parameters in the pending state
- #if 0 // This is not used yet, but will provide a way to avoid creating the view to set range values.
- @implementation _ASCollectionPendingState {
- std::vector<std::vector<ASRangeTuningParameters>> _tuningParameters;
- }
- - (instancetype)init
- {
- self = [super init];
- if (self) {
- _tuningParameters = std::vector<std::vector<ASRangeTuningParameters>> (ASLayoutRangeModeCount, std::vector<ASRangeTuningParameters> (ASLayoutRangeTypeCount));
- _rangeMode = ASLayoutRangeModeCount;
- }
- return self;
- }
- - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
- {
- return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
- }
- - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
- {
- return [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
- }
- - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
- {
- ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters");
- return _tuningParameters[rangeMode][rangeType];
- }
- - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
- {
- ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters");
- _tuningParameters[rangeMode][rangeType] = tuningParameters;
- }
- @end
- #endif
- #pragma mark - ASCollectionNode
- @interface ASCollectionNode ()
- {
- ASDN::RecursiveMutex _environmentStateLock;
- }
- @property (nonatomic) _ASCollectionPendingState *pendingState;
- @end
- @implementation ASCollectionNode
- #pragma mark Lifecycle
- - (instancetype)init
- {
- ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
- UICollectionViewLayout *nilLayout = nil;
- self = [self initWithCollectionViewLayout:nilLayout]; // Will throw an exception for lacking a UICV Layout.
- return nil;
- }
- - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout
- {
- return [self initWithFrame:CGRectZero collectionViewLayout:layout];
- }
- - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
- {
- return [self initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil];
- }
- - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator
- {
- __weak __typeof__(self) weakSelf = self;
- ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{
- // Variable will be unused if event logging is off.
- __unused __typeof__(self) strongSelf = weakSelf;
- return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(strongSelf)];
- };
- if (self = [super initWithViewBlock:collectionViewBlock]) {
- return self;
- }
- return nil;
- }
- #pragma mark ASDisplayNode
- - (void)didLoad
- {
- [super didLoad];
-
- ASCollectionView *view = self.view;
- view.collectionNode = self;
-
- if (_pendingState) {
- _ASCollectionPendingState *pendingState = _pendingState;
- self.pendingState = nil;
- view.asyncDelegate = pendingState.delegate;
- view.asyncDataSource = pendingState.dataSource;
- view.inverted = pendingState.inverted;
- view.allowsSelection = pendingState.allowsSelection;
- view.allowsMultipleSelection = pendingState.allowsMultipleSelection;
- if (pendingState.rangeMode != ASLayoutRangeModeCount) {
- [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
- }
- }
- }
- - (ASCollectionView *)view
- {
- return (ASCollectionView *)[super view];
- }
- - (void)clearContents
- {
- [super clearContents];
- [self.rangeController clearContents];
- }
- - (void)didExitPreloadState
- {
- [super didExitPreloadState];
- [self.rangeController clearPreloadedData];
- }
- - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
- {
- [super interfaceStateDidChange:newState fromState:oldState];
- [ASRangeController layoutDebugOverlayIfNeeded];
- }
- #if ASRangeControllerLoggingEnabled
- - (void)didEnterVisibleState
- {
- [super didEnterVisibleState];
- NSLog(@"%@ - visible: YES", self);
- }
- - (void)didExitVisibleState
- {
- [super didExitVisibleState];
- NSLog(@"%@ - visible: NO", self);
- }
- #endif
- #pragma mark Setter / Getter
- // TODO: Implement this without the view.
- - (ASCollectionDataController *)dataController
- {
- return (ASCollectionDataController *)self.view.dataController;
- }
- // TODO: Implement this without the view.
- - (ASRangeController *)rangeController
- {
- return self.view.rangeController;
- }
- - (_ASCollectionPendingState *)pendingState
- {
- if (!_pendingState && ![self isNodeLoaded]) {
- self.pendingState = [[_ASCollectionPendingState alloc] init];
- }
- ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASCollectionNode should not have a pendingState once it is loaded");
- return _pendingState;
- }
- - (void)setInverted:(BOOL)inverted
- {
- self.transform = inverted ? CATransform3DMakeScale(1, -1, 1) : CATransform3DIdentity;
- if ([self pendingState]) {
- _pendingState.inverted = inverted;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
- self.view.inverted = inverted;
- }
- }
- - (BOOL)inverted
- {
- if ([self pendingState]) {
- return _pendingState.inverted;
- } else {
- return self.view.inverted;
- }
- }
- - (void)setDelegate:(id <ASCollectionDelegate>)delegate
- {
- if ([self pendingState]) {
- _pendingState.delegate = delegate;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
- // Manually trampoline to the main thread. The view requires this be called on main
- // and asserting here isn't an option – it is a common pattern for users to clear
- // the delegate/dataSource in dealloc, which may be running on a background thread.
- // It is important that we avoid retaining self in this block, so that this method is dealloc-safe.
- ASCollectionView *view = self.view;
- ASPerformBlockOnMainThread(^{
- view.asyncDelegate = delegate;
- });
- }
- }
- - (id <ASCollectionDelegate>)delegate
- {
- if ([self pendingState]) {
- return _pendingState.delegate;
- } else {
- return self.view.asyncDelegate;
- }
- }
- - (void)setDataSource:(id <ASCollectionDataSource>)dataSource
- {
- if ([self pendingState]) {
- _pendingState.dataSource = dataSource;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
- // Manually trampoline to the main thread. The view requires this be called on main
- // and asserting here isn't an option – it is a common pattern for users to clear
- // the delegate/dataSource in dealloc, which may be running on a background thread.
- // It is important that we avoid retaining self in this block, so that this method is dealloc-safe.
- ASCollectionView *view = self.view;
- ASPerformBlockOnMainThread(^{
- view.asyncDataSource = dataSource;
- });
- }
- }
- - (id <ASCollectionDataSource>)dataSource
- {
- if ([self pendingState]) {
- return _pendingState.dataSource;
- } else {
- return self.view.asyncDataSource;
- }
- }
- - (void)setAllowsSelection:(BOOL)allowsSelection
- {
- if ([self pendingState]) {
- _pendingState.allowsSelection = allowsSelection;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
- self.view.allowsSelection = allowsSelection;
- }
- }
- - (BOOL)allowsSelection
- {
- if ([self pendingState]) {
- return _pendingState.allowsSelection;
- } else {
- return self.view.allowsSelection;
- }
- }
- - (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection
- {
- if ([self pendingState]) {
- _pendingState.allowsMultipleSelection = allowsMultipleSelection;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
- self.view.allowsMultipleSelection = allowsMultipleSelection;
- }
- }
- - (BOOL)allowsMultipleSelection
- {
- if ([self pendingState]) {
- return _pendingState.allowsMultipleSelection;
- } else {
- return self.view.allowsMultipleSelection;
- }
- }
- #pragma mark - Range Tuning
- - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
- {
- return [self.rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
- }
- - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
- {
- [self.rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
- }
- - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
- {
- return [self.rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType];
- }
- - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
- {
- return [self.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType];
- }
- #pragma mark - Selection
- - (NSArray<NSIndexPath *> *)indexPathsForSelectedItems
- {
- ASDisplayNodeAssertMainThread();
- ASCollectionView *view = self.view;
- return [view convertIndexPathsToCollectionNode:view.indexPathsForSelectedItems];
- }
- - (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition
- {
- ASDisplayNodeAssertMainThread();
- ASCollectionView *collectionView = self.view;
- indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES];
- if (indexPath != nil) {
- [collectionView selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition];
- } else {
- NSLog(@"Failed to select item at index path %@ because the item never reached the view.", indexPath);
- }
- }
- - (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated
- {
- ASDisplayNodeAssertMainThread();
- ASCollectionView *collectionView = self.view;
- indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES];
- if (indexPath != nil) {
- [collectionView deselectItemAtIndexPath:indexPath animated:animated];
- } else {
- NSLog(@"Failed to deselect item at index path %@ because the item never reached the view.", indexPath);
- }
- }
- - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated
- {
- ASDisplayNodeAssertMainThread();
- ASCollectionView *collectionView = self.view;
- indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES];
- if (indexPath != nil) {
- [collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
- } else {
- NSLog(@"Failed to scroll to item at index path %@ because the item never reached the view.", indexPath);
- }
- }
- #pragma mark - Querying Data
- - (void)reloadDataInitiallyIfNeeded
- {
- if (!self.dataController.initialReloadDataHasBeenCalled) {
- [self reloadData];
- }
- }
- - (NSInteger)numberOfItemsInSection:(NSInteger)section
- {
- [self reloadDataInitiallyIfNeeded];
- return [self.dataController numberOfRowsInSection:section];
- }
- - (NSInteger)numberOfSections
- {
- [self reloadDataInitiallyIfNeeded];
- return [self.dataController numberOfSections];
- }
- - (NSArray<__kindof ASCellNode *> *)visibleNodes
- {
- ASDisplayNodeAssertMainThread();
- return self.isNodeLoaded ? [self.view visibleNodes] : @[];
- }
- - (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath
- {
- [self reloadDataInitiallyIfNeeded];
- return [self.dataController nodeAtIndexPath:indexPath];
- }
- - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
- {
- return [self.dataController indexPathForNode:cellNode];
- }
- - (NSArray<NSIndexPath *> *)indexPathsForVisibleItems
- {
- ASDisplayNodeAssertMainThread();
- NSMutableArray *indexPathsArray = [NSMutableArray new];
- for (ASCellNode *cell in [self visibleNodes]) {
- NSIndexPath *indexPath = [self indexPathForNode:cell];
- if (indexPath) {
- [indexPathsArray addObject:indexPath];
- }
- }
- return indexPathsArray;
- }
- - (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point
- {
- ASDisplayNodeAssertMainThread();
- ASCollectionView *collectionView = self.view;
- NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:point];
- if (indexPath != nil) {
- return [collectionView convertIndexPathToCollectionNode:indexPath];
- }
- return indexPath;
- }
- - (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath
- {
- ASDisplayNodeAssertMainThread();
- ASCollectionView *collectionView = self.view;
- indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES];
- if (indexPath == nil) {
- return nil;
- }
- return [collectionView cellForItemAtIndexPath:indexPath];
- }
- - (id<ASSectionContext>)contextForSection:(NSInteger)section
- {
- ASDisplayNodeAssertMainThread();
- return [self.dataController contextForSection:section];
- }
- #pragma mark - Editing
- - (void)registerSupplementaryNodeOfKind:(NSString *)elementKind
- {
- [self.view registerSupplementaryNodeOfKind:elementKind];
- }
- - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion
- {
- [self.view performBatchAnimated:animated updates:updates completion:completion];
- }
- - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion
- {
- [self.view performBatchUpdates:updates completion:completion];
- }
- - (void)waitUntilAllUpdatesAreCommitted
- {
- [self.view waitUntilAllUpdatesAreCommitted];
- }
- - (void)reloadDataWithCompletion:(void (^)())completion
- {
- [self.view reloadDataWithCompletion:completion];
- }
- - (void)reloadData
- {
- [self.view reloadData];
- }
- - (void)relayoutItems
- {
- [self.view relayoutItems];
- }
- - (void)reloadDataImmediately
- {
- [self.view reloadDataImmediately];
- }
- - (void)beginUpdates
- {
- [self.view beginUpdates];
- }
- - (void)endUpdatesAnimated:(BOOL)animated
- {
- [self endUpdatesAnimated:animated completion:nil];
- }
- - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
- {
- [self.view endUpdatesAnimated:animated completion:completion];
- }
- - (void)insertSections:(NSIndexSet *)sections
- {
- [self.view insertSections:sections];
- }
- - (void)deleteSections:(NSIndexSet *)sections
- {
- [self.view deleteSections:sections];
- }
- - (void)reloadSections:(NSIndexSet *)sections
- {
- [self.view reloadSections:sections];
- }
- - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
- {
- [self.view moveSection:section toSection:newSection];
- }
- - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
- {
- [self.view insertItemsAtIndexPaths:indexPaths];
- }
- - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths
- {
- [self.view deleteItemsAtIndexPaths:indexPaths];
- }
- - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths
- {
- [self.view reloadItemsAtIndexPaths:indexPaths];
- }
- - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
- {
- [self.view moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
- }
- #pragma mark - ASRangeControllerUpdateRangeProtocol
- - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode;
- {
- if ([self pendingState]) {
- _pendingState.rangeMode = rangeMode;
- } else {
- [self.rangeController updateCurrentRangeWithMode:rangeMode];
- }
- }
- #pragma mark - ASPrimitiveTraitCollection
- ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock)
- #pragma mark - Debugging (Private)
- - (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
- {
- NSMutableArray<NSDictionary *> *result = [super propertiesForDebugDescription];
- [result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }];
- [result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }];
- return result;
- }
- @end
|