mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-09 14:10:05 +00:00
* Add scrollToItem: method to node, handle section index paths * Update ASPagerNode.mm to use the node version * Add some docs
489 lines
14 KiB
Plaintext
489 lines
14 KiB
Plaintext
//
|
|
// 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 "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;
|
|
@end
|
|
|
|
@implementation _ASTablePendingState
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_rangeMode = ASLayoutRangeModeCount;
|
|
_allowsSelection = YES;
|
|
_allowsSelectionDuringEditing = NO;
|
|
_allowsMultipleSelection = NO;
|
|
_allowsMultipleSelectionDuringEditing = 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
|
|
{
|
|
ASDisplayNodeViewBlock tableViewBlock = ^UIView *{
|
|
return [[ASTableView alloc] _initWithFrame:frame style:style dataControllerClass:dataControllerClass];
|
|
};
|
|
|
|
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.allowsSelection = pendingState.allowsSelection;
|
|
view.allowsSelectionDuringEditing = pendingState.allowsSelectionDuringEditing;
|
|
view.allowsMultipleSelection = pendingState.allowsMultipleSelection;
|
|
view.allowsMultipleSelectionDuringEditing = pendingState.allowsMultipleSelectionDuringEditing;
|
|
if (pendingState.rangeMode != ASLayoutRangeModeCount) {
|
|
[view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
self.delegate = nil;
|
|
self.dataSource = nil;
|
|
}
|
|
|
|
- (ASTableView *)view
|
|
{
|
|
return (ASTableView *)[super view];
|
|
}
|
|
|
|
- (void)clearContents
|
|
{
|
|
[super clearContents];
|
|
[self.rangeController clearContents];
|
|
}
|
|
|
|
- (void)clearFetchedData
|
|
{
|
|
[super clearFetchedData];
|
|
[self.rangeController clearFetchedData];
|
|
}
|
|
|
|
- (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)setDelegate:(id <ASTableDelegate>)delegate
|
|
{
|
|
if ([self pendingState]) {
|
|
_pendingState.delegate = delegate;
|
|
} else {
|
|
ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
|
|
self.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");
|
|
self.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();
|
|
// TODO: Solve this in a way to be able to remove this restriction (https://github.com/facebook/AsyncDisplayKit/pull/2453#discussion_r84515457)
|
|
ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded before calling selectRowAtIndexPath");
|
|
[self.view selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition];
|
|
}
|
|
|
|
- (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
// TODO: Solve this in a way to be able to remove this restriction (https://github.com/facebook/AsyncDisplayKit/pull/2453#discussion_r84515457)
|
|
ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded before calling deselectRowAtIndexPath");
|
|
[self.view deselectRowAtIndexPath:indexPath animated:animated];
|
|
}
|
|
|
|
- (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
|
|
{
|
|
if (!self.dataController.initialReloadDataHasBeenCalled) {
|
|
[self reloadData];
|
|
}
|
|
}
|
|
|
|
- (NSInteger)numberOfRowsInSection:(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] : @[];
|
|
}
|
|
|
|
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
|
|
{
|
|
return [self.dataController indexPathForNode:cellNode];
|
|
}
|
|
|
|
- (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
[self reloadDataInitiallyIfNeeded];
|
|
return [self.dataController nodeAtIndexPath:indexPath];
|
|
}
|
|
|
|
#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];
|
|
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];
|
|
}
|
|
|
|
@end
|