Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Max Gu 2016-02-19 12:59:00 -08:00
commit fa4fc2b6ae
25 changed files with 895 additions and 559 deletions

View File

@ -286,12 +286,13 @@
9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; };
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; };
9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; };
9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; };
9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; };
9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; };
9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; };
9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; };
9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; };
A32FEDD51C501B6A004F642A /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; };
A32FEDD61C501B6A004F642A /* ASTextKitFontSizeAdjuster.m in Sources */ = {isa = PBXBuildFile; fileRef = A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */; };
A32FEDD71C5042C1004F642A /* ASTextKitFontSizeAdjuster.m in Sources */ = {isa = PBXBuildFile; fileRef = A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */; };
A32FEDD51C501B6A004F642A /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; settings = {ATTRIBUTES = (Public, ); }; };
A373200F1C571B730011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; };
A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; };
AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */; };
@ -646,7 +647,7 @@
205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = "<group>"; };
205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = "<group>"; };
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = "<group>"; };
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = "<group>"; };
251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = "<group>"; };
251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = "<group>"; };
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = "<group>"; };
@ -715,10 +716,10 @@
9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStaticLayoutable.h; path = AsyncDisplayKit/Layout/ASStaticLayoutable.h; sourceTree = "<group>"; };
9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackBaselinePositionedLayout.h; sourceTree = "<group>"; };
9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackBaselinePositionedLayout.mm; sourceTree = "<group>"; };
9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitFontSizeAdjuster.mm; path = TextKit/ASTextKitFontSizeAdjuster.mm; sourceTree = "<group>"; };
9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutablePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutablePrivate.h; sourceTree = "<group>"; };
9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewTests.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitFontSizeAdjuster.h; path = TextKit/ASTextKitFontSizeAdjuster.h; sourceTree = "<group>"; };
A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitFontSizeAdjuster.m; path = TextKit/ASTextKitFontSizeAdjuster.m; sourceTree = "<group>"; };
A373200E1C571B050011FC94 /* ASTextNode+Beta.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASTextNode+Beta.h"; sourceTree = "<group>"; };
AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASStaticLayoutSpecSnapshotTests.m; sourceTree = "<group>"; };
AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASChangeSetDataController.h; sourceTree = "<group>"; };
@ -1206,7 +1207,7 @@
257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */,
257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */,
A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */,
A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */,
9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */,
257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */,
257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */,
2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */,
@ -1470,6 +1471,7 @@
B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */,
34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */,
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */,
B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */,
254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */,
509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */,
@ -1810,7 +1812,6 @@
257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */,
0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */,
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
A32FEDD61C501B6A004F642A /* ASTextKitFontSizeAdjuster.m in Sources */,
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */,
AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */,
@ -1824,6 +1825,7 @@
ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */,
AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */,
205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */,
9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */,
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
@ -1910,6 +1912,7 @@
509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */,
254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */,
DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */,
9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */,
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
@ -1945,7 +1948,6 @@
9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */,
9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
A32FEDD71C5042C1004F642A /* ASTextKitFontSizeAdjuster.m in Sources */,
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */,
34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */,
254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */,

View File

@ -252,7 +252,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_superIsPendingDataLoad = YES;
[super reloadData];
});
[_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion];
[_dataController reloadDataWithCompletion:completion];
}
- (void)reloadData
@ -264,7 +264,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
{
ASDisplayNodeAssertMainThread();
_superIsPendingDataLoad = YES;
[_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone];
[_dataController reloadDataImmediately];
[super reloadData];
}
@ -451,7 +451,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{
ASDisplayNodeAssertMainThread();
[_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone];
[_dataController moveSection:section toSection:newSection];
}
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
@ -475,7 +475,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{
ASDisplayNodeAssertMainThread();
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone];
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
}
- (NSString *)__reuseIdentifierForKind:(NSString *)kind
@ -486,14 +486,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
#pragma mark -
#pragma mark Intercepted selectors.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
_ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kCellReuseIdentifier forIndexPath:indexPath];
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
cell.node = node;
[_rangeController configureContentView:cell.contentView forCellNode:node];
return cell;
_superIsPendingDataLoad = NO;
return [_dataController numberOfSections];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [_dataController numberOfRowsInSection:section];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
@ -510,17 +511,61 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return view;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
_superIsPendingDataLoad = NO;
return [_dataController numberOfSections];
_ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kCellReuseIdentifier forIndexPath:indexPath];
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
cell.node = node;
[_rangeController configureContentView:cell.contentView forCellNode:node];
if (ASRunningOnOS7()) {
// Even though UICV was introduced in iOS 6, and UITableView has always had the equivalent method,
// -willDisplayCell: was not introduced until iOS 8 for UICV. didEndDisplayingCell, however, is available.
[self collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
}
return cell;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
return [_dataController numberOfRowsInSection:section];
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) {
[_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
}
ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath];
if (cellNode.neverShowPlaceholders) {
[cellNode recursivelyEnsureDisplaySynchronously:YES];
}
[_cellsForVisibilityUpdates addObject:cell];
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]) {
ASCellNode *node = ((_ASCollectionViewCell *)cell).node;
ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath];
}
[_cellsForVisibilityUpdates removeObject:cell];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) {
[_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath];
}
#pragma clang diagnostic pop
}
#pragma mark -
#pragma mark Scroll Direction.
- (ASScrollDirection)scrollDirection
{
CGPoint scrollVelocity;
@ -582,39 +627,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return scrollableDirection;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) {
[_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
}
ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath];
if (cellNode.neverShowPlaceholders) {
[cellNode recursivelyEnsureDisplaySynchronously:YES];
}
[_cellsForVisibilityUpdates addObject:cell];
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]) {
ASCellNode *node = ((_ASCollectionViewCell *)cell).node;
ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath];
}
[_cellsForVisibilityUpdates removeObject:cell];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) {
[_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath];
}
#pragma clang diagnostic pop
}
- (void)layoutSubviews
{
if (_zeroContentInsets) {
@ -650,7 +662,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0)
);
[self handleBatchFetchScrollingToOffset:*targetContentOffset];
if (targetContentOffset != NULL) {
[self handleBatchFetchScrollingToOffset:*targetContentOffset];
}
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
@ -763,19 +777,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
if (_asyncDelegateImplementsInsetSection) {
sectionInset = [(id<ASCollectionViewDelegateFlowLayout>)_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section];
}
if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) {
constrainedSize.min.width = MAX(0, constrainedSize.min.width - sectionInset.left - sectionInset.right);
//ignore insets for FLT_MAX so FLT_MAX can be compared against
if (constrainedSize.max.width - FLT_EPSILON < FLT_MAX) {
constrainedSize.max.width = MAX(0, constrainedSize.max.width - sectionInset.left - sectionInset.right);
}
} else {
constrainedSize.min.height = MAX(0, constrainedSize.min.height - sectionInset.top - sectionInset.bottom);
//ignore insets for FLT_MAX so FLT_MAX can be compared against
if (constrainedSize.max.height - FLT_EPSILON < FLT_MAX) {
constrainedSize.max.height = MAX(0, constrainedSize.max.height - sectionInset.top - sectionInset.bottom);
}
constrainedSize.min.height = MAX(0, constrainedSize.min.height - sectionInset.top - sectionInset.bottom);
constrainedSize.min.width = MAX(0, constrainedSize.min.width - sectionInset.left - sectionInset.right);
//ignore insets for FLT_MAX so FLT_MAX can be compared against
if (constrainedSize.max.width - FLT_EPSILON < FLT_MAX) {
constrainedSize.max.width = MAX(0, constrainedSize.max.width - sectionInset.left - sectionInset.right);
}
if (constrainedSize.max.height - FLT_EPSILON < FLT_MAX) {
constrainedSize.max.height = MAX(0, constrainedSize.max.height - sectionInset.top - sectionInset.bottom);
}
return constrainedSize;
@ -960,6 +970,46 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
}
}
- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES];
[_batchUpdateBlocks addObject:^{
[super reloadItemsAtIndexPaths:indexPaths];
}];
} else {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
[UIView performWithoutAnimation:^{
[super reloadItemsAtIndexPaths:indexPaths];
}];
}
}
- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:YES];
[_batchUpdateBlocks addObject:^{
[super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
}];
} else {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:NO];
[UIView performWithoutAnimation:^{
[super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
}];
}
}
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
@ -980,6 +1030,26 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
}
}
- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES];
[_batchUpdateBlocks addObject:^{
[super reloadSections:indexSet];
}];
} else {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
[UIView performWithoutAnimation:^{
[super reloadSections:indexSet];
}];
}
}
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
@ -1000,6 +1070,43 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
}
}
- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:YES];
[_batchUpdateBlocks addObject:^{
[super moveSection:fromIndex toSection:toIndex];
}];
} else {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:NO];
[UIView performWithoutAnimation:^{
[super moveSection:fromIndex toSection:toIndex];
}];
}
}
- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_batchUpdateBlocks addObject:^{
[super reloadData];
}];
} else {
[UIView performWithoutAnimation:^{
[super reloadData];
}];
}
}
#pragma mark - ASCellNodeDelegate
- (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged

View File

@ -8,6 +8,11 @@
#import "ASContextTransitioning.h"
ASDISPLAYNODE_EXTERN_C_BEGIN
void ASPerformBlockOnMainThread(void (^block)());
void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORITY_DEFAULT
ASDISPLAYNODE_EXTERN_C_END
@interface ASDisplayNode (Beta)
+ (BOOL)usesImplicitHierarchyManagement;

View File

@ -219,7 +219,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
[self _setDownloadIdentifier:nil];
if (_cacheSupportsClearing) {
if (_cacheSupportsClearing && self.loadedImageIdentifier != nil) {
[_cache clearFetchedImageFromCacheWithURL:[_dataSource multiplexImageNode:self URLForImageIdentifier:self.loadedImageIdentifier]];
}

View File

@ -303,10 +303,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)reloadDataWithCompletion:(void (^)())completion
{
ASPerformBlockOnMainThread(^{
[super reloadData];
});
[_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion];
[_dataController reloadDataWithCompletion:completion];
}
- (void)reloadData
@ -317,8 +314,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)reloadDataImmediately
{
ASDisplayNodeAssertMainThread();
[_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone];
[super reloadData];
[_dataController reloadDataImmediately];
}
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
@ -433,7 +429,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{
ASDisplayNodeAssertMainThread();
[_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone];
[_dataController moveSection:section toSection:newSection];
}
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
@ -457,7 +453,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{
ASDisplayNodeAssertMainThread();
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone];
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
}
#pragma mark -
@ -637,8 +633,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0),
scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0)
);
[self handleBatchFetchScrollingToOffset:*targetContentOffset];
if (targetContentOffset != NULL) {
[self handleBatchFetchScrollingToOffset:*targetContentOffset];
}
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
@ -834,6 +832,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
}
}
- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
LOG(@"UITableView reloadRows:%ld rows", indexPaths.count);
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
ASPerformBlockWithoutAnimation(preventAnimation, ^{
[super reloadRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions];
});
if (_automaticallyAdjustsContentOffset) {
[self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:YES];
}
}
- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
[self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
}
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
@ -850,6 +878,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
});
}
- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
LOG(@"UITableView reloadSections:%@", indexSet);
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
ASPerformBlockWithoutAnimation(preventAnimation, ^{
[super reloadSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions];
});
}
- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex
{
ASDisplayNodeAssertMainThread();
LOG(@"UITableView moveSection:%@", indexSet);
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
[super moveSection:fromIndex toSection:toIndex];
}
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
@ -865,6 +923,17 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
});
}
- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{
ASDisplayNodeAssertMainThread();
LOG(@"UITableView reloadData");
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
[super reloadData];
}
#pragma mark - ASDataControllerDelegate
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath {

View File

@ -10,9 +10,14 @@
@interface ASTextNode ()
/**
@abstract The minimum scale that the textnode can apply to fit long words.
@default 0 (No scaling)
@abstract An array of descending scale factors that will be applied to this text node to try to make it fit within its constrained size
@default nil (no scaling)
*/
@property (nonatomic, assign) CGFloat minimumScaleFactor;
@property (nonatomic, copy) NSArray *pointSizeScaleFactors;
/**
@abstract The currently applied scale factor, or 0 if the text node is not being scaled.
*/
@property (nonatomic, assign, readonly) CGFloat currentScaleFactor;
@end

View File

@ -18,6 +18,7 @@
#import "ASTextKitCoreTextAdditions.h"
#import "ASTextKitHelpers.h"
#import "ASTextKitFontSizeAdjuster.h"
#import "ASTextKitRenderer.h"
#import "ASTextKitRenderer+Positioning.h"
#import "ASTextKitShadower.h"
@ -242,7 +243,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
.lineBreakMode = _truncationMode,
.maximumNumberOfLines = _maximumNumberOfLines,
.exclusionPaths = _exclusionPaths,
.minimumScaleFactor = _minimumScaleFactor,
.pointSizeScaleFactors = _pointSizeScaleFactors,
.currentScaleFactor = self.currentScaleFactor,
};
}
@ -255,6 +257,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
// expensive, and can take some time, so we dispatch onto a bg queue to
// actually dealloc.
__block ASTextKitRenderer *renderer = _renderer;
ASPerformBlockOnBackgroundThread(^{
renderer = nil;
});
@ -335,7 +338,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[self setNeedsDisplay];
});
return [[self _renderer] size];
CGSize size = [[self _renderer] size];
// the renderer computes the current scale factor during sizing, so let's grab it here
_currentScaleFactor = _renderer.currentScaleFactor;
return size;
}
#pragma mark - Modifying User Text
@ -376,6 +382,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
}
});
// reset the scale factor if we get a new string.
_currentScaleFactor = 0;
if (attributedString.length > 0) {
CGFloat screenScale = ASScreenScale();
self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale;
@ -1059,16 +1067,15 @@ static NSAttributedString *DefaultTruncationAttributedString()
return visibleRange.length < _attributedString.length;
}
- (void)setMinimumScaleFactor:(CGFloat)minimumScaleFactor
- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors
{
if (_minimumScaleFactor != minimumScaleFactor) {
_minimumScaleFactor = minimumScaleFactor;
if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) {
_pointSizeScaleFactors = pointSizeScaleFactors;
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
}
}
}}
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
{

View File

@ -10,9 +10,9 @@
@protocol ASVideoNodeDelegate;
// If you need ASVideoNode, please use AsyncDisplayKit master until this comment is removed.
// As of 1.9.6, ASVideoNode accidentally triggers creating the AVPlayerLayer even before playing
// the video. Using a lot of them intended to show static frame placeholders will be slow.
// This is a relatively new component of AsyncDisplayKit. It has many useful features, but
// there is room for further expansion and optimization. Please report any issues or requests
// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues
@interface ASVideoNode : ASControlNode
@property (atomic, strong, readwrite) AVAsset *asset;

View File

@ -46,11 +46,7 @@
if (!(self = [super init])) {
return nil;
}
#if DEBUG
NSLog(@"*** Warning: ASVideoNode is a new component - the 1.9.6 version may cause performance hiccups.");
#endif
_previewQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
self.playButton = [[ASDefaultPlayButton alloc] init];

View File

@ -131,7 +131,7 @@
[_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions];
[_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions];
} else {
[super moveSection:section toSection:newSection withAnimationOptions:animationOptions];
[super moveSection:section toSection:newSection];
}
}
@ -174,7 +174,7 @@
[_changeSet deleteItems:@[indexPath] animationOptions:animationOptions];
[_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions];
} else {
[super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions];
[super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
}
}

View File

@ -49,37 +49,27 @@
[self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}
}
- (void)willReloadData
{
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind];
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
NSArray *editingNodes = [self editingNodesOfKind:kind];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
[self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil];
// Insert each section
// Insert sections
NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind];
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[NSMutableArray array]];
}
[self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil];
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
self.editingNode[kind] = sections;
[self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:kind withCompletion:nil];
}];
[_pendingNodes removeObjectForKey:kind];
[_pendingIndexPaths removeObjectForKey:kind];
}];
[_pendingNodes removeAllObjects];
[_pendingIndexPaths removeAllObjects];
}
- (void)prepareForInsertSections:(NSIndexSet *)sections
@ -91,9 +81,6 @@
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}
}
@ -104,23 +91,22 @@
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil];
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
[self insertSections:sectionArray ofKind:kind atIndexSet:sections];
[self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:kind withCompletion:nil];
}];
[_pendingNodes removeObjectForKey:kind];
[_pendingIndexPaths removeObjectForKey:kind];
}];
[_pendingNodes removeAllObjects];
[_pendingIndexPaths removeAllObjects];
}
- (void)willDeleteSections:(NSIndexSet *)sections
{
for (NSString *kind in [self supplementaryKinds]) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
[self deleteSectionsOfKind:kind atIndexSet:sections completion:nil];
[self deleteSectionsOfKind:kind atIndexSet:sections];
[self commitChangesToNodesOfKind:kind withCompletion:nil];
}
}
@ -132,40 +118,31 @@
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}
}
- (void)willReloadSections:(NSIndexSet *)sections
{
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// reinsert the elements
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
// clear sections
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
self.editingNode[kind][idx] = [[NSMutableArray alloc] init];
}];
// reinsert the elements
[self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:kind withCompletion:nil];
}];
[_pendingNodes removeObjectForKey:kind];
[_pendingIndexPaths removeObjectForKey:kind];
}];
[_pendingNodes removeAllObjects];
[_pendingIndexPaths removeAllObjects];
}
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
{
for (NSString *kind in [self supplementaryKinds]) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// 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]]];
}];
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
[self moveSection:section ofKind:kind toSection:newSection];
[self commitChangesToNodesOfKind:kind withCompletion:nil];
}
}

View File

@ -14,16 +14,7 @@
@interface ASDataController (Subclasses)
#pragma mark - Internal editing & completed store querying
/**
* Provides a collection of index paths for nodes of the given kind that are currently in the editing store
*/
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind;
/**
* Read-only access to the underlying editing nodes of the given kind
*/
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind;
@property (nonatomic, strong, readonly) NSMutableDictionary *editingNode;
/**
* Read only access to the underlying completed nodes of the given kind
@ -35,7 +26,7 @@
/**
* Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`.
*/
- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock;
- (void)layoutAndInsertFromNodeBlocks:(NSArray<ASCellNodeBlock> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths completion:(void (^)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths))completionBlock;
/*
* Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes.
@ -53,24 +44,34 @@
#pragma mark - Node & Section Insertion/Deletion API
/**
* Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes.
* Inserts the given nodes of the specified kind into the backing store.
*/
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock;
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths;
/**
* Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes.
* Deletes the given nodes of the specified kind in the backing store.
*/
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock;
- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths;
/**
* Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished.
* Inserts the given sections of the specified kind in the backing store.
*/
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock;
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet;
/**
* Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished.
* Deletes the given sections of the specified kind in the backing store.
*/
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock;
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet;
/**
* Moves the given section of the specified kind in the backing store.
*/
- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection;
/**
* Commit the change for insert/delete node or sections to the backing store, calling completion on the main thread when finished.
*/
- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock;
#pragma mark - Data Manipulation Hooks

View File

@ -91,16 +91,41 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/
- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
Called for reload of elements.
*/
- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
Called for movement of elements.
*/
- (void)dataController:(ASDataController *)dataController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;
/**
Called for insertion of sections.
*/
- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray<NSArray<ASCellNode *> *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
Called for deletion of sections.
*/
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
Called for reload of sections.
*/
- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
Called for movement of sections.
*/
- (void)dataController:(ASDataController *)dataController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex;
/**
Called for reload data.
*/
- (void)dataControllerDidReloadData:(ASDataController *)dataController;
@end
/**
@ -137,14 +162,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled;
/** @name Initial loading
*
* @discussion This method allows choosing an animation style for the first load of content. It is typically used just once,
* for example in viewWillAppear:, to specify an animation option for the information already present in the asyncDataSource.
*/
- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/** @name Data Updating */
- (void)beginUpdates;
@ -159,7 +176,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
@ -175,11 +192,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/
- (void)relayoutAllNodes;
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion;
- (void)reloadDataWithCompletion:(void (^)())completion;
- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
- (void)reloadDataImmediately;
/** @name Data Querying */

View File

@ -30,7 +30,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
static void *kASSizingQueueContext = &kASSizingQueueContext;
@interface ASDataController () {
NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available.
NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable.
NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes.
@ -42,9 +41,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
BOOL _asyncDataFetchingEnabled;
BOOL _delegateDidInsertNodes;
BOOL _delegateDidReloadNodes;
BOOL _delegateDidDeleteNodes;
BOOL _delegateDidMoveNode;
BOOL _delegateDidInsertSections;
BOOL _delegateDidDeleteSections;
BOOL _delegateDidReloadSections;
BOOL _delegateDidMoveSection;
BOOL _delegateDidReloadData;
}
@property (atomic, assign) NSUInteger batchUpdateCounter;
@ -92,8 +96,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later.
_delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)];
_delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)];
_delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)];
_delegateDidReloadNodes = [_delegate respondsToSelector:@selector(dataController:didReloadNodes:atIndexPaths:withAnimationOptions:)];
_delegateDidMoveNode = [_delegate respondsToSelector:@selector(dataController:didMoveNodeAtIndexPath:toIndexPath:)];
_delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSectionsAtIndexSet:withAnimationOptions:)];
_delegateDidReloadSections = [_delegate respondsToSelector:@selector(dataController:didReloadSectionsAtIndexSet:withAnimationOptions:)];
_delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)];
_delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)];
_delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)];
}
+ (NSUInteger)parallelProcessorCount
@ -110,17 +119,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Cell Layout
- (void)batchLayoutNodes:(NSArray<ASCellNodeBlock> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths completion:(void (^)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths))completionBlock
- (void)layoutAndInsertFromNodeBlocks:(NSArray<ASCellNodeBlock> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths completion:(void (^)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths))completionBlock
{
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
// Processing in batches
for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) {
NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize));
NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange];
NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange];
[self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock];
}
[self _layoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths];
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
}];
}
- (void)layoutLoadedNodes:(NSArray<ASCellNode *> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths {
@ -144,42 +150,47 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
}
/**
* Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store.
*/
- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
// Insert finished nodes into data storage
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}
- (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{
if (!nodes.count) {
return;
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
return;
}
NSUInteger nodeCount = nodes.count;
NSMutableArray<ASCellNode *> *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount];
NSMutableArray<ASCellNode *> *allocatedNodes = [NSMutableArray<ASCellNode *> arrayWithCapacity:nodeCount];
dispatch_group_t layoutGroup = dispatch_group_create();
ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodeCount);
for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) {
NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j);
__block NSArray *subarray;
// Allocate nodes concurrently.
dispatch_block_t allocationBlock = ^{
for (NSUInteger k = j; k < j + batchCount; k++) {
__strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(batchCount, sizeof(ASCellNode *));
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(batchCount, queue, ^(size_t i) {
unsigned long k = j + i;
ASCellNodeBlock cellBlock = nodes[k];
ASCellNode *node = cellBlock();
ASDisplayNodeAssertNotNil(node, @"Node block created nil node");
[allocatedNodes addObject:node];
allocatedNodeBuffer[i] = node;
if (!node.isNodeLoaded) {
nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]];
}
});
subarray = [[NSArray alloc] initWithObjects:allocatedNodeBuffer count:batchCount];
// Nil out buffer indexes to allow arc to free the stored cells.
for (int i = 0; i < batchCount; i++) {
allocatedNodeBuffer[i] = nil;
}
free(allocatedNodeBuffer);
};
if (ASDisplayNodeThreadIsMain()) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@ -187,14 +198,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[self layoutLoadedNodes:[allocatedNodes subarrayWithRange:NSMakeRange(j, batchCount)] ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]];
[self layoutLoadedNodes:subarray ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]];
} else {
allocationBlock();
[_mainSerialQueue performBlockOnMainThread:^{
[self layoutLoadedNodes:[allocatedNodes subarrayWithRange:NSMakeRange(j, batchCount)] ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]];
[self layoutLoadedNodes:subarray ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]];
}];
}
[allocatedNodes addObjectsFromArray:subarray];
dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSUInteger k = j; k < j + batchCount; k++) {
ASCellNode *node = allocatedNodes[k];
@ -223,178 +236,84 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - External Data Querying + Editing
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths
{
if (indexPaths.count == 0)
return;
LOG(@"insertNodes:%@ ofKind:%@", nodes, kind);
NSMutableArray *editingNodes = _editingNodes[kind];
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes);
_editingNodes[kind] = editingNodes;
// Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads.
NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes);
[_mainSerialQueue performBlockOnMainThread:^{
_completedNodes[kind] = completedNodes;
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
}];
}
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths
{
if (indexPaths.count == 0) {
return;
return @[];
}
LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind]));
LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@,", indexPaths, kind);
NSMutableArray *editingNodes = _editingNodes[kind];
NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths);
_editingNodes[kind] = editingNodes;
[_mainSerialQueue performBlockOnMainThread:^{
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
}];
return deletedNodes;
}
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock
{
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet{
if (indexSet.count == 0)
return;
LOG(@"insertSections:%@ ofKind:%@", sections, kind);
if (_editingNodes[kind] == nil) {
_editingNodes[kind] = [NSMutableArray array];
}
[_editingNodes[kind] insertObjects:sections atIndexes:indexSet];
// Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads.
NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections);
[_mainSerialQueue performBlockOnMainThread:^{
[_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet];
if (completionBlock) {
completionBlock(sections, indexSet);
}
}];
}
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet
{
if (indexSet.count == 0)
return;
LOG(@"deleteSectionsOfKind:%@", kind);
[_editingNodes[kind] removeObjectsAtIndexes:indexSet];
}
- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection
{
NSArray *movedSection = _editingNodes[kind][section];
[_editingNodes[kind] removeObjectAtIndex:section];
[_editingNodes[kind] insertObject:movedSection atIndex:newSection];
}
- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock
{
NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_editingNodes[kind]);
[_mainSerialQueue performBlockOnMainThread:^{
[_completedNodes[kind] removeObjectsAtIndexes:indexSet];
_completedNodes[kind] = completedNodes;
if (completionBlock) {
completionBlock(indexSet);
completionBlock();
}
}];
}
#pragma mark - Internal Data Querying + Editing
#pragma mark - Reload (External API)
/**
* Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes.
*
* @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy
* of the editing nodes. The delegate is invoked on the main thread.
*/
- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)reloadDataWithCompletion:(void (^)())completion
{
[self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
[self _reloadDataSynchronously:NO completion:completion];
}
/**
* Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed.
*
* @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread.
* Once the backing stores are consistent, the delegate is invoked on the main thread.
*/
- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)reloadDataImmediately
{
[self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
[self _reloadDataSynchronously:YES completion:nil];
}
/**
* Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate.
*
* @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted
* in the completed store on the main thread. The delegate is invoked on the main thread.
*/
- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) {
if (_delegateDidInsertSections)
[_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
}];
}
/**
* Removes sections at the given indicies from the backing store and notifies the delegate.
*
* @discussion Section array are first removed from the editing store, then the associated section in the completed
* store is removed on the main thread. The delegate is invoked on the main thread.
*/
- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) {
if (_delegateDidDeleteSections)
[_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
}];
}
#pragma mark - Initial Load & Full Reload (External API)
- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
[self accessDataSourceWithBlock:^{
NSMutableArray *indexPaths = [NSMutableArray array];
NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self];
// insert sections
[self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0];
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 withAnimationOptions:animationOptions];
}];
}];
}
- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion
{
[self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion];
}
- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil];
}
- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion
- (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())completion
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
@ -402,39 +321,35 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[self accessDataSourceSynchronously:synchronously withBlock:^{
NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self];
NSMutableArray *updatedNodes = [NSMutableArray array];
NSMutableArray *updatedNodeBlocks = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
[self _populateFromEntireDataSourceWithMutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths];
// Allow subclasses to perform setup before going into the edit transaction
[self prepareForReloadData];
void (^transactionBlock)() = ^{
LOG(@"Edit Transaction - reloadData");
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
[self willReloadData];
// Insert each section
// Insert sections
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]];
}
[self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions];
_editingNodes[ASDataControllerRowNodeKind] = sections;
[self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
if (completion) {
dispatch_async(dispatch_get_main_queue(), completion);
}
[self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidReloadData) {
[_delegate dataControllerDidReloadData:self];
}
if (completion) {
completion();
}
}];
}];
};
if (synchronously) {
@ -533,15 +448,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
if (_batchUpdateCounter == 0) {
LOG(@"endUpdatesWithCompletion - beginning");
[_editingTransactionQueue addOperationWithBlock:^{
[_mainSerialQueue performBlockOnMainThread:^{
// Deep copy _completedNodes to _externalCompletedNodes.
// Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate.
_externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]);
LOG(@"endUpdatesWithCompletion - begin updates call to delegate");
[_delegate dataControllerBeginUpdates:self];
}];
[_mainSerialQueue performBlockOnMainThread:^{
[_delegate dataControllerBeginUpdates:self];
}];
// Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates.
@ -552,15 +460,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
block();
}];
[_pendingEditCommandBlocks removeAllObjects];
[_editingTransactionQueue addOperationWithBlock:^{
[_mainSerialQueue performBlockOnMainThread:^{
// Now that the transaction is done, _completedNodes can be accessed externally again.
_externalCompletedNodes = nil;
LOG(@"endUpdatesWithCompletion - calling delegate end");
[_delegate dataController:self endUpdatesAnimated:animated completion:completion];
}];
[_mainSerialQueue performBlockOnMainThread:^{
[_delegate dataController:self endUpdatesAnimated:animated completion:completion];
}];
}
}
@ -592,9 +494,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{
NSMutableArray *updatedNodes = [NSMutableArray array];
NSMutableArray *updatedNodeBlocks = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths];
[self prepareForInsertSections:sections];
@ -607,8 +509,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[sectionArray addObject:[NSMutableArray array]];
}
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
[self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
[self insertSections:sectionArray ofKind:ASDataControllerRowNodeKind atIndexSet:sections];
[self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidInsertSections)
[_delegate dataController:self didInsertSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}];
}];
}];
}];
@ -626,10 +534,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// remove elements
LOG(@"Edit Transaction - deleteSections: %@", sections);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
[self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections];
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidDeleteSections)
[_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}];
}];
}
@ -643,29 +553,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{
NSMutableArray *updatedNodes = [NSMutableArray array];
NSMutableArray *updatedNodeBlocks = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths];
[self prepareForReloadSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{
[self willReloadSections:sections];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]));
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// clear sections
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
_editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init];
}];
// reinsert the elements
[self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
[self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidReloadSections)
[_delegate dataController:self didReloadSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}];
}];
}];
}];
}
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
@ -675,24 +588,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{
[self willMoveSection:section toSection:newSection];
// remove elements
LOG(@"Edit Transaction - moveSection");
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// update the section of indexpaths
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection];
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) {
[updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]];
}
// Don't re-calculate size for moving
[self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
[self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection];
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidMoveSection) {
[_delegate dataController:self didMoveSection:section toSection:newSection];
}
}];
}];
}];
}
@ -760,7 +663,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
[self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self layoutAndInsertFromNodeBlocks:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}];
}];
}];
}];
@ -780,7 +688,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - deleteRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
NSArray *deletedNodes = [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:sortedIndexPaths];
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:deletedNodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}];
}];
}
@ -795,20 +707,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source.
[self accessDataSourceWithBlock:^{
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
// FIXME: This doesn't currently do anything
// FIXME: Shouldn't deletes be sorted in descending order?
[indexPaths sortedArrayUsingSelector:@selector(compare:)];
for (NSIndexPath *indexPath in indexPaths) {
[nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]];
[nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]];
}
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths];
[self layoutAndInsertFromNodeBlocks:nodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidReloadNodes)
[_delegate dataController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}];
}];
}];
}];
@ -854,7 +771,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}];
}
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
@ -864,26 +781,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]);
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]];
// Don't re-calculate size for moving
NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath];
[self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions];
[self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]];
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidMoveNode) {
[_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath];
}
}];
}];
}];
}
#pragma mark - Data Querying (Subclass API)
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind
{
return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil;
}
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind
{
return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array];
- (NSMutableDictionary *)editingNode{
return _editingNodes;
}
- (NSMutableArray *)completedNodesOfKind:(NSString *)kind
@ -950,11 +864,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]);
}
/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise.
- (NSArray *)completedNodes
{
ASDisplayNodeAssertMainThread();
return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind];
return _completedNodes[ASDataControllerRowNodeKind];
}
#pragma mark - Dealloc

View File

@ -163,6 +163,30 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Called for nodes reload.
*
* @param rangeController Sender.
*
* @param nodes Inserted nodes.
*
* @param indexPaths Index path of reloaded nodes.
*
* @param animationOptions Animation options. See ASDataControllerAnimationOptions.
*/
- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Called for movement of node.
*
* @param rangeController Sender.
*
* @param fromIndexPath Index path of moved node before the movement.
*
* @param toIndexPath Index path of moved node after the movement.
*/
- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;
/**
* Called for section insertion.
*
@ -174,6 +198,17 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Called for section reload.
*
* @param rangeController Sender.
*
* @param indexSet Index set of reloaded sections.
*
* @param animationOptions Animation options. See ASDataControllerAnimationOptions.
*/
- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Called for section deletion.
*
@ -185,6 +220,24 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Called for movement of section.
*
* @param rangeController Sender.
*
* @param fromIndex Index of moved section before the movement.
*
* @param toIndex Index of moved section after the movement.
*/
- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex;
/**
* Called for reload data.
*
* @param rangeController Sender.
*/
- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController;
@end
NS_ASSUME_NONNULL_END

View File

@ -361,15 +361,30 @@
});
}
- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions{
ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path");
ASPerformBlockOnMainThread(^{
_rangeIsValid = NO;
[_delegate rangeController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
});
}
- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections");
ASPerformBlockOnMainThread(^{
_rangeIsValid = NO;
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
});
}
- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASPerformBlockOnMainThread(^{
_rangeIsValid = NO;
[_delegate rangeController:self didReloadSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
});
}
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASPerformBlockOnMainThread(^{
@ -378,4 +393,11 @@
});
}
@end
- (void)dataControllerDidReloadData:(ASDataController *)dataController{
ASPerformBlockOnMainThread(^{
_rangeIsValid = NO;
[_delegate rangeControllerDidReloadData:self];
});
}
@end

View File

@ -28,6 +28,8 @@ CGFloat ASCeilPixelValue(CGFloat f);
CGFloat ASRoundPixelValue(CGFloat f);
BOOL ASRunningOnOS7();
ASDISPLAYNODE_EXTERN_C_END
/**

View File

@ -93,6 +93,16 @@ CGFloat ASRoundPixelValue(CGFloat f)
return roundf(f * ASScreenScale()) / ASScreenScale();
}
BOOL ASRunningOnOS7()
{
static BOOL isOS7 = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
isOS7 = ([[UIDevice currentDevice].systemVersion floatValue] < 8.0);
});
return isOS7;
}
@implementation NSIndexPath (ASInverseComparison)
- (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath

View File

@ -58,6 +58,7 @@ struct ASTextKitAttributes {
NSLineBreakMode lineBreakMode;
/**
The maximum number of lines to draw in the drawable region. Leave blank or set to 0 to define no maximum.
This is required to apply scale factors to shrink text to fit within a number of lines
*/
NSUInteger maximumNumberOfLines;
/**
@ -82,9 +83,13 @@ struct ASTextKitAttributes {
*/
CGFloat shadowRadius;
/**
The minimum scale that the textnode can apply to fit long words in constrained size.
An array of scale factors in descending order to apply to the text to try to make it fit into a constrained size.
*/
CGFloat minimumScaleFactor;
NSArray *pointSizeScaleFactors;
/**
The currently applied scale factor. Only valid if pointSizeScaleFactors are provided. Defaults to 0 (no scaling)
*/
CGFloat currentScaleFactor;
/**
A pointer to a function that that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager.
*/
@ -112,8 +117,10 @@ struct ASTextKitAttributes {
[shadowColor copy],
shadowOpacity,
shadowRadius,
minimumScaleFactor,
layoutManagerFactory
pointSizeScaleFactors,
currentScaleFactor,
layoutManagerFactory,
layoutManagerDelegate,
};
};
@ -124,7 +131,8 @@ struct ASTextKitAttributes {
&& maximumNumberOfLines == other.maximumNumberOfLines
&& shadowOpacity == other.shadowOpacity
&& shadowRadius == other.shadowRadius
&& minimumScaleFactor == other.minimumScaleFactor
&& [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors]
&& currentScaleFactor == currentScaleFactor
&& layoutManagerFactory == other.layoutManagerFactory
&& CGSizeEqualToSize(shadowOffset, other.shadowOffset)
&& _objectsEqual(exclusionPaths, other.exclusionPaths)

View File

@ -1,20 +1,46 @@
//
// ASTextKitFontSizeAdjuster.h
// AsyncDisplayKit
//
// Created by Luke on 1/20/16.
// Copyright © 2016 Facebook. All rights reserved.
//
/* 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 <Foundation/Foundation.h>
#import "ASTextKitAttributes.h"
#import "ASTextKitContext.h"
@interface ASTextKitFontSizeAdjuster : NSObject
@property (nonatomic, assign) CGSize constrainedSize;
/**
* Creates a class that will return a scale factor the will make a string fit inside the constrained size.
*
* "Fitting" means that both the longest word in the string will fit without breaking in the constrained
* size's width AND that the entire string will try to fit within attribute's maximumLineCount. The amount
* that the string will scale is based upon the attribute's pointSizeScaleFactors. If the string cannot fit
* in the given width/number of lines, the smallest scale factor will be returned.
*
* @param context The text kit context
* @param constrainedSize The constrained size to render into
* @param textComponentAttributes The renderer's text attributes
*/
- (instancetype)initWithContext:(ASTextKitContext *)context
minimumScaleFactor:(CGFloat)minimumScaleFactor
constrainedSize:(CGSize)constrainedSize;
constrainedSize:(CGSize)constrainedSize
textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes;
/**
* Returns the best fit scale factor for the text
*/
- (CGFloat)scaleFactor;
/**
* Takes all of the attributed string attributes dealing with size (font size, line spacing, kerning, etc) and
* scales them by the scaleFactor. I wouldn't be surprised if I missed some in here.
*/
+ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor;
- (void) adjustFontSize;
@end

View File

@ -1,98 +0,0 @@
//
// ASTextKitFontSizeAdjuster.m
// AsyncDisplayKit
//
// Created by Luke on 1/20/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import "ASTextKitContext.h"
#import "ASTextKitFontSizeAdjuster.h"
@implementation ASTextKitFontSizeAdjuster
{
__weak ASTextKitContext *_context;
CGFloat _minimumScaleFactor;
}
- (instancetype)initWithContext:(ASTextKitContext *)context
minimumScaleFactor:(CGFloat)minimumScaleFactor
constrainedSize:(CGSize)constrainedSize
{
if (self = [super init]) {
_context = context;
_minimumScaleFactor = minimumScaleFactor;
_constrainedSize = constrainedSize;
}
return self;
}
- (CGSize)sizeForAttributedString:(NSAttributedString *)attrString
{
return [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
context:nil].size;
}
- (void) adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor
{
{
[attrString beginEditing];
[attrString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
UIFont* font = value;
font = [font fontWithSize:font.pointSize * scaleFactor];
[attrString removeAttribute:NSFontAttributeName range:range];
[attrString addAttribute:NSFontAttributeName value:font range:range];
}];
[attrString endEditing];
}
}
- (void)adjustFontSize
{
if (_minimumScaleFactor <= 0 || _minimumScaleFactor >= 1) {
return;
}
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
NSString *str = textStorage.string;
NSArray *words = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *longestWordNeedingResize = @"";
for (NSString *word in words) {
if ([word length] > [longestWordNeedingResize length]) {
longestWordNeedingResize = word;
}
}
if ([longestWordNeedingResize length] == 0) {
return;
}
NSRange range = [str rangeOfString:longestWordNeedingResize];
NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:range].mutableCopy;
CGSize defaultSize = [self sizeForAttributedString:attrString];
if (defaultSize.width > _constrainedSize.width) {
[attrString removeAttribute:NSParagraphStyleAttributeName range:NSMakeRange(0, [attrString length])];
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
context.minimumScaleFactor = _minimumScaleFactor;
[attrString boundingRectWithSize:CGSizeMake(_constrainedSize.width, defaultSize.height)
options:NSStringDrawingUsesLineFragmentOrigin
context:context];
[self adjustFontSizeForAttributeString:attrString withScaleFactor:context.actualScaleFactor];
if ([self sizeForAttributedString:attrString].width <= _constrainedSize.width) {
[self adjustFontSizeForAttributeString:textStorage withScaleFactor:context.actualScaleFactor];
NSLog(@"ASTextKitFontSizeAdjuster : adjusted \"%@\"to fontsize actualScaleFactor:%f", longestWordNeedingResize, context.actualScaleFactor);
}
}
}];
}
@end

View File

@ -0,0 +1,183 @@
/* 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 "ASTextKitContext.h"
#import "ASTextKitFontSizeAdjuster.h"
#import "ASLayoutManager.h"
#import <mutex>
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
@implementation ASTextKitFontSizeAdjuster
{
__weak ASTextKitContext *_context;
ASTextKitAttributes _attributes;
std::mutex _textKitMutex;
}
- (instancetype)initWithContext:(ASTextKitContext *)context
constrainedSize:(CGSize)constrainedSize
textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes;
{
if (self = [super init]) {
_context = context;
_constrainedSize = constrainedSize;
_attributes = textComponentAttributes;
}
return self;
}
+ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor
{
[attrString beginEditing];
// scale all the attributes that will change the bounding box
[attrString enumerateAttributesInRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
if (attrs[NSFontAttributeName] != nil) {
UIFont *font = attrs[NSFontAttributeName];
font = [font fontWithSize:roundf(font.pointSize * scaleFactor)];
[attrString removeAttribute:NSFontAttributeName range:range];
[attrString addAttribute:NSFontAttributeName value:font range:range];
}
if (attrs[NSKernAttributeName] != nil) {
NSNumber *kerning = attrs[NSKernAttributeName];
[attrString removeAttribute:NSKernAttributeName range:range];
[attrString addAttribute:NSKernAttributeName value:@([kerning floatValue] * scaleFactor) range:range];
}
if (attrs[NSParagraphStyleAttributeName] != nil) {
NSMutableParagraphStyle *paragraphStyle = [attrs[NSParagraphStyleAttributeName] mutableCopy];
paragraphStyle.lineSpacing = (paragraphStyle.lineSpacing * scaleFactor);
paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor);
paragraphStyle.firstLineHeadIndent = (paragraphStyle.firstLineHeadIndent * scaleFactor);
paragraphStyle.headIndent = (paragraphStyle.headIndent * scaleFactor);
paragraphStyle.tailIndent = (paragraphStyle.tailIndent * scaleFactor);
paragraphStyle.minimumLineHeight = (paragraphStyle.minimumLineHeight * scaleFactor);
paragraphStyle.maximumLineHeight = (paragraphStyle.maximumLineHeight * scaleFactor);
paragraphStyle.lineHeightMultiple = (paragraphStyle.lineHeightMultiple * scaleFactor);
paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor);
[attrString removeAttribute:NSParagraphStyleAttributeName range:range];
[attrString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
}
}];
[attrString endEditing];
}
- (NSUInteger)lineCountForString:(NSAttributedString *)attributedString
{
NSUInteger lineCount = 0;
static std::mutex __static_mutex;
std::lock_guard<std::mutex> l(__static_mutex);
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
NSLayoutManager *layoutManager = _attributes.layoutManagerFactory ? _attributes.layoutManagerFactory() : [[ASLayoutManager alloc] init];
layoutManager.usesFontLeading = NO;
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:_constrainedSize];
textContainer.lineFragmentPadding = 0;
textContainer.lineBreakMode = _attributes.lineBreakMode;
// use 0 regardless of what is in the attributes so that we get an accurate line count
textContainer.maximumNumberOfLines = 0;
textContainer.exclusionPaths = _attributes.exclusionPaths;
[layoutManager addTextContainer:textContainer];
for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs] && lineCount <= _attributes.maximumNumberOfLines; lineCount++) {
[layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange];
}
return lineCount;
}
- (CGFloat)scaleFactor
{
if ([_attributes.pointSizeScaleFactors count] == 0 || isinf(_constrainedSize.width)) {
return 1.0;
}
__block CGFloat adjustedScale = 1.0;
NSArray *scaleFactors = _attributes.pointSizeScaleFactors;
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
// Check for two different situations (and correct for both)
// 1. The longest word in the string fits without being wrapped
// 2. The entire text fits in the given constrained size.
NSString *str = textStorage.string;
NSArray *words = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *longestWordNeedingResize = @"";
for (NSString *word in words) {
if ([word length] > [longestWordNeedingResize length]) {
longestWordNeedingResize = word;
}
}
NSUInteger scaleIndex = 0;
// find the longest word and make sure it fits in the constrained width
if ([longestWordNeedingResize length] > 0) {
NSRange longestWordRange = [str rangeOfString:longestWordNeedingResize];
NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange].mutableCopy;
CGSize longestWordSize = [attrString boundingRectWithSize:CGSizeMake(FLT_MAX, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
// check if the longest word is larger than our constrained width
if (longestWordSize.width > _constrainedSize.width) {
// we have a word that is too long. Loop through our scale factors until we fit
for (NSNumber *scaleFactor in scaleFactors) {
// even if we still don't fit, save this scaleFactor so more of the word will fit
adjustedScale = [scaleFactor floatValue];
// adjust here so we start at the proper place in our scale array if we have too many lines
scaleIndex++;
if (ceilf(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) {
// we fit! we are done
break;
}
}
}
}
if (_attributes.maximumNumberOfLines > 0) {
// get the number of lines in our possibly scaled string
NSUInteger numberOfLines = [self lineCountForString:textStorage];
if (numberOfLines > _attributes.maximumNumberOfLines) {
for (NSUInteger index = scaleIndex; index < scaleFactors.count; index++) {
NSMutableAttributedString *entireAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage];
[[self class] adjustFontSizeForAttributeString:entireAttributedString withScaleFactor:[scaleFactors[index] floatValue]];
// save away this scale factor. Even if we don't fit completely we should still scale down
adjustedScale = [scaleFactors[index] floatValue];
if ([self lineCountForString:entireAttributedString] <= _attributes.maximumNumberOfLines) {
// we fit! we are done
break;
}
}
}
}
}];
return adjustedScale;
}
@end

View File

@ -55,6 +55,8 @@
@property (nonatomic, assign, readwrite) CGSize constrainedSize;
@property (nonatomic, assign, readonly) CGFloat currentScaleFactor;
#pragma mark - Drawing
/*
Draw the renderer's text content into the bounds provided.

View File

@ -49,6 +49,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
_constrainedSize = constrainedSize;
_attributes = attributes;
_sizeIsCalculated = NO;
if ([attributes.pointSizeScaleFactors count] > 0) {
_currentScaleFactor = attributes.currentScaleFactor;
}
}
return self;
}
@ -84,8 +87,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
// We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
_fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context]
minimumScaleFactor:attributes.minimumScaleFactor
constrainedSize:shadowConstrainedSize];
constrainedSize:shadowConstrainedSize
textKitAttributes:attributes];
}
return _fontSizeAdjuster;
}
@ -137,8 +140,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
- (void)_calculateSize
{
[self truncater];
if (_attributes.minimumScaleFactor < 1 && _attributes.minimumScaleFactor > 0) {
[[self fontSizeAdjuster] adjustFontSize];
if ([_attributes.pointSizeScaleFactors count] > 0) {
_currentScaleFactor = [[self fontSizeAdjuster] scaleFactor];
}
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
@ -156,8 +159,12 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
// TextKit often returns incorrect glyph bounding rects in the horizontal direction, so we clip to our bounding rect
// to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect.
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
_calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
CGSize boundingSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
_calculatedSize = CGSizeMake(boundingSize.width, boundingSize.height);
if (_currentScaleFactor > 0.0 && _currentScaleFactor < 1.0) {
_calculatedSize.height = ceilf(_calculatedSize.height * _currentScaleFactor);
}
}
#pragma mark - Drawing
@ -176,11 +183,32 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds));
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
NSTextStorage *scaledTextStorage = nil;
BOOL isScaled = (self.currentScaleFactor > 0 && self.currentScaleFactor < 1.0);
if (isScaled) {
// if we are going to scale the text, swap out the non-scaled text for the scaled version.
NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage];
[ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:scaledString withScaleFactor:_currentScaleFactor];
scaledTextStorage = [[NSTextStorage alloc] initWithAttributedString:scaledString];
[textStorage removeLayoutManager:layoutManager];
[scaledTextStorage addLayoutManager:layoutManager];
}
LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer]));
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
NSRange glyphRange = [layoutManager glyphRangeForBoundingRect:CGRectMake(0,0,textContainer.size.width, textContainer.size.height) inTextContainer:textContainer];
LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]));
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
if (isScaled) {
// put the non-scaled version back
[scaledTextStorage removeLayoutManager:layoutManager];
[textStorage addLayoutManager:layoutManager];
}
}];
UIGraphicsPopContext();

View File

@ -166,6 +166,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
}
- (BOOL)resignFirstResponder {
[super resignFirstResponder];
if (self.isFirstResponder) {
self.isFirstResponder = NO;
return YES;