mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 11:20:18 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
fa4fc2b6ae
@ -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 */,
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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];
|
||||||
|
|||||||
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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
|
|
||||||
183
AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm
Normal file
183
AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm
Normal 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
|
||||||
@ -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.
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user