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 */; }; 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; };
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; };
9C8221981BA237B80037F19A /* 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, ); }; }; 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, ); }; }; 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 */; }; 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; };
A32FEDD51C501B6A004F642A /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; }; A32FEDD51C501B6A004F642A /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; settings = {ATTRIBUTES = (Public, ); }; };
A32FEDD61C501B6A004F642A /* ASTextKitFontSizeAdjuster.m in Sources */ = {isa = PBXBuildFile; fileRef = A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */; };
A32FEDD71C5042C1004F642A /* ASTextKitFontSizeAdjuster.m in Sources */ = {isa = PBXBuildFile; fileRef = A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */; };
A373200F1C571B730011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.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, ); }; }; 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 */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASChangeSetDataController.h; sourceTree = "<group>"; };
@ -1206,7 +1207,7 @@
257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */, 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */,
257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */, 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */,
A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */, A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */,
A32FEDD41C501B6A004F642A /* ASTextKitFontSizeAdjuster.m */, 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */,
257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */, 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */,
257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */, 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */,
2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */, 2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */,
@ -1470,6 +1471,7 @@
B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */,
34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */, 34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */,
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */,
B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */, B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */,
254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */,
509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */, 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */,
@ -1810,7 +1812,6 @@
257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */, 257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */,
0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */, 0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */,
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */, DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
A32FEDD61C501B6A004F642A /* ASTextKitFontSizeAdjuster.m in Sources */,
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */, 058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */, 055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */,
AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */, AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */,
@ -1824,6 +1825,7 @@
ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */, ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */,
AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */, AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */,
205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */, 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */,
9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */, D785F6631A74327E00291744 /* ASScrollNode.m in Sources */,
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */, 058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
@ -1910,6 +1912,7 @@
509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */,
254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */,
DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */, DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */,
9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */,
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
@ -1945,7 +1948,6 @@
9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */, 9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */,
9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */, 9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
A32FEDD71C5042C1004F642A /* ASTextKitFontSizeAdjuster.m in Sources */,
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */,
34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */,
254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */, 254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */,

View File

@ -252,7 +252,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_superIsPendingDataLoad = YES; _superIsPendingDataLoad = YES;
[super reloadData]; [super reloadData];
}); });
[_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; [_dataController reloadDataWithCompletion:completion];
} }
- (void)reloadData - (void)reloadData
@ -264,7 +264,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
_superIsPendingDataLoad = YES; _superIsPendingDataLoad = YES;
[_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone]; [_dataController reloadDataImmediately];
[super reloadData]; [super reloadData];
} }
@ -451,7 +451,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; [_dataController moveSection:section toSection:newSection];
} }
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
@ -475,7 +475,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
} }
- (NSString *)__reuseIdentifierForKind:(NSString *)kind - (NSString *)__reuseIdentifierForKind:(NSString *)kind
@ -486,14 +486,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
#pragma mark - #pragma mark -
#pragma mark Intercepted selectors. #pragma mark Intercepted selectors.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{ {
_ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kCellReuseIdentifier forIndexPath:indexPath]; _superIsPendingDataLoad = NO;
return [_dataController numberOfSections];
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; }
cell.node = node;
[_rangeController configureContentView:cell.contentView forCellNode:node]; - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
return cell; {
return [_dataController numberOfRowsInSection:section];
} }
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
@ -510,17 +511,61 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return view; return view;
} }
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{ {
_superIsPendingDataLoad = NO; _ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kCellReuseIdentifier forIndexPath:indexPath];
return [_dataController numberOfSections];
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 - (ASScrollDirection)scrollDirection
{ {
CGPoint scrollVelocity; CGPoint scrollVelocity;
@ -582,39 +627,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return scrollableDirection; 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 - (void)layoutSubviews
{ {
if (_zeroContentInsets) { if (_zeroContentInsets) {
@ -650,7 +662,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 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:)]) { if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
@ -763,19 +777,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
if (_asyncDelegateImplementsInsetSection) { if (_asyncDelegateImplementsInsetSection) {
sectionInset = [(id<ASCollectionViewDelegateFlowLayout>)_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section]; sectionInset = [(id<ASCollectionViewDelegateFlowLayout>)_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section];
} }
if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) { constrainedSize.min.height = MAX(0, constrainedSize.min.height - sectionInset.top - sectionInset.bottom);
constrainedSize.min.width = MAX(0, constrainedSize.min.width - sectionInset.left - sectionInset.right); constrainedSize.min.width = MAX(0, constrainedSize.min.width - sectionInset.left - sectionInset.right);
//ignore insets for FLT_MAX so FLT_MAX can be compared against //ignore insets for FLT_MAX so FLT_MAX can be compared against
if (constrainedSize.max.width - FLT_EPSILON < FLT_MAX) { if (constrainedSize.max.width - FLT_EPSILON < FLT_MAX) {
constrainedSize.max.width = MAX(0, constrainedSize.max.width - sectionInset.left - sectionInset.right); constrainedSize.max.width = MAX(0, constrainedSize.max.width - sectionInset.left - sectionInset.right);
} }
} else { if (constrainedSize.max.height - FLT_EPSILON < FLT_MAX) {
constrainedSize.min.height = MAX(0, constrainedSize.min.height - sectionInset.top - sectionInset.bottom); constrainedSize.max.height = MAX(0, constrainedSize.max.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);
}
} }
return constrainedSize; 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 - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssertMainThread(); 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 - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssertMainThread(); 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 #pragma mark - ASCellNodeDelegate
- (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged

View File

@ -8,6 +8,11 @@
#import "ASContextTransitioning.h" #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) @interface ASDisplayNode (Beta)
+ (BOOL)usesImplicitHierarchyManagement; + (BOOL)usesImplicitHierarchyManagement;

View File

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

View File

@ -303,10 +303,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)reloadDataWithCompletion:(void (^)())completion - (void)reloadDataWithCompletion:(void (^)())completion
{ {
ASPerformBlockOnMainThread(^{ [_dataController reloadDataWithCompletion:completion];
[super reloadData];
});
[_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion];
} }
- (void)reloadData - (void)reloadData
@ -317,8 +314,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)reloadDataImmediately - (void)reloadDataImmediately
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; [_dataController reloadDataImmediately];
[super reloadData];
} }
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
@ -433,7 +429,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; [_dataController moveSection:section toSection:newSection];
} }
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
@ -457,7 +453,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
} }
#pragma mark - #pragma mark -
@ -637,8 +633,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0),
scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 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:)]) { if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset: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 - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssertMainThread(); 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 - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssertMainThread(); 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 #pragma mark - ASDataControllerDelegate
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath {

View File

@ -10,9 +10,14 @@
@interface ASTextNode () @interface ASTextNode ()
/** /**
@abstract The minimum scale that the textnode can apply to fit long words. @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 0 (No scaling) @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 @end

View File

@ -18,6 +18,7 @@
#import "ASTextKitCoreTextAdditions.h" #import "ASTextKitCoreTextAdditions.h"
#import "ASTextKitHelpers.h" #import "ASTextKitHelpers.h"
#import "ASTextKitFontSizeAdjuster.h"
#import "ASTextKitRenderer.h" #import "ASTextKitRenderer.h"
#import "ASTextKitRenderer+Positioning.h" #import "ASTextKitRenderer+Positioning.h"
#import "ASTextKitShadower.h" #import "ASTextKitShadower.h"
@ -242,7 +243,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
.lineBreakMode = _truncationMode, .lineBreakMode = _truncationMode,
.maximumNumberOfLines = _maximumNumberOfLines, .maximumNumberOfLines = _maximumNumberOfLines,
.exclusionPaths = _exclusionPaths, .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 // expensive, and can take some time, so we dispatch onto a bg queue to
// actually dealloc. // actually dealloc.
__block ASTextKitRenderer *renderer = _renderer; __block ASTextKitRenderer *renderer = _renderer;
ASPerformBlockOnBackgroundThread(^{ ASPerformBlockOnBackgroundThread(^{
renderer = nil; renderer = nil;
}); });
@ -335,7 +338,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[self setNeedsDisplay]; [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 #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) { if (attributedString.length > 0) {
CGFloat screenScale = ASScreenScale(); CGFloat screenScale = ASScreenScale();
self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; 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; return visibleRange.length < _attributedString.length;
} }
- (void)setMinimumScaleFactor:(CGFloat)minimumScaleFactor - (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors
{ {
if (_minimumScaleFactor != minimumScaleFactor) { if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) {
_minimumScaleFactor = minimumScaleFactor; _pointSizeScaleFactors = pointSizeScaleFactors;
[self _invalidateRenderer]; [self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay]; [self setNeedsDisplay];
}); });
} }}
}
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines - (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
{ {

View File

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

View File

@ -46,11 +46,7 @@
if (!(self = [super init])) { if (!(self = [super init])) {
return nil; 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); _previewQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
self.playButton = [[ASDefaultPlayButton alloc] init]; self.playButton = [[ASDefaultPlayButton alloc] init];

View File

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

View File

@ -14,16 +14,7 @@
@interface ASDataController (Subclasses) @interface ASDataController (Subclasses)
#pragma mark - Internal editing & completed store querying #pragma mark - Internal editing & completed store querying
@property (nonatomic, strong, readonly) NSMutableDictionary *editingNode;
/**
* 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;
/** /**
* Read only access to the underlying completed nodes of the given kind * 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:`. * 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. * 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 #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 #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; - (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. 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. Called for deletion of sections.
*/ */
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (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 @end
/** /**
@ -137,14 +162,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/ */
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; - (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 */ /** @name Data Updating */
- (void)beginUpdates; - (void)beginUpdates;
@ -159,7 +176,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (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; - (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
@ -175,11 +192,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/ */
- (void)relayoutAllNodes; - (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 */ /** @name Data Querying */

View File

@ -30,7 +30,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
static void *kASSizingQueueContext = &kASSizingQueueContext; static void *kASSizingQueueContext = &kASSizingQueueContext;
@interface ASDataController () { @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 *_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. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes.
@ -42,9 +41,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
BOOL _asyncDataFetchingEnabled; BOOL _asyncDataFetchingEnabled;
BOOL _delegateDidInsertNodes; BOOL _delegateDidInsertNodes;
BOOL _delegateDidReloadNodes;
BOOL _delegateDidDeleteNodes; BOOL _delegateDidDeleteNodes;
BOOL _delegateDidMoveNode;
BOOL _delegateDidInsertSections; BOOL _delegateDidInsertSections;
BOOL _delegateDidDeleteSections; BOOL _delegateDidDeleteSections;
BOOL _delegateDidReloadSections;
BOOL _delegateDidMoveSection;
BOOL _delegateDidReloadData;
} }
@property (atomic, assign) NSUInteger batchUpdateCounter; @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. // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later.
_delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)];
_delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes: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:)]; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)];
_delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)];
_delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)];
} }
+ (NSUInteger)parallelProcessorCount + (NSUInteger)parallelProcessorCount
@ -110,17 +119,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Cell Layout #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; [self _layoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths];
// Processing in batches if (completionBlock) {
for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { completionBlock(nodes, indexPaths);
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];
}
} }
- (void)layoutLoadedNodes:(NSArray<ASCellNode *> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)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); 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 - (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{ {
if (!nodes.count) { if (!nodes.count) {
return; if (completionBlock) {
completionBlock(nodes, indexPaths);
}
return;
} }
NSUInteger nodeCount = nodes.count; NSUInteger nodeCount = nodes.count;
NSMutableArray<ASCellNode *> *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount]; NSMutableArray<ASCellNode *> *allocatedNodes = [NSMutableArray<ASCellNode *> arrayWithCapacity:nodeCount];
dispatch_group_t layoutGroup = dispatch_group_create(); dispatch_group_t layoutGroup = dispatch_group_create();
ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodeCount); ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodeCount);
for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) {
NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j);
__block NSArray *subarray;
// Allocate nodes concurrently.
dispatch_block_t allocationBlock = ^{ 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]; ASCellNodeBlock cellBlock = nodes[k];
ASCellNode *node = cellBlock(); ASCellNode *node = cellBlock();
ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); ASDisplayNodeAssertNotNil(node, @"Node block created nil node");
[allocatedNodes addObject:node]; allocatedNodeBuffer[i] = node;
if (!node.isNodeLoaded) { if (!node.isNodeLoaded) {
nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; 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()) { if (ASDisplayNodeThreadIsMain()) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0); dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 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_signal(sema);
}); });
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); 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 { } else {
allocationBlock(); allocationBlock();
[_mainSerialQueue performBlockOnMainThread:^{ [_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), ^{ dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSUInteger k = j; k < j + batchCount; k++) { for (NSUInteger k = j; k < j + batchCount; k++) {
ASCellNode *node = allocatedNodes[k]; ASCellNode *node = allocatedNodes[k];
@ -223,178 +236,84 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - External Data Querying + Editing #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) if (indexPaths.count == 0)
return; return;
LOG(@"insertNodes:%@ ofKind:%@", nodes, kind);
NSMutableArray *editingNodes = _editingNodes[kind]; NSMutableArray *editingNodes = _editingNodes[kind];
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes);
_editingNodes[kind] = editingNodes; _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) { 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]; NSMutableArray *editingNodes = _editingNodes[kind];
NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths);
_editingNodes[kind] = editingNodes; _editingNodes[kind] = editingNodes;
return deletedNodes;
[_mainSerialQueue performBlockOnMainThread:^{
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
}];
} }
- (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) if (indexSet.count == 0)
return; return;
LOG(@"insertSections:%@ ofKind:%@", sections, kind);
if (_editingNodes[kind] == nil) { if (_editingNodes[kind] == nil) {
_editingNodes[kind] = [NSMutableArray array]; _editingNodes[kind] = [NSMutableArray array];
} }
[_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; [_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) if (indexSet.count == 0)
return; return;
LOG(@"deleteSectionsOfKind:%@", kind);
[_editingNodes[kind] removeObjectsAtIndexes:indexSet]; [_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:^{ [_mainSerialQueue performBlockOnMainThread:^{
[_completedNodes[kind] removeObjectsAtIndexes:indexSet]; _completedNodes[kind] = completedNodes;
if (completionBlock) { if (completionBlock) {
completionBlock(indexSet); completionBlock();
} }
}]; }];
} }
#pragma mark - Internal Data Querying + Editing #pragma mark - Reload (External API)
/** - (void)reloadDataWithCompletion:(void (^)())completion
* 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
{ {
[self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { [self _reloadDataSynchronously:NO completion:completion];
if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
} }
/** - (void)reloadDataImmediately
* 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
{ {
[self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { [self _reloadDataSynchronously:YES completion:nil];
if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
} }
/** - (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())completion
* 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
{ {
[self performEditCommandWithBlock:^{ [self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -402,39 +321,35 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[self accessDataSourceSynchronously:synchronously withBlock:^{ [self accessDataSourceSynchronously:synchronously withBlock:^{
NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self];
NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedNodeBlocks = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [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 // Allow subclasses to perform setup before going into the edit transaction
[self prepareForReloadData]; [self prepareForReloadData];
void (^transactionBlock)() = ^{ void (^transactionBlock)() = ^{
LOG(@"Edit Transaction - reloadData"); 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]; [self willReloadData];
// Insert each section // Insert sections
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) { for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]]; [sections addObject:[[NSMutableArray alloc] init]];
} }
_editingNodes[ASDataControllerRowNodeKind] = sections;
[self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions];
[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 (completion) { if (_delegateDidReloadData) {
dispatch_async(dispatch_get_main_queue(), completion); [_delegate dataControllerDidReloadData:self];
} }
if (completion) {
completion();
}
}];
}];
}; };
if (synchronously) { if (synchronously) {
@ -533,15 +448,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
if (_batchUpdateCounter == 0) { if (_batchUpdateCounter == 0) {
LOG(@"endUpdatesWithCompletion - beginning"); LOG(@"endUpdatesWithCompletion - beginning");
[_editingTransactionQueue addOperationWithBlock:^{ [_mainSerialQueue performBlockOnMainThread:^{
[_mainSerialQueue performBlockOnMainThread:^{ [_delegate dataControllerBeginUpdates:self];
// 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];
}];
}]; }];
// Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. // 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(); block();
}]; }];
[_pendingEditCommandBlocks removeAllObjects]; [_pendingEditCommandBlocks removeAllObjects];
[_editingTransactionQueue addOperationWithBlock:^{ [_mainSerialQueue performBlockOnMainThread:^{
[_mainSerialQueue performBlockOnMainThread:^{ [_delegate dataController:self endUpdatesAnimated:animated completion:completion];
// 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];
}];
}]; }];
} }
} }
@ -592,9 +494,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished]; [_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{ [self accessDataSourceWithBlock:^{
NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedNodeBlocks = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths];
[self prepareForInsertSections:sections]; [self prepareForInsertSections:sections];
@ -607,8 +509,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[sectionArray addObject:[NSMutableArray array]]; [sectionArray addObject:[NSMutableArray array]];
} }
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; [self insertSections:sectionArray ofKind:ASDataControllerRowNodeKind atIndexSet:sections];
[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 (_delegateDidInsertSections)
[_delegate dataController:self didInsertSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}];
}]; }];
}]; }];
}]; }];
@ -626,10 +534,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// remove elements // remove elements
LOG(@"Edit Transaction - deleteSections: %@", sections); LOG(@"Edit Transaction - deleteSections: %@", sections);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
[self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
[self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; if (_delegateDidDeleteSections)
[_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}]; }];
}]; }];
} }
@ -643,29 +553,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished]; [_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{ [self accessDataSourceWithBlock:^{
NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedNodeBlocks = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths];
[self prepareForReloadSections:sections]; [self prepareForReloadSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
[self willReloadSections:sections]; [self willReloadSections:sections];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); // clear sections
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); _editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init];
}];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// reinsert the elements [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; [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:^{ [self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -675,24 +588,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
[self willMoveSection:section toSection:newSection]; [self willMoveSection:section toSection:newSection];
// remove elements
LOG(@"Edit Transaction - moveSection"); LOG(@"Edit Transaction - moveSection");
[self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); if (_delegateDidMoveSection) {
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; [_delegate dataController:self didMoveSection:section toSection:newSection];
}
// 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];
}]; }];
}]; }];
} }
@ -760,7 +663,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - insertRows: %@", indexPaths); 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:^{ [_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - deleteRows: %@", indexPaths); 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. // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source.
[self accessDataSourceWithBlock:^{ [self accessDataSourceWithBlock:^{
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
// FIXME: This doesn't currently do anything // FIXME: This doesn't currently do anything
// FIXME: Shouldn't deletes be sorted in descending order? // FIXME: Shouldn't deletes be sorted in descending order?
[indexPaths sortedArrayUsingSelector:@selector(compare:)]; [indexPaths sortedArrayUsingSelector:@selector(compare:)];
for (NSIndexPath *indexPath in indexPaths) { for (NSIndexPath *indexPath in indexPaths) {
[nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; [nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]];
} }
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - reloadRows: %@", indexPaths); LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths];
[self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; [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:^{ [self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -864,26 +781,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]);
NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// Don't re-calculate size for moving // Don't re-calculate size for moving
NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]];
[self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidMoveNode) {
[_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath];
}
}];
}]; }];
}]; }];
} }
#pragma mark - Data Querying (Subclass API) #pragma mark - Data Querying (Subclass API)
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind - (NSMutableDictionary *)editingNode{
{ return _editingNodes;
return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil;
}
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind
{
return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array];
} }
- (NSMutableArray *)completedNodesOfKind:(NSString *)kind - (NSMutableArray *)completedNodesOfKind:(NSString *)kind
@ -950,11 +864,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); 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 - (NSArray *)completedNodes
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; return _completedNodes[ASDataControllerRowNodeKind];
} }
#pragma mark - Dealloc #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; - (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. * Called for section insertion.
* *
@ -174,6 +198,17 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (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. * Called for section deletion.
* *
@ -185,6 +220,24 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (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 @end
NS_ASSUME_NONNULL_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(^{ ASPerformBlockOnMainThread(^{
_rangeIsValid = NO; _rangeIsValid = NO;
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; [_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 - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASPerformBlockOnMainThread(^{ 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); CGFloat ASRoundPixelValue(CGFloat f);
BOOL ASRunningOnOS7();
ASDISPLAYNODE_EXTERN_C_END ASDISPLAYNODE_EXTERN_C_END
/** /**

View File

@ -93,6 +93,16 @@ CGFloat ASRoundPixelValue(CGFloat f)
return roundf(f * ASScreenScale()) / ASScreenScale(); 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) @implementation NSIndexPath (ASInverseComparison)
- (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath - (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath

View File

@ -58,6 +58,7 @@ struct ASTextKitAttributes {
NSLineBreakMode lineBreakMode; NSLineBreakMode lineBreakMode;
/** /**
The maximum number of lines to draw in the drawable region. Leave blank or set to 0 to define no maximum. 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; NSUInteger maximumNumberOfLines;
/** /**
@ -82,9 +83,13 @@ struct ASTextKitAttributes {
*/ */
CGFloat shadowRadius; 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. 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], [shadowColor copy],
shadowOpacity, shadowOpacity,
shadowRadius, shadowRadius,
minimumScaleFactor, pointSizeScaleFactors,
layoutManagerFactory currentScaleFactor,
layoutManagerFactory,
layoutManagerDelegate,
}; };
}; };
@ -124,7 +131,8 @@ struct ASTextKitAttributes {
&& maximumNumberOfLines == other.maximumNumberOfLines && maximumNumberOfLines == other.maximumNumberOfLines
&& shadowOpacity == other.shadowOpacity && shadowOpacity == other.shadowOpacity
&& shadowRadius == other.shadowRadius && shadowRadius == other.shadowRadius
&& minimumScaleFactor == other.minimumScaleFactor && [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors]
&& currentScaleFactor == currentScaleFactor
&& layoutManagerFactory == other.layoutManagerFactory && layoutManagerFactory == other.layoutManagerFactory
&& CGSizeEqualToSize(shadowOffset, other.shadowOffset) && CGSizeEqualToSize(shadowOffset, other.shadowOffset)
&& _objectsEqual(exclusionPaths, other.exclusionPaths) && _objectsEqual(exclusionPaths, other.exclusionPaths)

View File

@ -1,20 +1,46 @@
// /* Copyright (c) 2014-present, Facebook, Inc.
// ASTextKitFontSizeAdjuster.h * All rights reserved.
// AsyncDisplayKit *
// * This source code is licensed under the BSD-style license found in the
// Created by Luke on 1/20/16. * LICENSE file in the root directory of this source tree. An additional grant
// Copyright © 2016 Facebook. All rights reserved. * of patent rights can be found in the PATENTS file in the same directory.
// */
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "ASTextKitAttributes.h"
#import "ASTextKitContext.h"
@interface ASTextKitFontSizeAdjuster : NSObject @interface ASTextKitFontSizeAdjuster : NSObject
@property (nonatomic, assign) CGSize constrainedSize; @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 - (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 @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, readwrite) CGSize constrainedSize;
@property (nonatomic, assign, readonly) CGFloat currentScaleFactor;
#pragma mark - Drawing #pragma mark - Drawing
/* /*
Draw the renderer's text content into the bounds provided. Draw the renderer's text content into the bounds provided.

View File

@ -49,6 +49,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
_constrainedSize = constrainedSize; _constrainedSize = constrainedSize;
_attributes = attributes; _attributes = attributes;
_sizeIsCalculated = NO; _sizeIsCalculated = NO;
if ([attributes.pointSizeScaleFactors count] > 0) {
_currentScaleFactor = attributes.currentScaleFactor;
}
} }
return self; return self;
} }
@ -84,8 +87,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
// We must inset the constrained size by the size of the shadower. // We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
_fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context] _fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context]
minimumScaleFactor:attributes.minimumScaleFactor constrainedSize:shadowConstrainedSize
constrainedSize:shadowConstrainedSize]; textKitAttributes:attributes];
} }
return _fontSizeAdjuster; return _fontSizeAdjuster;
} }
@ -137,8 +140,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
- (void)_calculateSize - (void)_calculateSize
{ {
[self truncater]; [self truncater];
if (_attributes.minimumScaleFactor < 1 && _attributes.minimumScaleFactor > 0) { if ([_attributes.pointSizeScaleFactors count] > 0) {
[[self fontSizeAdjuster] adjustFontSize]; _currentScaleFactor = [[self fontSizeAdjuster] scaleFactor];
} }
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by // 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 // 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. // to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect.
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size}); boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
CGSize boundingSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
_calculatedSize = [_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 #pragma mark - Drawing
@ -176,11 +183,32 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds)); LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds));
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { [[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])); 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])); LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]));
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
[layoutManager drawGlyphsForGlyphRange: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(); UIGraphicsPopContext();

View File

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