Support editing in table view and collection view

This commit is contained in:
Li Tan
2014-12-15 13:18:14 -08:00
parent 5cb261bbd1
commit f7f5988fcd
23 changed files with 1473 additions and 891 deletions

View File

@@ -17,6 +17,7 @@
051943151A1575670030A7D0 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
052EE06B1A15A0D8002C6279 /* TestResources in Resources */ = {isa = PBXBuildFile; fileRef = 052EE06A1A15A0D8002C6279 /* TestResources */; };
0540322B1A37B69C00001D02 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 0540322A1A37B69C00001D02 /* ASEqualityHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; };
0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */; };
055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -134,8 +135,14 @@
05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; };
1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; };
3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
464052201A3F83C40061C0BA /* ASDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; settings = {ATTRIBUTES = (Public, ); }; };
464052211A3F83C40061C0BA /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; };
464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; };
464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */; };
464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521D1A3F83C40061C0BA /* ASLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; };
464052251A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */; };
464052261A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */; };
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; };
AC3C4A521A1139C100143C57 /* ASCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A501A1139C100143C57 /* ASCollectionView.m */; };
@@ -175,6 +182,7 @@
052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMultiplexImageNodeTests.m; sourceTree = "<group>"; };
052EE06A1A15A0D8002C6279 /* TestResources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = TestResources; sourceTree = "<group>"; };
053011A719B9882B00A9F2D0 /* ASRangeControllerInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerInternal.h; sourceTree = "<group>"; };
0540322A1A37B69C00001D02 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = "<group>"; };
054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBasicImageDownloader.h; sourceTree = "<group>"; };
054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBasicImageDownloader.mm; sourceTree = "<group>"; };
055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageNode.h; sourceTree = "<group>"; };
@@ -269,8 +277,14 @@
05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDealloc2MainObject.h; path = ../Details/ASDealloc2MainObject.h; sourceTree = "<group>"; };
05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = "<group>"; };
05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = "<group>"; };
1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = "<group>"; };
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewTests.m; sourceTree = "<group>"; };
464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDataController.h; sourceTree = "<group>"; };
4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDataController.mm; sourceTree = "<group>"; };
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = "<group>"; };
4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = "<group>"; };
4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = "<group>"; };
4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultidimensionalArrayUtils.h; sourceTree = "<group>"; };
4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultidimensionalArrayUtils.mm; sourceTree = "<group>"; };
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionView.h; sourceTree = "<group>"; };
AC3C4A501A1139C100143C57 /* ASCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionView.m; sourceTree = "<group>"; };
@@ -419,6 +433,13 @@
058D09E1195D050800B7D73C /* Details */ = {
isa = PBXGroup;
children = (
464052191A3F83C40061C0BA /* ASDataController.h */,
4640521A1A3F83C40061C0BA /* ASDataController.mm */,
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */,
4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */,
4640521D1A3F83C40061C0BA /* ASLayoutController.h */,
4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */,
4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */,
05F20AA31A15733C00DCA68A /* ASImageProtocols.h */,
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */,
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
@@ -499,7 +520,7 @@
0516FA3A1A15563400B4EBED /* ASAvailability.h */,
058D0A44195D058D00B7D73C /* ASBaseDefines.h */,
058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */,
1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */,
0540322A1A37B69C00001D02 /* ASEqualityHelpers.h */,
0516FA3B1A15563400B4EBED /* ASLog.h */,
);
path = Base;
@@ -521,13 +542,15 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */,
464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */,
464052201A3F83C40061C0BA /* ASDataController.h in Headers */,
05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */,
0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */,
058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */,
058D0A48195D05CB00B7D73C /* ASControlNode.m in Headers */,
058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */,
058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */,
1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */,
058D0A4B195D05CB00B7D73C /* ASDisplayNode.mm in Headers */,
058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */,
058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */,
@@ -557,6 +580,7 @@
058D0A61195D05DC00B7D73C /* ASTextNodeTextKitHelpers.h in Headers */,
058D0A62195D05DC00B7D73C /* ASTextNodeTextKitHelpers.mm in Headers */,
058D0A63195D05DC00B7D73C /* ASTextNodeTypes.h in Headers */,
464052251A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h in Headers */,
058D0A64195D05DC00B7D73C /* ASTextNodeWordKerner.h in Headers */,
058D0A65195D05DC00B7D73C /* ASTextNodeWordKerner.m in Headers */,
058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */,
@@ -580,6 +604,7 @@
055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */,
054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */,
AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */,
0540322B1A37B69C00001D02 /* ASEqualityHelpers.h in Headers */,
05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */,
058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */,
058D0A72195D05F800B7D73C /* _ASCoreAnimationExtras.h in Headers */,
@@ -723,15 +748,18 @@
058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */,
058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */,
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
464052211A3F83C40061C0BA /* ASDataController.mm in Sources */,
058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */,
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */,
055F1A3519ABD3E3004DAFF1 /* ASTableView.m in Sources */,
464052261A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm in Sources */,
055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */,
058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */,
058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */,
AC3C4A521A1139C100143C57 /* ASCollectionView.m in Sources */,
058D0A20195D050800B7D73C /* ASTextNodeWordKerner.m in Sources */,
058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */,
464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */,
058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */,
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */,

View File

@@ -9,21 +9,12 @@
#import "ASCellNode.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASRangeControllerInternal.h"
#import "ASTextNode.h"
#pragma mark -
#pragma mark ASCellNode
@interface ASCellNode () {
// used by ASRangeController machinery
NSIndexPath *_asyncdisplaykit_indexPath;
}
@end
@implementation ASCellNode
- (instancetype)init
@@ -43,20 +34,6 @@
ASDisplayNodeAssert(!layerBacked, @"ASCellNode does not support layer-backing.");
}
// TODO consider making this property an associated object in ASRangeController.mm
- (NSIndexPath *)asyncdisplaykit_indexPath
{
return _asyncdisplaykit_indexPath;
}
- (void)setAsyncdisplaykit_indexPath:(NSIndexPath *)asyncdisplaykit_indexPath
{
if (_asyncdisplaykit_indexPath == asyncdisplaykit_indexPath)
return;
_asyncdisplaykit_indexPath = [asyncdisplaykit_indexPath copy];
}
@end

View File

@@ -34,13 +34,6 @@
*/
@property (nonatomic, assign) ASRangeTuningParameters rangeTuningParameters;
/**
* @abstract An optional block which can perform custom calculation for working range.
*
* @discussion Can be used to provide custom working range logic for custom layouts.
*/
@property (nonatomic, assign) asrangecontroller_working_range_calculation_block_t workingRangeCalculationBlock;
/**
* Reload everything from scratch, destroying the working range and all cached nodes.
*
@@ -49,30 +42,26 @@
- (void)reloadData;
/**
* WARNING: ASCollectionView's update/editing support is not yet implemented. Use of these methods will fire an assertion.
* Section updating.
*
* This initial version of ASCollectionView only supports appending nodes (see below). If you'd like to see full-fledged
* support for data source updates and interactive editing, please file a GitHub issue -- AsyncDisplayKit can do it,
* we just haven't built it out yet. :]
* All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the UI table
* view will be updated asynchronously. The asyncDataSource must be updated to reflect the changes before these methods are called.
*/
//- (void)insertSections:(NSIndexSet *)sections;
//- (void)deleteSections:(NSIndexSet *)sections;
//- (void)reloadSections:(NSIndexSet *)sections;
//- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
//
//- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths;
//- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths;
//- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths;
//- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
- (void)insertSections:(NSIndexSet *)sections;
- (void)deleteSections:(NSIndexSet *)sections;
- (void)reloadSections:(NSIndexSet *)sections;
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
/**
* Append nodes.
* Items updating.
*
* As with UICollectionView, the asyncDataSource must be updated to reflect the new nodes before this method is called.
*
* @param indexPaths Ordered array of index paths corresponding to the nodes to be added.
* All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the UI table
* view will be updated asynchronously. The asyncDataSource must be updated to reflect the changes before these methods are called.
*/
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths;
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths;
- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths;
- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths;
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
/**
* Query the sized node at `indexPath` for its calculatedSize.

View File

@@ -9,7 +9,9 @@
#import "ASCollectionView.h"
#import "ASAssert.h"
#import "ASFlowLayoutController.h"
#import "ASRangeController.h"
#import "ASDataController.h"
#pragma mark -
@@ -87,11 +89,13 @@ static BOOL _isInterceptedSelector(SEL sel)
#pragma mark -
#pragma mark ASCollectionView.
@interface ASCollectionView () <ASRangeControllerDelegate> {
@interface ASCollectionView () <ASRangeControllerDelegate, ASDataControllerSource> {
_ASCollectionViewProxy *_proxyDataSource;
_ASCollectionViewProxy *_proxyDelegate;
ASDataController *_dataController;
ASRangeController *_rangeController;
ASFlowLayoutController *_layoutController;
}
@end
@@ -105,22 +109,33 @@ static BOOL _isInterceptedSelector(SEL sel)
{
if (!(self = [super initWithFrame:frame collectionViewLayout:layout]))
return nil;
ASDisplayNodeAssert([layout isKindOfClass:UICollectionViewFlowLayout.class], @"only flow layouts are currently supported");
ASFlowLayoutDirection direction = (((UICollectionViewFlowLayout *)layout).scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASFlowLayoutDirectionHorizontal : ASFlowLayoutDirectionVertical;
_layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:direction];
_rangeController = [[ASRangeController alloc] init];
_rangeController.delegate = self;
_rangeController.layoutController = _layoutController;
_dataController = [[ASDataController alloc] init];
_dataController.delegate = _rangeController;
_dataController.dataSource = self;
[self registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"_ASCollectionViewCell"];
return self;
}
#pragma mark -
#pragma mark Overrides.
- (void)reloadData
{
[_rangeController rebuildData];
[_dataController reloadData];
// It is required
[super reloadData];
}
@@ -169,81 +184,59 @@ static BOOL _isInterceptedSelector(SEL sel)
- (ASRangeTuningParameters)rangeTuningParameters
{
return _rangeController.tuningParameters;
return _layoutController.tuningParameters;
}
- (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters
{
_rangeController.tuningParameters = tuningParameters;
}
- (asrangecontroller_working_range_calculation_block_t)workingRangeCalculationBlock
{
return _rangeController.workingRangeCalculationBlock;
}
- (void)setWorkingRangeCalculationBlock:(asrangecontroller_working_range_calculation_block_t)workingRangeCalculationBlock
{
_rangeController.workingRangeCalculationBlock = workingRangeCalculationBlock;
}
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths
{
[_rangeController appendNodesWithIndexPaths:indexPaths];
_layoutController.tuningParameters = tuningParameters;
}
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
return [_rangeController calculatedSizeForNodeAtIndexPath:indexPath];
return [[_dataController nodeAtIndexPath:indexPath] calculatedSize];
}
#pragma mark Assertions.
- (void)throwUnimplementedException
{
[[NSException exceptionWithName:@"UnimplementedException"
reason:@"ASCollectionView's update/editing support is not yet implemented. Please see ASCollectionView.h."
userInfo:nil] raise];
}
- (void)insertSections:(NSIndexSet *)sections
{
[self throwUnimplementedException];
[_dataController insertSections:sections];
}
- (void)deleteSections:(NSIndexSet *)sections
{
[self throwUnimplementedException];
[_dataController deleteSections:sections];
}
- (void)reloadSections:(NSIndexSet *)sections
{
[self throwUnimplementedException];
[_dataController reloadSections:sections];
}
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{
[self throwUnimplementedException];
[_dataController moveSection:section toSection:newSection];
}
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
{
[self throwUnimplementedException];
[_dataController insertRowsAtIndexPaths:indexPaths];
}
- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths
{
[self throwUnimplementedException];
[_dataController deleteRowsAtIndexPaths:indexPaths];
}
- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths
{
[self throwUnimplementedException];
[_dataController reloadRowsAtIndexPaths:indexPaths];
}
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{
[self throwUnimplementedException];
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
}
@@ -255,30 +248,53 @@ static BOOL _isInterceptedSelector(SEL sel)
static NSString *reuseIdentifier = @"_ASCollectionViewCell";
UICollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
[_rangeController configureContentView:cell.contentView forIndexPath:indexPath];
[_rangeController configureContentView:cell.contentView forCellNode:node];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [_rangeController calculatedSizeForNodeAtIndexPath:indexPath];
return [[_dataController nodeAtIndexPath:indexPath] calculatedSize];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return [_rangeController numberOfSizedSections];
return [_dataController numberOfSections];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [_rangeController numberOfSizedRowsInSection:section];
return [_dataController numberOfRowsInSection:section];
}
- (ASScrollDirection)scrollDirection
{
CGPoint scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview];
ASScrollDirection direction = ASScrollDirectionNone;
if (_layoutController.layoutDirection == ASFlowLayoutDirectionHorizontal) {
if (scrollVelocity.x > 0) {
direction = ASScrollDirectionRight;
} else if (scrollVelocity.x < 0) {
direction = ASScrollDirectionLeft;
}
} else {
if (scrollVelocity.y > 0) {
direction = ASScrollDirectionDown;
} else {
direction = ASScrollDirectionUp;
}
}
return direction;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
[_rangeController visibleNodeIndexPathsDidChange];
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) {
[_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
@@ -287,7 +303,7 @@ static BOOL _isInterceptedSelector(SEL sel)
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
[_rangeController visibleNodeIndexPathsDidChange];
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) {
[_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath];
@@ -295,13 +311,48 @@ static BOOL _isInterceptedSelector(SEL sel)
}
#pragma mark - ASDataControllerSource
- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath
{
return [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
}
- (CGSize)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
CGSize restrainedSize = self.bounds.size;
if (_layoutController.layoutDirection == ASFlowLayoutDirectionHorizontal) {
restrainedSize.width = FLT_MAX;
} else {
restrainedSize.height = FLT_MAX;
}
return restrainedSize;
}
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
{
return [_asyncDataSource collectionView:self numberOfItemsInSection:section];
}
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController {
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
return [_asyncDataSource numberOfSectionsInCollectionView:self];
} else {
return 1;
}
}
#pragma mark -
#pragma mark ASRangeControllerDelegate.
- (NSArray *)rangeControllerVisibleNodeIndexPaths:(ASRangeController *)rangeController
{
ASDisplayNodeAssertMainThread();
return [[self indexPathsForVisibleItems] sortedArrayUsingSelector:@selector(compare:)];
return [self indexPathsForVisibleItems];
}
- (CGSize)rangeControllerViewportSize:(ASRangeController *)rangeController
@@ -310,55 +361,44 @@ static BOOL _isInterceptedSelector(SEL sel)
return self.bounds.size;
}
- (NSInteger)rangeControllerSections:(ASRangeController *)rangeController
{
ASDisplayNodeAssertMainThread();
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
return [_asyncDataSource numberOfSectionsInCollectionView:self];
} else {
return 1;
}
- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths {
return [_dataController nodesAtIndexPaths:indexPaths];
}
- (NSInteger)rangeController:(ASRangeController *)rangeController rowsInSection:(NSInteger)section
{
ASDisplayNodeAssertMainThread();
return [_asyncDataSource collectionView:self numberOfItemsInSection:section];
}
- (ASCellNode *)rangeController:(ASRangeController *)rangeController nodeForIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssertNotMainThread();
return [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
}
- (CGSize)rangeController:(ASRangeController *)rangeController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssertNotMainThread();
CGSize contentSize = [self.collectionViewLayout collectionViewContentSize];
CGSize viewSize = self.bounds.size;
CGFloat constrainedWidth = viewSize.width == contentSize.width ? viewSize.width : FLT_MAX;
CGFloat constrainedHeight = viewSize.height == contentSize.height ? viewSize.height : FLT_MAX;
return CGSizeMake(constrainedWidth, constrainedHeight);
}
- (void)rangeController:(ASRangeController *)rangeController didSizeNodesWithIndexPaths:(NSArray *)indexPaths
{
- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths {
ASDisplayNodeAssertMainThread();
[UIView performWithoutAnimation:^{
[self performBatchUpdates:^{
// -insertItemsAtIndexPaths: is insufficient; UICollectionView also needs to be notified of section changes
NSInteger sectionCount = [super numberOfSections];
NSInteger newSectionCount = [_rangeController numberOfSizedSections];
if (newSectionCount > sectionCount) {
NSRange range = NSMakeRange(sectionCount, newSectionCount - sectionCount);
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:range];
[super insertSections:sections];
}
[super insertItemsAtIndexPaths:indexPaths];
} completion:nil];
}];
}
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths {
ASDisplayNodeAssertMainThread();
[UIView performWithoutAnimation:^{
[self performBatchUpdates:^{
[super deleteItemsAtIndexPaths:indexPaths];
} completion:nil];
}];
}
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet {
ASDisplayNodeAssertMainThread();
[UIView performWithoutAnimation:^{
[self performBatchUpdates:^{
[super insertSections:indexSet];
} completion:nil];
}];
}
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet {
ASDisplayNodeAssertMainThread();
[UIView performWithoutAnimation:^{
[self performBatchUpdates:^{
[super deleteSections:indexSet];
} completion:nil];
}];
}
@end

View File

@@ -13,8 +13,8 @@
#import <objc/runtime.h>
#import "_ASAsyncTransaction.h"
#import "_ASPendingState.h"
#import "_ASDisplayView.h"
#import "_ASPendingState.h"
#import "_ASScopeTimer.h"
#import "ASDisplayNodeExtras.h"

View File

@@ -181,8 +181,7 @@
|| alphaInfo == kCGImageAlphaPremultipliedLast;
BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill
|| contentMode == UIViewContentModeScaleAspectFit
|| contentMode == UIViewContentModeCenter;
|| contentMode == UIViewContentModeScaleAspectFit;
CGSize backingSize;
CGRect imageDrawRect;

View File

@@ -41,37 +41,36 @@
*/
- (void)reloadData;
/**
* WARNING: ASTableView's update/editing support is not yet implemented. Use of these methods will fire an assertion.
*
* This initial version of ASTableView only supports appending nodes (see below). If you'd like to see full-fledged
* support for data source updates and interactive editing, please file a GitHub issue -- AsyncDisplayKit can do it,
* we just haven't built it out yet. :]
*/
//- (void)beginUpdates;
//- (void)endUpdates;
//
//- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
//- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
//- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
//- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
//
//- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
//- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
//- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
//- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
//
//- (void)setEditing:(BOOL)editing;
//- (void)setEditing:(BOOL)editing animated:(BOOL)animated;
/**
* Append nodes.
* We don't support the these methods for animation yet.
*
* As with UITableView, the asyncDataSource must be updated to reflect the new nodes before this method is called.
*
* @param indexPaths Ordered array of index paths corresponding to the nodes to be added.
* TODO: support animations.
*/
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths;
- (void)beginUpdates;
- (void)endUpdates;
/**
* Section updating.
*
* All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the UI collection
* view will be updated asynchronously. The asyncDataSource must be updated to reflect the changes before these methods are called.
*/
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
/**
* Row updating.
*
* All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the UI collection
* view will be updated asynchronously. The asyncDataSource must be updated to reflect the changes before these methods are called.
*/
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
@end

View File

@@ -9,6 +9,9 @@
#import "ASTableView.h"
#import "ASAssert.h"
#import "ASDataController.h"
#import "ASFlowLayoutController.h"
#import "ASLayoutController.h"
#import "ASRangeController.h"
@@ -98,10 +101,13 @@ static BOOL _isInterceptedSelector(SEL sel)
#pragma mark -
#pragma mark ASTableView.
@interface ASTableView () <ASRangeControllerDelegate> {
@interface ASTableView () <ASRangeControllerDelegate, ASDataControllerSource> {
_ASTableViewProxy *_proxyDataSource;
_ASTableViewProxy *_proxyDelegate;
ASDataController *_dataController;
ASFlowLayoutController *_layoutController;
ASRangeController *_rangeController;
}
@@ -117,22 +123,22 @@ static BOOL _isInterceptedSelector(SEL sel)
if (!(self = [super initWithFrame:frame style:style]))
return nil;
_layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical];
_rangeController = [[ASRangeController alloc] init];
_rangeController.layoutController = _layoutController;
_rangeController.delegate = self;
_dataController = [[ASDataController alloc] init];
_dataController.dataSource = self;
_dataController.delegate = _rangeController;
return self;
}
#pragma mark -
#pragma mark Overrides.
- (void)reloadData
{
[_rangeController rebuildData];
[super reloadData];
}
- (void)setDataSource:(id<UITableViewDataSource>)dataSource
{
ASDisplayNodeAssert(NO, @"ASTableView uses asyncDataSource, not UITableView's dataSource property.");
@@ -176,19 +182,22 @@ static BOOL _isInterceptedSelector(SEL sel)
}
}
- (void)reloadData
{
[_dataController reloadData];
// It is required
[super reloadData];
}
- (ASRangeTuningParameters)rangeTuningParameters
{
return _rangeController.tuningParameters;
return _layoutController.tuningParameters;
}
- (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters
{
_rangeController.tuningParameters = tuningParameters;
}
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths
{
[_rangeController appendNodesWithIndexPaths:indexPaths];
_layoutController.tuningParameters = tuningParameters;
}
#pragma mark Assertions.
@@ -196,7 +205,7 @@ static BOOL _isInterceptedSelector(SEL sel)
- (void)throwUnimplementedException
{
[[NSException exceptionWithName:@"UnimplementedException"
reason:@"ASTableView's update/editing support is not yet implemented. Please see ASTableView.h."
reason:@"ASTableView's grouped updates aren't currently supported yet, please call the insert/delete function directly."
userInfo:nil] raise];
}
@@ -212,55 +221,44 @@ static BOOL _isInterceptedSelector(SEL sel)
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
[self throwUnimplementedException];
[_dataController insertSections:sections];
}
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
[self throwUnimplementedException];
[_dataController deleteSections:sections];
}
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
[self throwUnimplementedException];
[_dataController reloadSections:sections];
}
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{
[self throwUnimplementedException];
[_dataController moveSection:section toSection:newSection];
}
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
[self throwUnimplementedException];
[_dataController insertRowsAtIndexPaths:indexPaths];
}
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
[self throwUnimplementedException];
[_dataController deleteRowsAtIndexPaths:indexPaths];
}
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
[self throwUnimplementedException];
[_dataController reloadRowsAtIndexPaths:indexPaths];
}
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{
[self throwUnimplementedException];
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
}
- (void)setEditing:(BOOL)editing
{
[self throwUnimplementedException];
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[self throwUnimplementedException];
}
#pragma mark -
#pragma mark Intercepted selectors.
@@ -273,29 +271,56 @@ static BOOL _isInterceptedSelector(SEL sel)
cell = [[_ASTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
}
[_rangeController configureTableViewCell:cell forIndexPath:indexPath];
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
[_rangeController configureContentView:cell.contentView forCellNode:node];
cell.backgroundColor = node.backgroundColor;
cell.selectionStyle = node.selectionStyle;
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [_rangeController calculatedSizeForNodeAtIndexPath:indexPath].height;
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
return node.calculatedSize.height;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [_rangeController numberOfSizedSections];
return [_dataController numberOfSections];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_rangeController numberOfSizedRowsInSection:section];
return [_dataController numberOfRowsInSection:section];
}
- (ASScrollDirection)scrollDirection
{
CGPoint scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview];
ASScrollDirection direction = ASScrollDirectionNone;
if (_layoutController.layoutDirection == ASFlowLayoutDirectionHorizontal) {
if (scrollVelocity.x > 0) {
direction = ASScrollDirectionRight;
} else if (scrollVelocity.x < 0) {
direction = ASScrollDirectionLeft;
}
} else {
if (scrollVelocity.y > 0) {
direction = ASScrollDirectionDown;
} else {
direction = ASScrollDirectionUp;
}
}
return direction;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
[_rangeController visibleNodeIndexPathsDidChange];
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) {
[_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath];
@@ -304,7 +329,7 @@ static BOOL _isInterceptedSelector(SEL sel)
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
{
[_rangeController visibleNodeIndexPathsDidChange];
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)]) {
[_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath];
@@ -321,60 +346,76 @@ static BOOL _isInterceptedSelector(SEL sel)
return [self indexPathsForVisibleRows];
}
- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths {
return [_dataController nodesAtIndexPaths:indexPaths];
}
- (CGSize)rangeControllerViewportSize:(ASRangeController *)rangeController
{
ASDisplayNodeAssertMainThread();
return self.bounds.size;
}
- (NSInteger)rangeControllerSections:(ASRangeController *)rangeController
{
- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths {
ASDisplayNodeAssertMainThread();
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
return [_asyncDataSource numberOfSectionsInTableView:self];
} else {
return 1;
}
}
- (NSInteger)rangeController:(ASRangeController *)rangeController rowsInSection:(NSInteger)section
{
ASDisplayNodeAssertMainThread();
return [_asyncDataSource tableView:self numberOfRowsInSection:section];
}
- (ASCellNode *)rangeController:(ASRangeController *)rangeController nodeForIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssertNotMainThread();
return [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath];
}
- (CGSize)rangeController:(ASRangeController *)rangeController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssertNotMainThread();
return CGSizeMake(self.bounds.size.width, FLT_MAX);
}
- (void)rangeController:(ASRangeController *)rangeController didSizeNodesWithIndexPaths:(NSArray *)indexPaths
{
ASDisplayNodeAssertMainThread();
[UIView performWithoutAnimation:^{
[super beginUpdates];
// -insertRowsAtIndexPaths:: is insufficient; UITableView also needs to be notified of section changes
NSInteger sectionCount = [super numberOfSections];
NSInteger newSectionCount = [_rangeController numberOfSizedSections];
if (newSectionCount > sectionCount) {
NSRange range = NSMakeRange(sectionCount, newSectionCount - sectionCount);
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:range];
[super insertSections:sections withRowAnimation:UITableViewRowAnimationNone];
}
[super insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[super endUpdates];
}];
}
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths {
ASDisplayNodeAssertMainThread();
[UIView performWithoutAnimation:^{
[super beginUpdates];
[super deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[super endUpdates];
}];
}
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet {
ASDisplayNodeAssertMainThread();
[UIView performWithoutAnimation:^{
[super beginUpdates];
[super insertSections:indexSet withRowAnimation:UITableViewRowAnimationNone];
[super endUpdates];
}];
}
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet {
ASDisplayNodeAssertMainThread();
[UIView performWithoutAnimation:^{
[super beginUpdates];
[super deleteSections:indexSet withRowAnimation:UITableViewRowAnimationNone];
[super endUpdates];
}];
}
#pragma mark - ASDataControllerDelegate
- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath {
return [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath];
}
- (CGSize)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(self.bounds.size.width, FLT_MAX);
}
- (NSUInteger)dataController:(ASDataController *)dataControllre rowsInSection:(NSUInteger)section {
return [_asyncDataSource tableView:self numberOfRowsInSection:section];
}
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController {
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
return [_asyncDataSource numberOfSectionsInTableView:self];
} else {
return 1; // default section number
}
}
@end

View File

@@ -867,8 +867,6 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
return [[self _renderer] lineCount];
}
#pragma mark - Truncation Message
- (void)_invalidateTruncationString
{
_composedTruncationString = [self _prepareTruncationStringForDrawing:[self _composedTruncationString]];

View File

@@ -0,0 +1,125 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASDealloc2MainObject.h>
@class ASCellNode;
@class ASDataController;
/**
Data source for data controller
It will be invoked in the same thread as the api call of ASDataController.
*/
@protocol ASDataControllerSource <NSObject>
/**
Fetch the ASCellNode for specific index path.
*/
- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath;
/**
The constrained size for layout.
*/
- (CGSize)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
/**
Fetch the number of rows in specific section.
*/
- (NSUInteger)dataController:(ASDataController *)dataControllre rowsInSection:(NSUInteger)section;
/**
Fetch the number of sections.
*/
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController;
@end
/**
Delegate for notify the data updating of data controller.
These methods will be invoked from main thread right now, but it may be moved to background thread in the future.
*/
@protocol ASDataControllerDelegate <NSObject>
@optional
/**
Called for insertion of elements.
*/
- (void)dataController:(ASDataController *)dataController willInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths;
- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths;
/**
Called for deletion of elements.
*/
- (void)dataController:(ASDataController *)dataController willDeleteNodesAtIndexPaths:(NSArray *)indexPaths;
- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths;
/**
Called for insertion of sections.
*/
- (void)dataController:(ASDataController *)dataController willInsertSectionsAtIndexSet:(NSIndexSet *)indexSet;
- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet;
/**
Called for deletion of sections.
*/
- (void)dataController:(ASDataController *)dataController willDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet;
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet;
@end
/**
* Controller to layout data in background, and managed data updating.
*
* All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the data
* will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called.
* For each data updatin, the corresponding methods in delegate will be called.
*/
@interface ASDataController : ASDealloc2MainObject
/**
Data source for fetching data info.
*/
@property (nonatomic, weak) id<ASDataControllerSource> dataSource;
/**
Delegate to notify when data is updated.
*/
@property (nonatomic, weak) id<ASDataControllerDelegate> delegate;
/** @name Initial loading */
- (void)initialDataLoading;
/** @name Data Updating */
- (void)insertSections:(NSIndexSet *)sections;
- (void)deleteSections:(NSIndexSet *)sections;
- (void)reloadSections:(NSIndexSet *)sections;
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths;
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths;
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths;
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
- (void)reloadData;
/** @name Data Querying */
- (NSUInteger)numberOfSections;
- (NSUInteger)numberOfRowsInSection:(NSUInteger)section;
- (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath;
- (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths;
@end

View File

@@ -0,0 +1,474 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "ASDataController.h"
#import <Foundation/NSProcessInfo.h>
#import "ASAssert.h"
#import "ASCellNode.h"
#import "ASDisplayNode.h"
#import "ASMultidimensionalArrayUtils.h"
#define INSERT_NODES(multidimensionalArray, indexPath, elements) \
{ \
if ([_delegate respondsToSelector:@selector(dataController:willInsertNodes:atIndexPaths:)]) { \
[_delegate dataController:self willInsertNodes:elements atIndexPaths:indexPath]; \
} \
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(multidimensionalArray, indexPath, elements); \
if ([_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:)]) { \
[_delegate dataController:self didInsertNodes:elements atIndexPaths:indexPath]; \
} \
}
#define DELETE_NODES(multidimensionalArray, indexPath) \
{ \
if ([_delegate respondsToSelector:@selector(dataController:willDeleteNodesAtIndexPaths:)]) { \
[_delegate dataController:self willDeleteNodesAtIndexPaths:indexPath]; \
} \
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(multidimensionalArray, indexPath); \
if ([_delegate respondsToSelector:@selector(dataController:didDeleteNodesAtIndexPaths:)]) { \
[_delegate dataController:self didDeleteNodesAtIndexPaths:indexPath]; \
} \
}
#define INSERT_SECTIONS(multidimensionalArray, indexSet, sections) \
{ \
if ([_delegate respondsToSelector:@selector(dataController:willInsertSectionsAtIndexSet:)]) { \
[_delegate dataController:self willInsertSectionsAtIndexSet:indexSet]; \
} \
[_nodes insertObjects:sections atIndexes:indexSet]; \
if ([_delegate respondsToSelector:@selector(dataController:didInsertSectionsAtIndexSet:)]) { \
[_delegate dataController:self didInsertSectionsAtIndexSet:indexSet]; \
} \
}
#define DELETE_SECTIONS(multidimensionalArray, indexSet) \
{ \
if ([_delegate respondsToSelector:@selector(dataController:willDeleteSectionsAtIndexSet:)]) { \
[_delegate dataController:self willDeleteSectionsAtIndexSet:indexSet]; \
} \
[_nodes removeObjectsAtIndexes:indexSet]; \
if ([_delegate respondsToSelector:@selector(dataController:willDeleteSectionsAtIndexSet:)]) { \
[_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet]; \
} \
}
//
// The background update is not fully supported yet, although it is trivial to fix it. The underline
// problem is we need to do the profiling between the main thread updating and background updating,
// and then decided which way to go.
//
// For background update, we could avoid the multi-dimensinonal array operation (insertion / deletion)
// on main thread. However, the sideback is we need to dispatch_sync to lock main thread for data query,
// although it is running on a concurrent queue and should be fast enough.
//
// For main thread update, we need to do the multi-dimensional operations (insertion / deletion) on
// main thread, but we will gain the performance in data query. Considering data query is much more
// frequent than data updating, so we keep it on main thread for the initial version.
//
//
#define ENABLE_BACKGROUND_UPDATE 0
const static NSUInteger kASDataControllerSizingCountPerProcessor = 5;
static void *kASSizingQueueContext = &kASSizingQueueContext;
static void *kASDataUpdatingQueueContext = &kASDataUpdatingQueueContext;
@interface ASDataController () {
NSMutableArray *_nodes;
}
@end
@implementation ASDataController
- (instancetype)init {
if (self = [super init]) {
_nodes = [NSMutableArray array];
}
return self;
}
#pragma mark - Utils
+ (NSUInteger)parallelProcessorCount {
static NSUInteger parallelProcessorCount;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
parallelProcessorCount = [[NSProcessInfo processInfo] processorCount];
});
return parallelProcessorCount;
}
+ (dispatch_queue_t)sizingQueue
{
static dispatch_queue_t sizingQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingQueue = dispatch_queue_create("com.facebook.AsyncDisplayKit.ASDataController.sizingQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(sizingQueue, kASSizingQueueContext, kASSizingQueueContext, NULL);
dispatch_set_target_queue(sizingQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
});
return sizingQueue;
}
+ (BOOL)isSizingQueue {
return kASSizingQueueContext == dispatch_get_specific(kASSizingQueueContext);
}
/**
* Concurrent queue for query / updating the cached data.
* The data query is more frequent than the data updating, so we use dispatch_sync for reading, and dispatch_barrier_async for writing.
*/
+ (dispatch_queue_t)dataUpdatingQueue
{
static dispatch_queue_t dataUpdatingQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dataUpdatingQueue = dispatch_queue_create("com.facebook.AsyncDisplayKit.ASDataController.dataUpdatingQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_set_specific(dataUpdatingQueue, kASDataUpdatingQueueContext, kASDataUpdatingQueueContext, NULL);
});
return dataUpdatingQueue;
}
+ (BOOL)isDataUpdatingQueue {
return kASDataUpdatingQueueContext == dispatch_get_specific(kASDataUpdatingQueueContext);
}
- (void)asyncUpdateDataWithBlock:(dispatch_block_t)block {
#if ENABLE_BACKGROUND_UPDATE
dispatch_barrier_async([ASDataController dataUpdatingQueue], ^{
block();
});
#else
dispatch_async(dispatch_get_main_queue(), ^{
block();
});
#endif
}
- (void)syncUpdateDataWithBlock:(dispatch_block_t)block {
#if ENABLE_BACKGROUND_UPDATE
dispatch_barrier_sync([ASDataController dataUpdatingQueue], ^{
block();
});
#else
dispatch_sync(dispatch_get_main_queue(), ^{
block();
});
#endif
}
- (void)queryDataWithBlock:(dispatch_block_t)block {
#if ENABLE_BACKGROUND_UPDATE
if ([ASDataController isDataUpdatingQueue]) {
block();
} else {
dispatch_sync([ASDataController dataUpdatingQueue], ^{
block();
});
}
#else
ASDisplayNodeAssertMainThread();
block();
#endif
}
#pragma mark - Initial Data Loading
- (void)initialDataLoading {
NSMutableArray *indexPaths = [NSMutableArray array];
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
// insert sections
[self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)]];
for (NSUInteger i = 0; i < sectionNum; i++) {
NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i];
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i];
for (NSUInteger j = 0; j < rowNum; j++) {
[indexPaths addObject:[indexPath indexPathByAddingIndex:j]];
}
}
// insert elements
[self insertRowsAtIndexPaths:indexPaths];
}
#pragma mark - Data Update
- (void)insertSections:(NSIndexSet *)indexSet {
NSMutableArray *sectionArray = [[NSMutableArray alloc] initWithCapacity:indexSet.count];
for (int i = 0; i < indexSet.count; i++) {
[sectionArray addObject:[[NSMutableArray alloc] init]];
}
dispatch_async([[self class] sizingQueue], ^{
[self asyncUpdateDataWithBlock:^{
INSERT_SECTIONS(_nodes , indexSet, sectionArray);
}];
});
}
- (void)deleteSections:(NSIndexSet *)indexSet {
dispatch_async([[self class] sizingQueue], ^{
[self asyncUpdateDataWithBlock:^{
// remove elements
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, indexSet);
DELETE_NODES(_nodes, indexPaths);
DELETE_SECTIONS(_nodes, indexSet);
}];
});
}
- (void)reloadSections:(NSIndexSet *)sections {
// We need to keep data query on data source in the calling thread.
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init];
NSMutableArray *updatedNodes = [[NSMutableArray alloc] init];
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:sections.count];
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
for (NSUInteger i = 0; i < rowNum; i++) {
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
[updatedIndexPaths addObject:indexPath];
[updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
}
}];
dispatch_async([ASDataController sizingQueue], ^{
[self syncUpdateDataWithBlock:^{
// remove elements
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, sections);
DELETE_NODES(_nodes, indexPaths);
}];
// reinsert the elements
[self _insertNodes:updatedNodes atIndexPaths:updatedIndexPaths];
});
}
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection {
dispatch_async([ASDataController sizingQueue], ^{
[self asyncUpdateDataWithBlock:^{
// remove elements
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_nodes, [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_nodes, indexPaths);
DELETE_NODES(_nodes, indexPaths);
// update the section of indexpaths
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection];
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]];
}];
// Don't re-calculate size for moving
INSERT_NODES(_nodes, updatedIndexPaths, nodes);
}];
});
}
- (void)_insertNodes:(NSArray *)nodes
atIndexPaths:(NSArray *)indexPaths {
if (!nodes.count) {
return;
}
dispatch_group_t layoutGroup = dispatch_group_create();
for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) {
NSArray *subIndexPaths = [indexPaths subarrayWithRange:NSMakeRange(j, MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j))];
NSMutableArray *nodeBoundSizes = [[NSMutableArray alloc] initWithCapacity:kASDataControllerSizingCountPerProcessor];
[subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[nodeBoundSizes addObject:[NSValue valueWithCGSize:[_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]]];
}];
dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
ASCellNode *node = nodes[j + idx];
[node measure:[nodeBoundSizes[idx] CGSizeValue]];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
}];
});
}
dispatch_block_t block = ^{
dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER);
[self asyncUpdateDataWithBlock:^{
// updating the cells
INSERT_NODES(_nodes, indexPaths, nodes);
}];
};
if ([ASDataController isSizingQueue]) {
block();
} else {
dispatch_async([ASDataController sizingQueue], block);
}
}
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths {
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
// sort indexPath to avoid messing up the index when inserting in several batches
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
// Processing in batches
for (NSUInteger i = 0; i < sortedIndexPaths.count; i += blockSize) {
NSArray *batchedIndexPaths = [sortedIndexPaths subarrayWithRange:NSMakeRange(i, MIN(sortedIndexPaths.count - i, blockSize))];
NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:batchedIndexPaths.count];
[batchedIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
}];
[self _insertNodes:nodes atIndexPaths:batchedIndexPaths];
}
}
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths {
// sort indexPath in revse order to avoid messing up the index when deleting
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingComparator:^NSComparisonResult(NSIndexPath *obj1, NSIndexPath *obj2) {
return [obj2 compare:obj1];
}];
dispatch_async([ASDataController sizingQueue], ^{
[self asyncUpdateDataWithBlock:^{
DELETE_NODES(_nodes, sortedIndexPaths);
}];
});
}
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths {
// The reloading operation required reloading the data
// Loading data in the calling thread
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
}];
dispatch_async([ASDataController sizingQueue], ^{
[self syncUpdateDataWithBlock:^{
DELETE_NODES(_nodes, nodes);
}];
[self _insertNodes:nodes atIndexPaths:indexPaths];
});
}
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath {
dispatch_async([ASDataController sizingQueue], ^{
[self asyncUpdateDataWithBlock:^{
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_nodes, [NSArray arrayWithObject:indexPath]);
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
DELETE_NODES(_nodes, indexPaths);
// Don't re-calculate size for moving
INSERT_NODES(_nodes, indexPaths, nodes);
}];
});
}
- (void)reloadData {
// Fetching data in calling thread
NSMutableArray *updatedNodes = [[NSMutableArray alloc] init];
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init];
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
for (NSUInteger i = 0; i < sectionNum; i++) {
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i];
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i];
for (NSUInteger j = 0; j < rowNum; j++) {
NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j];
[updatedIndexPaths addObject:indexPath];
[updatedNodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
}
}
dispatch_async([ASDataController sizingQueue], ^{
[self syncUpdateDataWithBlock:^{
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_nodes);
// sort indexPath in reverse order to avoid messing up the index when inserting in several batches
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingComparator:^NSComparisonResult(NSIndexPath *obj1, NSIndexPath *obj2) {
return [obj2 compare:obj1];
}];
DELETE_NODES(_nodes, sortedIndexPaths);
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _nodes.count)];
DELETE_SECTIONS(_nodes, indexSet);
// Insert section
NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:sectionNum];
for (int i = 0; i < sectionNum; i++) {
[sections addObject:[[NSMutableArray alloc] init]];
}
INSERT_SECTIONS(_nodes, [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, sectionNum)], sections);
}];
[self _insertNodes:updatedNodes atIndexPaths:updatedIndexPaths];
});
}
#pragma mark - Data Querying
- (NSUInteger)numberOfSections {
__block NSUInteger sectionNum;
[self queryDataWithBlock:^{
sectionNum = [_nodes count];
}];
return sectionNum;
}
- (NSUInteger)numberOfRowsInSection:(NSUInteger)section {
__block NSUInteger rowNum;
[self queryDataWithBlock:^{
rowNum = [_nodes[section] count];
}];
return rowNum;
}
- (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath {
__block ASCellNode *node;
[self queryDataWithBlock:^{
node = _nodes[indexPath.section][indexPath.row];
}];
return node;
}
- (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths {
__block NSArray *arr = nil;
[self queryDataWithBlock:^{
arr = ASFindElementsInMultidimensionalArrayAtIndexPaths(_nodes, indexPaths);
}];
return arr;
}
@end

View File

@@ -0,0 +1,21 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <AsyncDisplayKit/ASLayoutController.h>
typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) {
ASFlowLayoutDirectionVertical,
ASFlowLayoutDirectionHorizontal,
};
/**
* The controller for flow layout.
*/
@interface ASFlowLayoutController : NSObject <ASLayoutController>
@property (nonatomic, assign) ASRangeTuningParameters tuningParameters;
@property (nonatomic, readonly, assign) ASFlowLayoutDirection layoutDirection;
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection;
@end

View File

@@ -0,0 +1,200 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "ASFlowLayoutController.h"
#include <map>
#include <vector>
#include <cassert>
#import "ASAssert.h"
static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3;
@interface ASFlowLayoutController() {
std::vector<std::vector<CGSize> > _nodeSizes;
std::pair<int, int> _visibleRangeStartPos;
std::pair<int, int> _visibleRangeEndPos;
std::pair<int, int> _workingRangeStartPos;
std::pair<int, int> _workingRangeEndPos;
}
@end
@implementation ASFlowLayoutController
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection {
if (!(self = [super init])) {
return nil;
}
_layoutDirection = layoutDirection;
_tuningParameters.leadingBufferScreenfuls = 2;
_tuningParameters.trailingBufferScreenfuls = 1;
return self;
}
- (void)insertNodesAtIndexPaths:(NSArray *)indexPaths withSizes:(NSArray *)nodeSizes {
ASDisplayNodeAssert(indexPaths.count == nodeSizes.count, @"Inconsistent index paths and node size");
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
std::vector<CGSize> &v = _nodeSizes[indexPath.section];
v.insert(v.begin() + indexPath.row, [(NSValue *)nodeSizes[idx] CGSizeValue]);
}];
}
- (void)deleteNodesAtIndexPaths:(NSArray *)indexPaths {
[indexPaths enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
std::vector<CGSize> &v = _nodeSizes[indexPath.section];
v.erase(v.begin() + indexPath.row);
}];
}
- (void)insertSectionsAtIndexSet:(NSIndexSet *)indexSet {
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
_nodeSizes.insert(_nodeSizes.begin() + idx, std::vector<CGSize>());
}];
}
- (void)deleteSectionsAtIndexSet:(NSIndexSet *)indexSet {
[indexSet enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *stop) {
_nodeSizes.erase(_nodeSizes.begin() +idx);
}];
}
- (BOOL)shouldUpdateWorkingRangesForVisibleIndexPath:(NSArray *)indexPaths
viewportSize:(CGSize)viewportSize {
if (!indexPaths.count) {
return NO;
}
std::pair<int, int> startPos, endPos;
ASFindIndexPathRange(indexPaths, startPos, endPos);
if (_workingRangeStartPos >= startPos || _workingRangeEndPos <= endPos) {
return YES;
}
return ASFlowLayoutDistance(startPos, _visibleRangeStartPos, _nodeSizes) > ASFlowLayoutDistance(_visibleRangeStartPos, _workingRangeStartPos, _nodeSizes) * kASFlowLayoutControllerRefreshingThreshold ||
ASFlowLayoutDistance(endPos, _visibleRangeEndPos, _nodeSizes) > ASFlowLayoutDistance(_visibleRangeEndPos, _workingRangeEndPos, _nodeSizes) * kASFlowLayoutControllerRefreshingThreshold;
}
- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths {
ASFindIndexPathRange(indexPaths, _visibleRangeStartPos, _visibleRangeEndPos);
}
/**
* IndexPath array for the element in the working range.
*/
- (NSSet *)workingRangeIndexPathsForScrolling:(enum ASScrollDirection)scrollDirection
viewportSize:(CGSize)viewportSize {
std::pair<int, int> startIter, endIter;
if (_layoutDirection == ASFlowLayoutDirectionHorizontal) {
ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || scrollDirection == ASScrollDirectionLeft || scrollDirection == ASScrollDirectionRight, @"Invalid scroll direction");
CGFloat backScreens = scrollDirection == ASScrollDirectionLeft ? self.tuningParameters.leadingBufferScreenfuls : self.tuningParameters.trailingBufferScreenfuls;
CGFloat frontScreens = scrollDirection == ASScrollDirectionLeft ? self.tuningParameters.trailingBufferScreenfuls : self.tuningParameters.leadingBufferScreenfuls;
startIter = ASFindIndexForRange(_nodeSizes, _visibleRangeStartPos, - backScreens * viewportSize.width, _layoutDirection);
endIter = ASFindIndexForRange(_nodeSizes, _visibleRangeEndPos, frontScreens * viewportSize.width, _layoutDirection);
} else {
ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || scrollDirection == ASScrollDirectionUp || scrollDirection == ASScrollDirectionDown, @"Invalid scroll direction");
int backScreens = scrollDirection == ASScrollDirectionUp ? self.tuningParameters.leadingBufferScreenfuls : self.tuningParameters.trailingBufferScreenfuls;
int frontScreens = scrollDirection == ASScrollDirectionUp ? self.tuningParameters.trailingBufferScreenfuls : self.tuningParameters.leadingBufferScreenfuls;
startIter = ASFindIndexForRange(_nodeSizes, _visibleRangeStartPos, - backScreens * viewportSize.height, _layoutDirection);
endIter = ASFindIndexForRange(_nodeSizes, _visibleRangeEndPos, frontScreens * viewportSize.height, _layoutDirection);
}
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
while (startIter != endIter) {
[indexPathSet addObject:[NSIndexPath indexPathForRow:startIter.second inSection:startIter.first]];
startIter.second++;
while (startIter.second == _nodeSizes[startIter.first].size() && startIter.first < _nodeSizes.size()) {
startIter.second = 0;
startIter.first++;
}
}
[indexPathSet addObject:[NSIndexPath indexPathForRow:endIter.second inSection:endIter.first]];
return indexPathSet;
}
static void ASFindIndexPathRange(NSArray *indexPaths, std::pair<int, int> &startPos, std::pair<int, int> &endPos) {
NSIndexPath *initialIndexPath = [indexPaths firstObject];
startPos = endPos = {initialIndexPath.section, initialIndexPath.row};
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
std::pair<int, int> p(indexPath.section, indexPath.row);
startPos = MIN(startPos, p);
endPos = MAX(endPos, p);
}];
}
static const std::pair<int, int> ASFindIndexForRange(const std::vector<std::vector<CGSize>> &nodes,
const std::pair<int, int> &pos,
CGFloat range,
ASFlowLayoutDirection layoutDirection) {
std::pair<int, int> cur = pos, pre = pos;
if (range < 0.0 && cur.first >= 0 && cur.second >= 0) {
// search backward
while (range < 0.0 && cur.second >= 0) {
pre = cur;
CGSize size = nodes[cur.first][cur.second];
range += layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height;
cur.second--;
while (cur.second < 0 && cur.first > 0) {
cur.second = (int)nodes[--cur.first].size() - 1;
}
}
if (cur.second < 0) {
cur = pre;
}
} else {
// search forward
while (range > 0.0 && cur.second < nodes[cur.first].size()) {
pre = cur;
CGSize size = nodes[cur.first][cur.second];
range -= layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height;
cur.second++;
while (cur.second == nodes[cur.first].size() && cur.first < (int)nodes.size() - 1) {
cur.second = 0;
cur.first++;
}
}
if (cur.second == nodes[cur.first].size()) {
cur = pre;
}
}
return cur;
}
static int ASFlowLayoutDistance(const std::pair<int, int> &start, const std::pair<int, int> &end, const std::vector<std::vector<CGSize>> &nodes) {
if (start == end) {
return 0;
} else if (start > end) {
return - ASFlowLayoutDistance(end, start, nodes);
}
int res = 0;
for (int i = start.first; i <= end.first; i++) {
res += (i == end.first ? end.second + 1 : nodes[i].size()) - (i == start.first ? start.second : 0);
}
return res;
}
@end

View File

@@ -0,0 +1,42 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
typedef struct {
CGFloat leadingBufferScreenfuls;
CGFloat trailingBufferScreenfuls;
} ASRangeTuningParameters;
typedef NS_ENUM(NSInteger, ASScrollDirection) {
ASScrollDirectionNone,
ASScrollDirectionRight,
ASScrollDirectionLeft,
ASScrollDirectionUp,
ASScrollDirectionDown,
};
@protocol ASLayoutController <NSObject>
/**
* Tuning parameters for the working range.
*
* Defaults to a trailing buffer of one screenful and a leading buffer of two screenfuls.
*/
@property (nonatomic, assign) ASRangeTuningParameters tuningParameters;
- (void)insertNodesAtIndexPaths:(NSArray *)indexPaths withSizes:(NSArray *)nodeSizes;
- (void)deleteNodesAtIndexPaths:(NSArray *)indexPaths;
- (void)insertSectionsAtIndexSet:(NSIndexSet *)indexSet;
- (void)deleteSectionsAtIndexSet:(NSIndexSet *)indexSet;
- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths;
- (BOOL)shouldUpdateWorkingRangesForVisibleIndexPath:(NSArray *)indexPath
viewportSize:(CGSize)viewportSize;
- (NSSet *)workingRangeIndexPathsForScrolling:(enum ASScrollDirection)scrollDirection
viewportSize:(CGSize)viewportSize;
@end

View File

@@ -0,0 +1,46 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
#import "ASBaseDefines.h"
/**
* Helper class for operation on multidimensional array, where the object of array may be an object or an array.
*/
ASDISPLAYNODE_EXTERN_C_BEGIN
/**
* Deep muutable copy of multidimensional array.
* It will recursively do the multiple copy for each subarray.
*/
extern NSObject<NSCopying> *ASMultidimensionalArrayDeepMutableCopy(NSObject<NSCopying> *obj);
/**
* Insert the elements into the mutable multidimensional array at given index paths.
*/
extern void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths, NSArray *elements);
/**
* Delete the elements of the mutable multidimensional array at given index paths
*/
extern void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths);
/**
* Find the elements of the mutable multidimensional array at given index paths.
*/
extern NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths);
/**
* Return all the index paths of mutable multidimensional array at given index set, in ascending order.
*/
extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *MultidimensionalArray, NSIndexSet *indexSet);
/**
* Reteurn all the index paths of mutable multidimensional array, in ascending order.
*/
extern NSArray *ASIndexPathsForMultidimensionalArray(NSArray *MultidimensionalArray);
ASDISPLAYNODE_EXTERN_C_END

View File

@@ -0,0 +1,119 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "ASAssert.h"
#import "ASMultidimensionalArrayUtils.h"
#pragma mark - Internal Methods
static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray,
const NSArray *indexPaths,
NSUInteger &curIdx,
NSIndexPath *curIndexPath,
const NSUInteger dimension,
void (^updateBlock)(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx)) {
if (curIdx == indexPaths.count) {
return;
}
if (curIndexPath.length < dimension - 1) {
for (int i = 0; i < mutableArray.count; i++) {
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray[i], indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock);
}
} else {
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
while (curIdx < indexPaths.count &&
[curIndexPath isEqual:[indexPaths[curIdx] indexPathByRemovingLastIndex]]) {
[indexSet addIndex:[indexPaths[curIdx] indexAtPosition:curIndexPath.length]];
curIdx++;
}
updateBlock(mutableArray, indexSet, curIdx);
}
}
static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray *res) {
if (![obj isKindOfClass:[NSArray class]]) {
[res addObject:curIndexPath];
} else {
NSArray *arr = (NSArray *)obj;
[arr enumerateObjectsUsingBlock:^(NSObject *subObj, NSUInteger idx, BOOL *stop) {
ASRecursivelyFindIndexPathsForMultidimensionalArray(subObj, [curIndexPath indexPathByAddingIndex:idx], res);
}];
}
}
#pragma mark - Public Methods
NSObject<NSCopying> *ASMultidimensionalArrayDeepMutableCopy(NSObject<NSCopying> *obj) {
if ([obj isKindOfClass:[NSArray class]]) {
NSArray *arr = (NSArray *)obj;
NSMutableArray * mutableArr = [NSMutableArray array];
for (NSObject<NSCopying> *elem in arr) {
[mutableArr addObject:ASMultidimensionalArrayDeepMutableCopy(elem)];
}
return mutableArr;
}
return obj;
}
void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths, NSArray *elements) {
ASDisplayNodeCAssert(indexPaths.count == elements.count, @"Inconsistent indexPaths and elements");
if (!indexPaths.count) {
return;
}
NSUInteger curIdx = 0;
NSIndexPath *indexPath = [[NSIndexPath alloc] init];
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray, indexPaths, curIdx, indexPath, [indexPaths[0] length], ^(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx) {
[arr insertObjects:[elements subarrayWithRange:NSMakeRange(idx - indexSet.count, indexSet.count)]
atIndexes:indexSet];
});
}
void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) {
if (!indexPaths.count) {
return;
}
NSUInteger curIdx = 0;
NSIndexPath *indexPath = [[NSIndexPath alloc] init];
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray, indexPaths, curIdx, indexPath, [indexPaths[0] length], ^(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx) {
[arr removeObjectsAtIndexes:indexSet];
});
}
NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) {
NSUInteger curIdx = 0;
NSIndexPath *indexPath = [[NSIndexPath alloc] init];
NSMutableArray *deletedElements = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
if (!indexPaths.count) {
return deletedElements;
}
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray, indexPaths, curIdx, indexPath, [indexPaths[0] length], ^(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx) {
[deletedElements addObjectsFromArray:[arr objectsAtIndexes:indexSet]];
});
return deletedElements;
}
NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet) {
NSMutableArray *res = [[NSMutableArray alloc] initWithCapacity:multidimensionalArray.count];
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray[idx], [NSIndexPath indexPathWithIndex:idx], res);
}];
return res;
}
NSArray *ASIndexPathsForMultidimensionalArray(NSArray *multidimensionalArray) {
NSMutableArray *res = [NSMutableArray arrayWithCapacity:multidimensionalArray.count];
ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray, [[NSIndexPath alloc] init], res);
return res;
}

View File

@@ -9,19 +9,9 @@
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASCellNode.h>
typedef struct {
// working range buffers, on either side of scroll
CGFloat trailingBufferScreenfuls;
CGFloat leadingBufferScreenfuls;
} ASRangeTuningParameters;
typedef NS_ENUM(NSInteger, ASScrollDirection) {
ASScrollDirectionBackward,
ASScrollDirectionForward,
};
typedef NSRange (^asrangecontroller_working_range_calculation_block_t)(ASRangeTuningParameters params, ASScrollDirection scrollDirection, NSRange visibleRange, NSArray *nodeSizes, CGSize viewport);
#import <AsyncDisplayKit/ASDataController.h>
#import <AsyncDisplayKit/ASFlowLayoutController.h>
#import <AsyncDisplayKit/ASLayoutController.h>
@protocol ASRangeControllerDelegate;
@@ -33,85 +23,30 @@ typedef NSRange (^asrangecontroller_working_range_calculation_block_t)(ASRangeTu
* a working range, and is responsible for handling AsyncDisplayKit machinery (sizing cell nodes, enqueueing and
* cancelling their asynchronous layout and display, and so on).
*/
@interface ASRangeController : ASDealloc2MainObject
/**
* Notify the receiver that its delegate's data source has been set or changed. This is like -[UITableView reloadData]
* but drastically more expensive, as it destroys the working range and all cached nodes.
*/
- (void)rebuildData;
@interface ASRangeController : ASDealloc2MainObject <ASDataControllerDelegate>
/**
* Notify the receiver that the visible range has been updated.
*
* @see [ASRangeControllerDelegate rangeControllerVisibleNodeIndexPaths:]
*/
- (void)visibleNodeIndexPathsDidChange;
/**
* ASTableView is only aware of nodes that have already been sized.
*
* Custom ASCellNode implementations are encouraged to have "realistic placeholders", since they can only be onscreen if
* they have enough data for layout. E.g., try setting all subnodes' background colours to [UIColor lightGrayColor].
*/
- (NSInteger)numberOfSizedSections;
- (NSInteger)numberOfSizedRowsInSection:(NSInteger)section;
/**
* Configure the specified UITableViewCell's content view, and apply properties from ASCellNode.
*
* @param cell UITableViewCell to configure.
*
* @param indexPath Index path for the node of interest.
*/
- (void)configureTableViewCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath;
- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(enum ASScrollDirection)scrollDirection;
/**
* Add the sized node for `indexPath` as a subview of `contentView`.
*
* @param contentView UIView to add a (sized) node's view to.
*
* @param indexPath Index path for the node to be added.
* @param node The ASCellNode to be added.
*/
- (void)configureContentView:(UIView *)contentView forIndexPath:(NSIndexPath *)indexPath;
/**
* Query the sized node at `indexPath` for its calculatedSize.
*
* @param indexPath The index path for the node of interest.
*
* TODO: Currently we disallow direct access to ASCellNode outside ASRangeController since touching the node's view can
* break async display. We should expose the node anyway, possibly with an assertion guarding against external
* use of the view property, so ASCellNode can support configuration for UITableViewCell properties (selection
* style, separator style, etc.) and ASTableView can query that data.
*/
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
/**
* Notify the receiver that its data source has been updated to append the specified nodes.
*
* @param indexPaths Array of NSIndexPaths for the newly-sized nodes.
*/
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths;
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node;
/**
* Delegate and ultimate data source. Must not be nil.
*/
@property (nonatomic, weak) id<ASRangeControllerDelegate> delegate;
/**
* Tuning parameters for the working range.
*
* Defaults to a trailing buffer of one screenful and a leading buffer of two screenfuls.
*/
@property (nonatomic, assign) ASRangeTuningParameters tuningParameters;
/**
* @abstract An optional block which can perform custom calculation for working range.
*
* @discussion Can be used to provide custom working range logic for custom layouts.
*/
@property (nonatomic, readwrite, copy) asrangecontroller_working_range_calculation_block_t workingRangeCalculationBlock;
@property (nonatomic, strong) id<ASLayoutController> layoutController;
@end
@@ -136,52 +71,28 @@ typedef NSRange (^asrangecontroller_working_range_calculation_block_t)(ASRangeTu
- (CGSize)rangeControllerViewportSize:(ASRangeController *)rangeController;
/**
* @param rangeController Sender.
*
* @returns The number of total sections.
*
* @discussion <ASTableView> forwards this method to its data source.
* Fetch nodes at specific index paths.
*/
- (NSInteger)rangeControllerSections:(ASRangeController *)rangeController;
- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths;
/**
* @param rangeController Sender.
*
* @param section Section.
*
* @returns The number of rows in `section`.
*
* @discussion <ASTableView> forwards this method to its data source.
* Called for nodes insertion.
*/
- (NSInteger)rangeController:(ASRangeController *)rangeController rowsInSection:(NSInteger)section;
- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths;
/**
* @param rangeController Sender.
*
* @param indexPath Index path for the node of interest.
*
* @returns A new <ASCellNode> corresponding to `indexPath`.
*
* @discussion <ASTableView> forwards this method to its data source.
* Called for nodes deletion.
*/
- (ASCellNode *)rangeController:(ASRangeController *)rangeController nodeForIndexPath:(NSIndexPath *)indexPath;
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths;
/**
* @param rangeController Sender.
*
* @param indexPath Node to be sized.
*
* @returns Sizing constraints for the node at `indexPath`, to be used as an argument to <[ASDisplayNode measure:]>.
* Called for section insertion.
*/
- (CGSize)rangeController:(ASRangeController *)rangeController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet;
/**
* Notifies the receiver that the specified nodes have been sized and are ready for display.
*
* @param rangeController Sender.
*
* @param indexPaths Array of NSIndexPaths for the newly-sized nodes.
* Called for section deletion.
*/
- (void)rangeController:(ASRangeController *)rangeController didSizeNodesWithIndexPaths:(NSArray *)indexPaths;
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet;
@end

View File

@@ -11,7 +11,9 @@
#import "ASAssert.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNodeInternal.h"
#import "ASRangeControllerInternal.h"
#import "ASLayoutController.h"
#import "ASMultiDimensionalArrayUtils.h"
@interface ASDisplayNode (ASRangeController)
@@ -53,170 +55,35 @@
@end
@interface ASRangeController () {
// index path -> node mapping
NSMutableDictionary *_nodes;
// array of boxed CGSizes. _nodeSizes.count == the number of nodes that have been sized
// TODO optimise this, perhaps by making _nodes an array
NSMutableArray *_nodeSizes;
// consumer data source information
NSArray *_sectionCounts;
NSInteger _totalNodeCount;
// used for global <-> section.row mapping. _sectionOffsets[section] is the index at which the section starts
NSArray *_sectionOffsets;
// sized data source information
NSInteger _sizedNodeCount;
// ranges
NSSet *_workingRangeIndexPaths;
NSSet *_workingRangeNodes;
BOOL _queuedRangeUpdate;
ASScrollDirection _scrollDirection;
NSRange _visibleRange;
NSRange _workingRange;
NSMutableOrderedSet *_workingIndexPaths;
}
@end
@implementation ASRangeController
#pragma mark -
#pragma mark Lifecycle.
- (instancetype)init {
if (self = [super init]) {
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_tuningParameters = {
.trailingBufferScreenfuls = 1.0f,
.leadingBufferScreenfuls = 2.0f,
};
_workingRangeIndexPaths = [NSSet set];
}
return self;
}
- (void)dealloc
{
[self teardownAllNodes];
}
- (void)teardownAllNodes
{
for (ASCellNode *node in _nodes.allValues) {
[node removeFromSupernode];
if (node.nodeLoaded)
[node.view removeFromSuperview];
}
[_nodes removeAllObjects];
_nodes = nil;
}
- (void)cancelSizeNextBlock{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(sizeNextBlock) object:nil];
}
+ (dispatch_queue_t)sizingQueue
{
static dispatch_queue_t sizingQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingQueue = dispatch_queue_create("com.facebook.AsyncDisplayKit.ASRangeController.sizingQueue", DISPATCH_QUEUE_CONCURRENT);
// we use the highpri queue to prioritize UI rendering over other async operations
dispatch_set_target_queue(sizingQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
});
return sizingQueue;
}
#pragma mark -
#pragma mark Helpers.
static NSOrderedSet *ASCopySetMinusSet(NSOrderedSet *minuend, NSOrderedSet *subtrahend)
{
NSMutableOrderedSet *difference = [minuend mutableCopy];
[difference minusOrderedSet:subtrahend];
return difference;
}
// useful for debugging: working range, buffer sizes, and visible range
__attribute__((unused)) static NSString *ASWorkingRangeDebugDescription(NSRange workingRange, NSRange visibleRange)
{
NSInteger visibleRangeLastElement = NSMaxRange(visibleRange) - 1;
NSInteger workingRangeLastElement = NSMaxRange(workingRange) - 1;
return [NSString stringWithFormat:@"[%zd(%zd) [%zd, %zd] (%zd)%zd]",
workingRange.location,
visibleRange.location - workingRange.location,
visibleRange.location,
visibleRangeLastElement,
workingRangeLastElement - visibleRangeLastElement,
workingRangeLastElement];
}
#pragma mark NSRange <-> NSIndexPath.
static BOOL ASRangeIsValid(NSRange range)
{
return range.location != NSNotFound && range.length > 0;
}
- (NSIndexPath *)indexPathForIndex:(NSInteger)index
{
ASDisplayNodeAssert(index < _totalNodeCount, @"invalid argument");
for (NSInteger section = _sectionCounts.count - 1; section >= 0; section--) {
NSInteger offset = [_sectionOffsets[section] integerValue];
if (offset <= index) {
return [NSIndexPath indexPathForRow:index - offset inSection:section];
}
}
ASDisplayNodeAssert(NO, @"logic error");
return nil;
}
- (NSArray *)indexPathsForRange:(NSRange)range
{
ASDisplayNodeAssert(ASRangeIsValid(range) && NSMaxRange(range) <= _totalNodeCount, @"invalid argument");
NSMutableArray *result = [NSMutableArray arrayWithCapacity:range.length];
NSIndexPath *indexPath = [self indexPathForIndex:range.location];
for (NSInteger i = range.location; i < NSMaxRange(range); i++) {
[result addObject:indexPath];
if (indexPath.row + 1 >= [_sectionCounts[indexPath.section] integerValue]) {
indexPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section + 1];
} else {
indexPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
}
}
return result;
}
- (NSInteger)indexForIndexPath:(NSIndexPath *)indexPath
{
NSInteger index = [_sectionOffsets[indexPath.section] integerValue] + indexPath.row;
ASDisplayNodeAssert(index < _totalNodeCount, @"invalid argument");
return index;
}
#pragma mark View manipulation.
#pragma mark - View manipulation.
- (void)discardNode:(ASCellNode *)node
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(node, @"invalid argument");
NSInteger index = [self indexForIndexPath:node.asyncdisplaykit_indexPath];
if (NSLocationInRange(index, _workingRange)) {
if ([_workingRangeNodes containsObject:node]) {
// move the node's view to the working range area, so its rendering persists
[self addNodeToWorkingRange:node];
} else {
@@ -236,8 +103,6 @@ static BOOL ASRangeIsValid(NSRange range)
// since this class usually manages large or infinite data sets, the working range
// directly bounds memory usage by requiring redrawing any content that falls outside the range.
[node recursivelyReclaimMemory];
[_workingIndexPaths removeObject:node.asyncdisplaykit_indexPath];
}
- (void)addNodeToWorkingRange:(ASCellNode *)node
@@ -249,8 +114,6 @@ static BOOL ASRangeIsValid(NSRange range)
[node.view removeFromSuperview];
[node recursivelyDisplay];
[_workingIndexPaths addObject:node.asyncdisplaykit_indexPath];
}
- (void)moveNode:(ASCellNode *)node toView:(UIView *)view
@@ -266,63 +129,16 @@ static BOOL ASRangeIsValid(NSRange range)
[CATransaction commit];
}
#pragma mark -
#pragma mark API.
- (void)recalculateDataSourceCounts
- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection
{
// data source information (_sectionCounts, _sectionOffsets, _totalNodeCount) is not currently thread-safe
ASDisplayNodeAssertMainThread();
_scrollDirection = scrollDirection;
NSInteger sections = [_delegate rangeControllerSections:self];
NSMutableArray *sectionCounts = [NSMutableArray arrayWithCapacity:sections];
for (NSInteger section = 0; section < sections; section++) {
sectionCounts[section] = @([_delegate rangeController:self rowsInSection:section]);
}
NSMutableArray *sectionOffsets = [NSMutableArray arrayWithCapacity:sections];
NSInteger offset = 0;
for (NSInteger section = 0; section < sections; section++) {
sectionOffsets[section] = @(offset);
offset += [sectionCounts[section] integerValue];
}
_sectionCounts = sectionCounts;
_sectionOffsets = sectionOffsets;
_totalNodeCount = offset;
}
- (void)rebuildData
{
/*
* teardown
*/
[self teardownAllNodes];
[self cancelSizeNextBlock];
/*
* setup
*/
[self recalculateDataSourceCounts];
_nodes = [NSMutableDictionary dictionaryWithCapacity:_totalNodeCount];
_visibleRange = _workingRange = NSMakeRange(NSNotFound, 0);
_sizedNodeCount = 0;
_nodeSizes = [NSMutableArray array];
_scrollDirection = ASScrollDirectionForward;
_workingIndexPaths = [NSMutableOrderedSet orderedSet];
// don't bother sizing if the data source is empty
if (_totalNodeCount > 0) {
[self sizeNextBlock];
}
}
- (void)visibleNodeIndexPathsDidChange
{
if (_queuedRangeUpdate)
if (_queuedRangeUpdate) {
return;
}
// coalesce these events -- handling them multiple times per runloop is noisy and expensive
_queuedRangeUpdate = YES;
@@ -334,67 +150,50 @@ static BOOL ASRangeIsValid(NSRange range)
- (void)updateVisibleNodeIndexPaths
{
if (!_queuedRangeUpdate) {
return;
}
NSArray *indexPaths = [_delegate rangeControllerVisibleNodeIndexPaths:self];
if (indexPaths.count) {
[self setVisibleRange:NSMakeRange([self indexForIndexPath:[indexPaths firstObject]],
indexPaths.count)];
CGSize viewportSize = [_delegate rangeControllerViewportSize:self];
if ([_layoutController shouldUpdateWorkingRangesForVisibleIndexPath:indexPaths viewportSize:viewportSize]) {
[_layoutController setVisibleNodeIndexPaths:indexPaths];
NSSet *workingRangeIndexPaths = [_layoutController workingRangeIndexPathsForScrolling:_scrollDirection viewportSize:viewportSize];
NSSet *visibleRangeIndexPaths = [NSSet setWithArray:indexPaths];
NSMutableSet *removedIndexPaths = [_workingRangeIndexPaths mutableCopy];
[removedIndexPaths minusSet:workingRangeIndexPaths];
[removedIndexPaths minusSet:visibleRangeIndexPaths];
if (removedIndexPaths.count) {
NSArray *removedNodes = [_delegate rangeController:self nodesAtIndexPaths:[removedIndexPaths allObjects]];
[removedNodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) {
[self removeNodeFromWorkingRange:node];
}];
}
NSMutableSet *addedIndexPaths = [workingRangeIndexPaths mutableCopy];
[addedIndexPaths minusSet:_workingRangeIndexPaths];
[addedIndexPaths minusSet:visibleRangeIndexPaths];
if (addedIndexPaths.count) {
NSArray *addedNodes = [_delegate rangeController:self nodesAtIndexPaths:[addedIndexPaths allObjects]];
[addedNodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) {
[self addNodeToWorkingRange:node];
}];
}
_workingRangeIndexPaths = workingRangeIndexPaths;
_workingRangeNodes = [NSSet setWithArray:[_delegate rangeController:self nodesAtIndexPaths:[workingRangeIndexPaths allObjects]]];
}
_queuedRangeUpdate = NO;
}
- (NSInteger)numberOfSizedSections
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)cellNode
{
// short-circuit if we haven't started sizing
if (_sizedNodeCount == 0)
return 0;
[cellNode recursivelySetDisplaySuspended:NO];
NSIndexPath *lastSizedIndex = [self indexPathForIndex:_sizedNodeCount - 1];
NSInteger sizedSectionCount = lastSizedIndex.section + 1;
ASDisplayNodeAssert(sizedSectionCount <= _sectionCounts.count, @"logic error");
return sizedSectionCount;
}
- (NSInteger)numberOfSizedRowsInSection:(NSInteger)section
{
// short-circuit if we haven't started sizing
if (_sizedNodeCount == 0)
return 0;
if (section > _sectionCounts.count) {
ASDisplayNodeAssert(NO, @"this isn't even a valid section");
return 0;
}
NSIndexPath *lastSizedIndex = [self indexPathForIndex:_sizedNodeCount - 1];
if (section > lastSizedIndex.section) {
ASDisplayNodeAssert(NO, @"this section hasn't been sized yet");
return 0;
} else if (section == lastSizedIndex.section) {
// we're still sizing this section, return the count we have
return lastSizedIndex.row + 1;
} else {
// we've already sized beyond this section, return the full count
return [_sectionCounts[section] integerValue];
}
}
- (void)configureTableViewCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath
{
[self configureContentView:cell.contentView forIndexPath:indexPath];
ASCellNode *node = [self sizedNodeForIndexPath:indexPath];
cell.backgroundColor = node.backgroundColor;
cell.selectionStyle = node.selectionStyle;
}
- (void)configureContentView:(UIView *)contentView forIndexPath:(NSIndexPath *)indexPath
{
ASCellNode *newNode = [self sizedNodeForIndexPath:indexPath];
ASDisplayNodeAssert(newNode, @"this node hasn't been sized yet!");
if (newNode.view.superview == contentView) {
if (cellNode.view.superview == contentView) {
// this content view is already correctly configured
return;
}
@@ -411,269 +210,58 @@ static BOOL ASRangeIsValid(NSRange range)
}
}
[self moveNode:newNode toView:contentView];
[self moveNode:cellNode toView:contentView];
}
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
// TODO add an assertion (here or in ASTableView) that the calculated size isn't bogus (eg must be < tableview width)
ASCellNode *node = [self sizedNodeForIndexPath:indexPath];
return node.calculatedSize;
}
#pragma mark - ASDataControllerDelegete
#pragma mark -
#pragma mark Working range.
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters
{
_tuningParameters = tuningParameters;
if (ASRangeIsValid(_visibleRange)) {
[self recalculateWorkingRange];
}
}
static NSRange ASCalculateWorkingRange(ASRangeTuningParameters params, ASScrollDirection scrollDirection,
NSRange visibleRange, NSArray *nodeSizes, CGSize viewport)
{
ASDisplayNodeCAssert(NSMaxRange(visibleRange) <= nodeSizes.count, @"nodes can't be visible until they're sized");
// extend the visible range by enough nodes to fill at least the requested number of screenfuls
// NB. this logic assumes there is no overlap between nodes. It also doesn't
// take spacing between nodes into account.
CGFloat viewportArea = viewport.width * viewport.height;
CGFloat minUpperBufferSize, minLowerBufferSize;
switch (scrollDirection) {
case ASScrollDirectionBackward:
minUpperBufferSize = viewportArea * params.leadingBufferScreenfuls;
minLowerBufferSize = viewportArea * params.trailingBufferScreenfuls;
break;
case ASScrollDirectionForward:
minUpperBufferSize = viewportArea * params.trailingBufferScreenfuls;
minLowerBufferSize = viewportArea * params.leadingBufferScreenfuls;
break;
}
// "top" buffer (above the screen, if we're scrolling vertically)
NSInteger upperBuffer = 0;
CGFloat upperBufferArea = 0.0f;
for (NSInteger idx = visibleRange.location - 1; idx >= 0 && upperBufferArea < minUpperBufferSize; idx--) {
upperBuffer++;
CGSize nodeSize = [nodeSizes[idx] CGSizeValue];
upperBufferArea += nodeSize.width * nodeSize.height;
}
// "bottom" buffer (below the screen, if we're scrolling vertically)
NSInteger lowerBuffer = 0;
CGFloat lowerBufferArea = 0.0f;
for (NSInteger idx = NSMaxRange(visibleRange); idx < nodeSizes.count && lowerBufferArea < minLowerBufferSize; idx++) {
lowerBuffer++;
CGSize nodeSize = [nodeSizes[idx] CGSizeValue];
lowerBufferArea += nodeSize.width * nodeSize.height;
}
return NSMakeRange(visibleRange.location - upperBuffer,
visibleRange.length + upperBuffer + lowerBuffer);
}
- (void)setVisibleRange:(NSRange)visibleRange
{
if (NSEqualRanges(_visibleRange, visibleRange))
return;
ASDisplayNodeAssert(ASRangeIsValid(visibleRange), @"invalid argument");
NSRange previouslyVisible = ASRangeIsValid(_visibleRange) ? _visibleRange : visibleRange;
_visibleRange = visibleRange;
// figure out where we're going, because that's where the bulk of the working range needs to be
NSInteger scrollDelta = _visibleRange.location - previouslyVisible.location;
if (scrollDelta < 0)
_scrollDirection = ASScrollDirectionBackward;
if (scrollDelta > 0)
_scrollDirection = ASScrollDirectionForward;
[self recalculateWorkingRange];
}
- (void)recalculateWorkingRange
{
NSRange workingRange;
if (self.workingRangeCalculationBlock != NULL) {
workingRange = self.workingRangeCalculationBlock(_tuningParameters,
_scrollDirection,
_visibleRange,
_nodeSizes,
[_delegate rangeControllerViewportSize:self]);
/**
* Dispatch to main thread for updating ranges.
* We are considering to move it to background queue if we could call recursive display in background thread.
*/
- (void)updateOnMainThreadWithBlock:(dispatch_block_t)block {
if ([NSThread isMainThread]) {
block();
} else {
workingRange = ASCalculateWorkingRange(_tuningParameters,
_scrollDirection,
_visibleRange,
_nodeSizes,
[_delegate rangeControllerViewportSize:self]);
}
[self setWorkingRange:workingRange];
}
- (void)setWorkingRange:(NSRange)newWorkingRange
{
if (NSEqualRanges(_workingRange, newWorkingRange))
return;
// the working range is a superset of the visible range, but we only care about offscreen nodes
ASDisplayNodeAssert(NSEqualRanges(_visibleRange, NSIntersectionRange(_visibleRange, newWorkingRange)), @"logic error");
NSOrderedSet *visibleIndexPaths = [NSOrderedSet orderedSetWithArray:[self indexPathsForRange:_visibleRange]];
NSOrderedSet *oldWorkingIndexPaths = ASCopySetMinusSet(_workingIndexPaths, visibleIndexPaths);
NSOrderedSet *newWorkingIndexPaths = ASCopySetMinusSet([NSOrderedSet orderedSetWithArray:[self indexPathsForRange:newWorkingRange]], visibleIndexPaths);
// update bookkeeping for visible nodes; these will be removed from the working range later in -configureContentView::
[_workingIndexPaths minusOrderedSet:visibleIndexPaths];
// evict nodes that have left the working range (i.e., those that are in the old working range but not the new one)
NSOrderedSet *removedIndexPaths = ASCopySetMinusSet(oldWorkingIndexPaths, newWorkingIndexPaths);
for (NSIndexPath *indexPath in removedIndexPaths) {
ASCellNode *node = [self sizedNodeForIndexPath:indexPath];
ASDisplayNodeAssert(node, @"an unsized node should never have entered the working range");
[self removeNodeFromWorkingRange:node];
}
// add nodes that have entered the working range (i.e., those that are in the new working range but not the old one)
NSOrderedSet *addedIndexPaths = ASCopySetMinusSet(newWorkingIndexPaths, oldWorkingIndexPaths);
for (NSIndexPath *indexPath in addedIndexPaths) {
// if a node in the working range is still sizing, the sizing logic will add it to the working range for us later
ASCellNode *node = [self sizedNodeForIndexPath:indexPath];
if (node) {
[self addNodeToWorkingRange:node];
} else {
ASDisplayNodeAssert(_sizedNodeCount != _totalNodeCount, @"logic error");
}
}
_workingRange = newWorkingRange;
}
#pragma mark -
#pragma mark Async sizing.
- (ASCellNode *)sizedNodeForIndexPath:(NSIndexPath *)indexPath
{
if ([self indexForIndexPath:indexPath] >= _sizedNodeCount) {
// this node hasn't been sized yet
return nil;
}
// work around applebug: a UIMutableIndexPath with row r and section s is not considered equal to an NSIndexPath with
// row r and section s, so we cannot use the provided indexPath directly as a dictionary index.
ASCellNode *sizedNode = _nodes[[NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section]];
ASDisplayNodeAssert(sizedNode, @"this node should be sized but doesn't even exist");
ASDisplayNodeAssert([sizedNode.asyncdisplaykit_indexPath isEqual:indexPath], @"this node has the wrong index path");
[sizedNode recursivelySetDisplaySuspended:NO];
return sizedNode;
}
- (void)sizeNextBlock
{
// can't size anything if we don't have a delegate
if (!_delegate)
return;
// concurrently size as many nodes as the CPU allows
static const NSInteger blockSize = [[NSProcessInfo processInfo] processorCount];
NSRange sizingRange = NSMakeRange(_sizedNodeCount, MIN(blockSize, _totalNodeCount - _sizedNodeCount));
// manage sizing on a throwaway background queue; we'll be blocking it
dispatch_async(dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT), ^{
dispatch_group_t group = dispatch_group_create();
NSArray *indexPaths = [self indexPathsForRange:sizingRange];
for (NSIndexPath *indexPath in indexPaths) {
ASCellNode *node = [_delegate rangeController:self nodeForIndexPath:indexPath];
node.asyncdisplaykit_indexPath = indexPath;
_nodes[indexPath] = node;
dispatch_group_async(group, [ASRangeController sizingQueue], ^{
[node measure:[_delegate rangeController:self constrainedSizeForNodeAtIndexPath:indexPath]];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
});
}
// wait for all sizing to finish, then bounce back to main
// TODO consider using a semaphore here -- we currently don't size nodes while updating the working range
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
// update sized node information
_sizedNodeCount = NSMaxRange(sizingRange);
for (NSIndexPath *indexPath in indexPaths) {
ASCellNode *node = _nodes[indexPath];
_nodeSizes[[self indexForIndexPath:indexPath]] = [NSValue valueWithCGSize:node.calculatedSize];
}
ASDisplayNodeAssert(_nodeSizes.count == _sizedNodeCount, @"logic error");
// update the working range
if (ASRangeIsValid(_visibleRange)) {
[self recalculateWorkingRange];
}
// delegateify
[_delegate rangeController:self didSizeNodesWithIndexPaths:indexPaths];
// kick off the next block
if (_sizedNodeCount < _totalNodeCount) {
[self performSelector:@selector(sizeNextBlock) withObject:NULL afterDelay:0];
}
block();
});
});
}
#pragma mark -
#pragma mark Editing.
static BOOL ASIndexPathsAreSequential(NSIndexPath *first, NSIndexPath *second)
{
BOOL row = (second.row == first.row + 1 && second.section == first.section);
BOOL section = (second.row == 0 && second.section == first.section + 1);
return row || section;
}
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths
{
// sanity-check input
// TODO this is proof-of-concept-quality, expand validation when fleshing out update / editing support
NSIndexPath *lastNode = (_totalNodeCount > 0) ? [self indexPathForIndex:_totalNodeCount - 1] : nil;
BOOL indexPathsAreValid = ((lastNode && ASIndexPathsAreSequential(lastNode, [indexPaths firstObject])) ||
[[indexPaths firstObject] isEqual:[NSIndexPath indexPathForRow:0 inSection:0]]);
if (!indexPaths || !indexPaths.count || !indexPathsAreValid) {
ASDisplayNodeAssert(NO, @"invalid argument");
return;
}
// update all the things
void (^updateBlock)() = ^{
BOOL isSizing = (_sizedNodeCount < _totalNodeCount);
NSInteger expectedTotalNodeCount = _totalNodeCount + indexPaths.count;
[self recalculateDataSourceCounts];
ASDisplayNodeAssert(_totalNodeCount == expectedTotalNodeCount, @"data source error");
if (!isSizing) {
// the last sizing pass completely finished, start a new one
[self sizeNextBlock];
}
};
// trampoline to main if necessary, we don't have locks on _sectionCounts / _sectionOffsets / _totalNodeCount
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
updateBlock();
});
} else {
updateBlock();
}
}
- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths {
ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path");
NSMutableArray *nodeSizes = [NSMutableArray arrayWithCapacity:nodes.count];
[nodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) {
[nodeSizes addObject:[NSValue valueWithCGSize:node.calculatedSize]];
}];
[self updateOnMainThreadWithBlock:^{
[_layoutController insertNodesAtIndexPaths:indexPaths withSizes:nodeSizes];
[_delegate rangeController:self didInsertNodesAtIndexPaths:indexPaths];
}];
}
- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths {
[self updateOnMainThreadWithBlock:^{
[_layoutController deleteNodesAtIndexPaths:indexPaths];
[_delegate rangeController:self didDeleteNodesAtIndexPaths:indexPaths];
}];
}
- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet {
[self updateOnMainThreadWithBlock:^{
[_layoutController insertSectionsAtIndexSet:indexSet];
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet];
}];
}
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet {
[self updateOnMainThreadWithBlock:^{
[_layoutController deleteSectionsAtIndexSet:indexSet];
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet];
}];
}
@end

View File

@@ -309,7 +309,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
if (stretchable) {
ASDisplayNodeSetupLayerContentsWithResizableImage(_layer, image);
} else {
_layer.contentsScale = self.contentsScale;
_layer.contentsScale = image.scale;
_layer.contents = (id)image.CGImage;
}
[self didDisplayAsyncLayer:self.asyncLayer];

View File

@@ -392,21 +392,21 @@
- (UIColor *)tintColor
{
_bridge_prologue;
ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes.");
return _getFromViewOnly(tintColor);
_bridge_prologue;
ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes.");
return _getFromViewOnly(tintColor);
}
- (void)setTintColor:(UIColor *)color
{
_bridge_prologue;
ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes.");
_setToViewOnly(tintColor, color);
_bridge_prologue;
ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes.");
_setToViewOnly(tintColor, color);
}
- (void)tintColorDidChange
{
// ignore this, allow subclasses to be notified
// ignore this, allow subclasses to be notified
}
- (CGColorRef)shadowColor

View File

@@ -1,15 +0,0 @@
/* 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 "ASCellNode.h"
@interface ASCellNode (ASRangeController)
@property (nonatomic, copy) NSIndexPath *asyncdisplaykit_indexPath;
@end

View File

@@ -28,6 +28,7 @@
#define ASDisplayNodeAssertImplementedBySubclass() ASDisplayNodeAssertWithSignal(NO, nil, @"This method must be implemented by subclass %@", [self class]);
#define ASDisplayNodeAssertNotInstantiable() ASDisplayNodeAssertWithSignal(NO, nil, @"This class is not instantiable.");
#define ASDisplayNodeAssertNotSupported() ASDisplayNodeAssertWithSignal(NO, nil, @"This method is not supported by class %@", [self class]);
#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssertWithSignal([NSThread isMainThread], nil, @"This method must be called on the main thread")
#define ASDisplayNodeCAssertMainThread() ASDisplayNodeCAssertWithSignal([NSThread isMainThread], nil, @"This function must be called on the main thread")

View File

@@ -79,7 +79,6 @@ static const CGFloat kInnerPadding = 10.0f;
_imageNode.URL = [NSURL URLWithString:[NSString stringWithFormat:@"http://placekitten.com/%zd/%zd",
(NSInteger)roundl(_kittenSize.width),
(NSInteger)roundl(_kittenSize.height)]];
// _imageNode.contentMode = UIViewContentModeCenter;
[self addSubnode:_imageNode];
// lorem ipsum text, plus some nice styling