| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- //
- // ASTableNode.mm
- // AsyncDisplayKit
- //
- // Created by Steven Ramkumar on 11/4/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 "ASTableNode.h"
- #import "ASTableViewInternal.h"
- #import "ASEnvironmentInternal.h"
- #import "ASDisplayNode+Subclasses.h"
- #import "ASDisplayNode+FrameworkPrivate.h"
- #import "ASInternalHelpers.h"
- #import "ASCellNode+Internal.h"
- #import "AsyncDisplayKit+Debug.h"
- #import "ASTableView+Undeprecated.h"
- #pragma mark - _ASTablePendingState
- @interface _ASTablePendingState : NSObject
- @property (weak, nonatomic) id <ASTableDelegate> delegate;
- @property (weak, nonatomic) id <ASTableDataSource> dataSource;
- @property (nonatomic, assign) ASLayoutRangeMode rangeMode;
- @property (nonatomic, assign) BOOL allowsSelection;
- @property (nonatomic, assign) BOOL allowsSelectionDuringEditing;
- @property (nonatomic, assign) BOOL allowsMultipleSelection;
- @property (nonatomic, assign) BOOL allowsMultipleSelectionDuringEditing;
- @property (nonatomic, assign) BOOL inverted;
- @end
- @implementation _ASTablePendingState
- - (instancetype)init
- {
- self = [super init];
- if (self) {
- _rangeMode = ASLayoutRangeModeCount;
- _allowsSelection = YES;
- _allowsSelectionDuringEditing = NO;
- _allowsMultipleSelection = NO;
- _allowsMultipleSelectionDuringEditing = NO;
- _inverted = NO;
- }
- return self;
- }
- @end
- #pragma mark - ASTableView
- @interface ASTableNode ()
- {
- ASDN::RecursiveMutex _environmentStateLock;
- }
- @property (nonatomic, strong) _ASTablePendingState *pendingState;
- @end
- @interface ASTableView ()
- - (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass;
- @end
- @implementation ASTableNode
- #pragma mark Lifecycle
- - (instancetype)_initWithTableView:(ASTableView *)tableView
- {
- // Avoid a retain cycle. In this case, the ASTableView is creating us, and strongly retains us.
- ASTableView * __weak weakTableView = tableView;
- if (self = [super initWithViewBlock:^UIView *{ return weakTableView; }]) {
- __unused __weak ASTableView *view = [self view];
- return self;
- }
- return nil;
- }
- - (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass
- {
- __weak __typeof__(self) weakSelf = self;
- ASDisplayNodeViewBlock tableViewBlock = ^UIView *{
- // Variable will be unused if event logging is off.
- __unused __typeof__(self) strongSelf = weakSelf;
- return [[ASTableView alloc] _initWithFrame:frame style:style dataControllerClass:dataControllerClass eventLog:ASDisplayNodeGetEventLog(strongSelf)];
- };
- if (self = [super initWithViewBlock:tableViewBlock]) {
- return self;
- }
- return nil;
- }
- - (instancetype)initWithStyle:(UITableViewStyle)style
- {
- return [self _initWithFrame:CGRectZero style:style dataControllerClass:nil];
- }
- - (instancetype)init
- {
- return [self _initWithFrame:CGRectZero style:UITableViewStylePlain dataControllerClass:nil];
- }
- #pragma mark ASDisplayNode
- - (void)didLoad
- {
- [super didLoad];
-
- ASTableView *view = self.view;
- view.tableNode = self;
- if (_pendingState) {
- _ASTablePendingState *pendingState = _pendingState;
- self.pendingState = nil;
- view.asyncDelegate = pendingState.delegate;
- view.asyncDataSource = pendingState.dataSource;
- view.inverted = pendingState.inverted;
- view.allowsSelection = pendingState.allowsSelection;
- view.allowsSelectionDuringEditing = pendingState.allowsSelectionDuringEditing;
- view.allowsMultipleSelection = pendingState.allowsMultipleSelection;
- view.allowsMultipleSelectionDuringEditing = pendingState.allowsMultipleSelectionDuringEditing;
- if (pendingState.rangeMode != ASLayoutRangeModeCount) {
- [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
- }
- }
- }
- - (ASTableView *)view
- {
- return (ASTableView *)[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.
- - (ASDataController *)dataController
- {
- return self.view.dataController;
- }
- // TODO: Implement this without the view.
- - (ASRangeController *)rangeController
- {
- return self.view.rangeController;
- }
- - (_ASTablePendingState *)pendingState
- {
- if (!_pendingState && ![self isNodeLoaded]) {
- _pendingState = [[_ASTablePendingState alloc] init];
- }
- ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASTableNode 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], @"ASTableNode 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 <ASTableDelegate>)delegate
- {
- if ([self pendingState]) {
- _pendingState.delegate = delegate;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode 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.
- ASTableView *view = self.view;
- ASPerformBlockOnMainThread(^{
- view.asyncDelegate = delegate;
- });
- }
- }
- - (id <ASTableDelegate>)delegate
- {
- if ([self pendingState]) {
- return _pendingState.delegate;
- } else {
- return self.view.asyncDelegate;
- }
- }
- - (void)setDataSource:(id <ASTableDataSource>)dataSource
- {
- if ([self pendingState]) {
- _pendingState.dataSource = dataSource;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode 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.
- ASTableView *view = self.view;
- ASPerformBlockOnMainThread(^{
- view.asyncDataSource = dataSource;
- });
- }
- }
- - (id <ASTableDataSource>)dataSource
- {
- if ([self pendingState]) {
- return _pendingState.dataSource;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
- return self.view.asyncDataSource;
- }
- }
- - (void)setAllowsSelection:(BOOL)allowsSelection
- {
- if ([self pendingState]) {
- _pendingState.allowsSelection = allowsSelection;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode 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)setAllowsSelectionDuringEditing:(BOOL)allowsSelectionDuringEditing
- {
- if ([self pendingState]) {
- _pendingState.allowsSelectionDuringEditing = allowsSelectionDuringEditing;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
- self.view.allowsSelectionDuringEditing = allowsSelectionDuringEditing;
- }
- }
- - (BOOL)allowsSelectionDuringEditing
- {
- if ([self pendingState]) {
- return _pendingState.allowsSelectionDuringEditing;
- } else {
- return self.view.allowsSelectionDuringEditing;
- }
- }
- - (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection
- {
- if ([self pendingState]) {
- _pendingState.allowsMultipleSelection = allowsMultipleSelection;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode 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;
- }
- }
- - (void)setAllowsMultipleSelectionDuringEditing:(BOOL)allowsMultipleSelectionDuringEditing
- {
- if ([self pendingState]) {
- _pendingState.allowsMultipleSelectionDuringEditing = allowsMultipleSelectionDuringEditing;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
- self.view.allowsMultipleSelectionDuringEditing = allowsMultipleSelectionDuringEditing;
- }
- }
- - (BOOL)allowsMultipleSelectionDuringEditing
- {
- if ([self pendingState]) {
- return _pendingState.allowsMultipleSelectionDuringEditing;
- } else {
- return self.view.allowsMultipleSelectionDuringEditing;
- }
- }
- #pragma mark ASRangeControllerUpdateRangeProtocol
- - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode
- {
- if ([self pendingState]) {
- _pendingState.rangeMode = rangeMode;
- } else {
- ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
- [self.rangeController updateCurrentRangeWithMode:rangeMode];
- }
- }
- #pragma mark ASEnvironment
- ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock)
- #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
- - (void)selectRowAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition
- {
- ASDisplayNodeAssertMainThread();
- ASTableView *tableView = self.view;
- indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
- if (indexPath != nil) {
- [tableView selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition];
- } else {
- NSLog(@"Failed to select row at index path %@ because the row never reached the view.", indexPath);
- }
- }
- - (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated
- {
- ASDisplayNodeAssertMainThread();
- ASTableView *tableView = self.view;
- indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
- if (indexPath != nil) {
- [tableView deselectRowAtIndexPath:indexPath animated:animated];
- } else {
- NSLog(@"Failed to deselect row at index path %@ because the row never reached the view.", indexPath);
- }
- }
- - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated
- {
- ASDisplayNodeAssertMainThread();
- ASTableView *tableView = self.view;
- indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
- if (indexPath != nil) {
- [tableView scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
- } else {
- NSLog(@"Failed to scroll to row at index path %@ because the row never reached the view.", indexPath);
- }
- }
- #pragma mark - Querying Data
- - (void)reloadDataInitiallyIfNeeded
- {
- ASDisplayNodeAssertMainThread();
- if (!self.dataController.initialReloadDataHasBeenCalled) {
- // Note: Just calling reloadData isn't enough here – we need to
- // ensure that _nodesConstrainedWidth is updated first.
- [self.view layoutIfNeeded];
- }
- }
- - (NSInteger)numberOfRowsInSection:(NSInteger)section
- {
- ASDisplayNodeAssertMainThread();
- [self reloadDataInitiallyIfNeeded];
- return [self.dataController numberOfRowsInSection:section];
- }
- - (NSInteger)numberOfSections
- {
- ASDisplayNodeAssertMainThread();
- [self reloadDataInitiallyIfNeeded];
- return [self.dataController numberOfSections];
- }
- - (NSArray<__kindof ASCellNode *> *)visibleNodes
- {
- ASDisplayNodeAssertMainThread();
- return self.isNodeLoaded ? [self.view visibleNodes] : @[];
- }
- - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
- {
- return [self.dataController indexPathForNode:cellNode];
- }
- - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- [self reloadDataInitiallyIfNeeded];
- return [self.dataController nodeAtIndexPath:indexPath];
- }
- - (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- ASDisplayNodeAssertMainThread();
- ASTableView *tableView = self.view;
- indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
- return [tableView rectForRowAtIndexPath:indexPath];
- }
- - (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- ASDisplayNodeAssertMainThread();
- ASTableView *tableView = self.view;
- indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
- if (indexPath == nil) {
- return nil;
- }
- return [tableView cellForRowAtIndexPath:indexPath];
- }
- - (nullable NSIndexPath *)indexPathForSelectedRow
- {
- ASDisplayNodeAssertMainThread();
- ASTableView *tableView = self.view;
- NSIndexPath *indexPath = tableView.indexPathForSelectedRow;
- if (indexPath != nil) {
- return [tableView convertIndexPathToTableNode:indexPath];
- }
- return indexPath;
- }
- - (NSArray<NSIndexPath *> *)indexPathsForSelectedRows
- {
- ASDisplayNodeAssertMainThread();
- ASTableView *tableView = self.view;
- return [tableView convertIndexPathsToTableNode:tableView.indexPathsForSelectedRows];
- }
- - (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point
- {
- ASDisplayNodeAssertMainThread();
- ASTableView *tableView = self.view;
- NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:point];
- if (indexPath != nil) {
- return [tableView convertIndexPathToTableNode:indexPath];
- }
- return indexPath;
- }
- - (nullable NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect
- {
- ASDisplayNodeAssertMainThread();
- ASTableView *tableView = self.view;
- return [tableView convertIndexPathsToTableNode:[tableView indexPathsForRowsInRect:rect]];
- }
- - (NSArray<NSIndexPath *> *)indexPathsForVisibleRows
- {
- ASDisplayNodeAssertMainThread();
- NSMutableArray *indexPathsArray = [NSMutableArray new];
- for (ASCellNode *cell in [self visibleNodes]) {
- NSIndexPath *indexPath = [self indexPathForNode:cell];
- if (indexPath) {
- [indexPathsArray addObject:indexPath];
- }
- }
- return indexPathsArray;
- }
- #pragma mark - Editing
- - (void)reloadDataWithCompletion:(void (^)())completion
- {
- [self.view reloadDataWithCompletion:completion];
- }
- - (void)reloadData
- {
- [self reloadDataWithCompletion:nil];
- }
- - (void)relayoutItems
- {
- [self.view relayoutItems];
- }
- - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion
- {
- [self.view beginUpdates];
- if (updates) {
- updates();
- }
- [self.view endUpdatesAnimated:animated completion:completion];
- }
- - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion
- {
- [self performBatchAnimated:YES updates:updates completion:completion];
- }
- - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
- {
- [self.view insertSections:sections withRowAnimation:animation];
- }
- - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
- {
- [self.view deleteSections:sections withRowAnimation:animation];
- }
- - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
- {
- [self.view reloadSections:sections withRowAnimation:animation];
- }
- - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
- {
- [self.view moveSection:section toSection:newSection];
- }
- - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
- {
- [self.view insertRowsAtIndexPaths:indexPaths withRowAnimation:animation];
- }
- - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
- {
- [self.view deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation];
- }
- - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
- {
- [self.view reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];
- }
- - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
- {
- [self.view moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
- }
- - (void)waitUntilAllUpdatesAreCommitted
- {
- [self.view waitUntilAllUpdatesAreCommitted];
- }
- #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
|