mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-10 08:20:16 +00:00
Merge branch 'master' into ASVideoNode
This commit is contained in:
commit
579313f086
@ -1,11 +1,11 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'AsyncDisplayKit'
|
||||
spec.version = '1.9.4'
|
||||
spec.version = '1.9.5'
|
||||
spec.license = { :type => 'BSD' }
|
||||
spec.homepage = 'http://asyncdisplaykit.org'
|
||||
spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' }
|
||||
spec.summary = 'Smooth asynchronous user interfaces for iOS apps.'
|
||||
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.4' }
|
||||
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.5' }
|
||||
|
||||
spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/'
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */; };
|
||||
0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
057D02C41AC0A66700C7AC3C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C31AC0A66700C7AC3C /* main.m */; };
|
||||
057D02C71AC0A66700C7AC3C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C61AC0A66700C7AC3C /* AppDelegate.m */; };
|
||||
057D02C71AC0A66700C7AC3C /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C61AC0A66700C7AC3C /* AppDelegate.mm */; };
|
||||
0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */; };
|
||||
058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; };
|
||||
@ -112,8 +112,8 @@
|
||||
05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */; };
|
||||
18C2ED831B9B7DE800F627B3 /* ASCollectionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */; };
|
||||
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; };
|
||||
18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; };
|
||||
1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
204C979E1B362CB3002B1083 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 204C979D1B362CB3002B1083 /* Default-568h@2x.png */; };
|
||||
205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@ -462,16 +462,25 @@
|
||||
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; };
|
||||
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
|
||||
DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; };
|
||||
DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; };
|
||||
DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; };
|
||||
DE8BEAC11C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; };
|
||||
DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; };
|
||||
DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; };
|
||||
DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; };
|
||||
DEC146B61C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; };
|
||||
DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; };
|
||||
DEC146B81C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; };
|
||||
DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; };
|
||||
DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; };
|
||||
DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; };
|
||||
DECC2ECD1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */ = {isa = PBXBuildFile; fileRef = DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */; };
|
||||
DECC2ECE1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */ = {isa = PBXBuildFile; fileRef = DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */; };
|
||||
DECC2ECF1C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */; };
|
||||
DECC2ED01C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -489,6 +498,13 @@
|
||||
remoteGlobalIDString = 058D09AB195D04C000B7D73C;
|
||||
remoteInfo = AsyncDisplayKit;
|
||||
};
|
||||
DEACA2B11C425DC400FA9DDF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 058D09A4195D04C000B7D73C /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 058D09AB195D04C000B7D73C;
|
||||
remoteInfo = AsyncDisplayKit;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@ -533,7 +549,7 @@
|
||||
057D02C21AC0A66700C7AC3C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
057D02C31AC0A66700C7AC3C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
057D02C51AC0A66700C7AC3C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
057D02C61AC0A66700C7AC3C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
057D02C61AC0A66700C7AC3C /* AppDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.mm; sourceTree = "<group>"; };
|
||||
0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEditableTextNode.h; sourceTree = "<group>"; };
|
||||
0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASEditableTextNode.mm; sourceTree = "<group>"; };
|
||||
058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAsyncDisplayKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@ -607,7 +623,7 @@
|
||||
05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASSnapshotTestCase.mm; sourceTree = "<group>"; };
|
||||
05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = "<group>"; };
|
||||
18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = "<group>"; };
|
||||
18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionNode.m; sourceTree = "<group>"; };
|
||||
18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = "<group>"; };
|
||||
1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = "<group>"; };
|
||||
204C979D1B362CB3002B1083 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = "<group>"; };
|
||||
205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionViewLayout+ASConvenience.h"; sourceTree = "<group>"; };
|
||||
@ -766,8 +782,12 @@
|
||||
DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = "<group>"; };
|
||||
DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = "<group>"; };
|
||||
DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = "<group>"; };
|
||||
DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASCollectionInternal.h; path = Details/ASCollectionInternal.h; sourceTree = "<group>"; };
|
||||
DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASCollectionInternal.m; path = Details/ASCollectionInternal.m; sourceTree = "<group>"; };
|
||||
DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = "<group>"; };
|
||||
DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = "<group>"; };
|
||||
DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerBeta.h; sourceTree = "<group>"; };
|
||||
DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeControllerBeta.mm; sourceTree = "<group>"; };
|
||||
EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@ -777,6 +797,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -825,7 +846,7 @@
|
||||
children = (
|
||||
204C979D1B362CB3002B1083 /* Default-568h@2x.png */,
|
||||
057D02C51AC0A66700C7AC3C /* AppDelegate.h */,
|
||||
057D02C61AC0A66700C7AC3C /* AppDelegate.m */,
|
||||
057D02C61AC0A66700C7AC3C /* AppDelegate.mm */,
|
||||
057D02C11AC0A66700C7AC3C /* Supporting Files */,
|
||||
);
|
||||
name = AsyncDisplayKitTestHost;
|
||||
@ -891,10 +912,12 @@
|
||||
055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */,
|
||||
AC6456071B0A335000CF11B8 /* ASCellNode.m */,
|
||||
18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */,
|
||||
18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */,
|
||||
18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */,
|
||||
AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */,
|
||||
AC3C4A501A1139C100143C57 /* ASCollectionView.mm */,
|
||||
AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */,
|
||||
DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */,
|
||||
DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */,
|
||||
058D09D5195D050800B7D73C /* ASControlNode.h */,
|
||||
058D09D6195D050800B7D73C /* ASControlNode.m */,
|
||||
DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */,
|
||||
@ -1038,6 +1061,8 @@
|
||||
058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */,
|
||||
055F1A3619ABD413004DAFF1 /* ASRangeController.h */,
|
||||
055F1A3719ABD413004DAFF1 /* ASRangeController.mm */,
|
||||
DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */,
|
||||
DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */,
|
||||
292C599C1A956527007E5DD6 /* ASRangeHandler.h */,
|
||||
258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */,
|
||||
258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */,
|
||||
@ -1325,6 +1350,7 @@
|
||||
9C65A72A1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */,
|
||||
292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */,
|
||||
257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */,
|
||||
DECC2ECD1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */,
|
||||
ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */,
|
||||
ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */,
|
||||
AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */,
|
||||
@ -1366,6 +1392,7 @@
|
||||
ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */,
|
||||
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */,
|
||||
257754AD1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h in Headers */,
|
||||
DEC146B61C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */,
|
||||
205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */,
|
||||
058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */,
|
||||
205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */,
|
||||
@ -1383,6 +1410,7 @@
|
||||
B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */,
|
||||
B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */,
|
||||
B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */,
|
||||
DECC2ECE1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */,
|
||||
254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */,
|
||||
B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */,
|
||||
B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */,
|
||||
@ -1441,6 +1469,7 @@
|
||||
34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */,
|
||||
34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */,
|
||||
34EFC7671B701CD900AD841F /* ASLayout.h in Headers */,
|
||||
DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */,
|
||||
34EFC7691B701CE100AD841F /* ASLayoutable.h in Headers */,
|
||||
9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */,
|
||||
B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */,
|
||||
@ -1512,6 +1541,7 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
DEACA2B21C425DC400FA9DDF /* PBXTargetDependency */,
|
||||
);
|
||||
name = AsyncDisplayKitTestHost;
|
||||
productName = AsyncDisplayKitTestHost;
|
||||
@ -1698,7 +1728,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
057D02C71AC0A66700C7AC3C /* AppDelegate.m in Sources */,
|
||||
057D02C71AC0A66700C7AC3C /* AppDelegate.mm in Sources */,
|
||||
057D02C41AC0A66700C7AC3C /* main.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -1726,7 +1756,7 @@
|
||||
AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */,
|
||||
DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */,
|
||||
ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */,
|
||||
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */,
|
||||
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */,
|
||||
92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */,
|
||||
AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */,
|
||||
205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */,
|
||||
@ -1739,6 +1769,7 @@
|
||||
058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */,
|
||||
25E327581C16819500A2170C /* ASPagerNode.m in Sources */,
|
||||
058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */,
|
||||
DEC146B81C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */,
|
||||
058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */,
|
||||
AEEC47E21C20C2DD00EC1693 /* ASVideoNode.mm in Sources */,
|
||||
0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */,
|
||||
@ -1781,6 +1812,7 @@
|
||||
257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */,
|
||||
257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */,
|
||||
ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */,
|
||||
DECC2ECF1C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */,
|
||||
ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */,
|
||||
257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */,
|
||||
ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */,
|
||||
@ -1861,7 +1893,7 @@
|
||||
B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */,
|
||||
AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */,
|
||||
34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */,
|
||||
18C2ED831B9B7DE800F627B3 /* ASCollectionNode.m in Sources */,
|
||||
18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */,
|
||||
B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */,
|
||||
509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */,
|
||||
B35061F91B010EFD0018CF92 /* ASControlNode.m in Sources */,
|
||||
@ -1871,6 +1903,7 @@
|
||||
B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */,
|
||||
25E327591C16819500A2170C /* ASPagerNode.m in Sources */,
|
||||
B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */,
|
||||
DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */,
|
||||
254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */,
|
||||
B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */,
|
||||
B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */,
|
||||
@ -1912,6 +1945,7 @@
|
||||
9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
|
||||
34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */,
|
||||
34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */,
|
||||
DECC2ED01C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */,
|
||||
34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */,
|
||||
AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */,
|
||||
34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */,
|
||||
@ -1942,6 +1976,11 @@
|
||||
target = 058D09AB195D04C000B7D73C /* AsyncDisplayKit */;
|
||||
targetProxy = 058D09C2195D04C000B7D73C /* PBXContainerItemProxy */;
|
||||
};
|
||||
DEACA2B21C425DC400FA9DDF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 058D09AB195D04C000B7D73C /* AsyncDisplayKit */;
|
||||
targetProxy = DEACA2B11C425DC400FA9DDF /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
@ -1963,13 +2002,13 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = AsyncDisplayKitTestHost/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -1984,9 +2023,9 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO;
|
||||
INFOPLIST_FILE = AsyncDisplayKitTestHost/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -2070,13 +2109,15 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_CODE_COVERAGE = YES;
|
||||
DSTROOT = /tmp/AsyncDisplayKit.dst;
|
||||
GCC_INPUT_FILETYPE = automatic;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
|
||||
OTHER_CFLAGS = "-Wall";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -2089,13 +2130,15 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_CODE_COVERAGE = YES;
|
||||
DSTROOT = /tmp/AsyncDisplayKit.dst;
|
||||
GCC_INPUT_FILETYPE = automatic;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
|
||||
OTHER_CFLAGS = "-Wall";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -2114,7 +2157,7 @@
|
||||
"$(inherited)",
|
||||
"$(DEVELOPER_FRAMEWORKS_DIR)",
|
||||
);
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@ -2126,6 +2169,7 @@
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
INFOPLIST_FILE = "AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost";
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
@ -2142,7 +2186,7 @@
|
||||
"$(inherited)",
|
||||
"$(DEVELOPER_FRAMEWORKS_DIR)",
|
||||
);
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
|
||||
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@ -2153,6 +2197,7 @@
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
INFOPLIST_FILE = "AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost";
|
||||
WRAPPER_EXTENSION = xctest;
|
||||
|
||||
@ -40,7 +40,8 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -9,12 +9,6 @@
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASImageNode.h>
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
ASButtonStateNormal,
|
||||
ASButtonStateHighlighted,
|
||||
ASButtonStateDisabled,
|
||||
} ASButtonState;
|
||||
|
||||
@interface ASButtonNode : ASControlNode
|
||||
|
||||
@property (nonatomic, readonly) ASTextNode *titleNode;
|
||||
@ -43,10 +37,10 @@ typedef enum : NSUInteger {
|
||||
@property (nonatomic, assign) ASVerticalAlignment contentVerticalAlignment;
|
||||
|
||||
|
||||
- (NSAttributedString *)attributedTitleForState:(ASButtonState)state;
|
||||
- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASButtonState)state;
|
||||
- (NSAttributedString *)attributedTitleForState:(ASControlState)state;
|
||||
- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASControlState)state;
|
||||
|
||||
- (UIImage *)imageForState:(ASButtonState)state;
|
||||
- (void)setImage:(UIImage *)image forState:(ASButtonState)state;
|
||||
- (UIImage *)imageForState:(ASControlState)state;
|
||||
- (void)setImage:(UIImage *)image forState:(ASControlState)state;
|
||||
|
||||
@end
|
||||
|
||||
@ -17,10 +17,12 @@
|
||||
|
||||
NSAttributedString *_normalAttributedTitle;
|
||||
NSAttributedString *_highlightedAttributedTitle;
|
||||
NSAttributedString *_selectedAttributedTitle;
|
||||
NSAttributedString *_disabledAttributedTitle;
|
||||
|
||||
UIImage *_normalImage;
|
||||
UIImage *_highlightedImage;
|
||||
UIImage *_selectedImage;
|
||||
UIImage *_disabledImage;
|
||||
}
|
||||
|
||||
@ -45,14 +47,27 @@
|
||||
|
||||
[self addSubnode:_titleNode];
|
||||
[self addSubnode:_imageNode];
|
||||
|
||||
[self addTarget:self action:@selector(controlEventUpdated:) forControlEvents:ASControlNodeEventAllEvents];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)controlEventUpdated:(ASControlNode *)node
|
||||
- (void)setEnabled:(BOOL)enabled
|
||||
{
|
||||
[super setEnabled:enabled];
|
||||
[self updateImage];
|
||||
[self updateTitle];
|
||||
}
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted
|
||||
{
|
||||
[super setHighlighted:highlighted];
|
||||
[self updateImage];
|
||||
[self updateTitle];
|
||||
}
|
||||
|
||||
- (void)setSelected:(BOOL)selected
|
||||
{
|
||||
[super setSelected:selected];
|
||||
[self updateImage];
|
||||
[self updateTitle];
|
||||
}
|
||||
@ -66,6 +81,8 @@
|
||||
newImage = _disabledImage;
|
||||
} else if (self.highlighted && _highlightedImage) {
|
||||
newImage = _highlightedImage;
|
||||
} else if (self.selected && _selectedImage) {
|
||||
newImage = _selectedImage;
|
||||
} else {
|
||||
newImage = _normalImage;
|
||||
}
|
||||
@ -84,6 +101,8 @@
|
||||
newTitle = _disabledAttributedTitle;
|
||||
} else if (self.highlighted && _highlightedAttributedTitle) {
|
||||
newTitle = _highlightedAttributedTitle;
|
||||
} else if (self.selected && _selectedAttributedTitle) {
|
||||
newTitle = _selectedAttributedTitle;
|
||||
} else {
|
||||
newTitle = _normalAttributedTitle;
|
||||
}
|
||||
@ -126,70 +145,96 @@
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedTitleForState:(ASButtonState)state
|
||||
- (NSAttributedString *)attributedTitleForState:(ASControlState)state
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
switch (state) {
|
||||
case ASButtonStateNormal:
|
||||
case ASControlStateNormal:
|
||||
return _normalAttributedTitle;
|
||||
|
||||
case ASButtonStateHighlighted:
|
||||
case ASControlStateHighlighted:
|
||||
return _highlightedAttributedTitle;
|
||||
|
||||
case ASButtonStateDisabled:
|
||||
case ASControlStateSelected:
|
||||
return _selectedAttributedTitle;
|
||||
|
||||
case ASControlStateDisabled:
|
||||
return _disabledAttributedTitle;
|
||||
|
||||
default:
|
||||
return _normalAttributedTitle;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASButtonState)state
|
||||
- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASControlState)state
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
switch (state) {
|
||||
case ASButtonStateNormal:
|
||||
case ASControlStateNormal:
|
||||
_normalAttributedTitle = [title copy];
|
||||
break;
|
||||
|
||||
case ASButtonStateHighlighted:
|
||||
case ASControlStateHighlighted:
|
||||
_highlightedAttributedTitle = [title copy];
|
||||
break;
|
||||
|
||||
case ASButtonStateDisabled:
|
||||
case ASControlStateSelected:
|
||||
_selectedAttributedTitle = [title copy];
|
||||
break;
|
||||
|
||||
case ASControlStateDisabled:
|
||||
_disabledAttributedTitle = [title copy];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
[self updateTitle];
|
||||
}
|
||||
|
||||
- (UIImage *)imageForState:(ASButtonState)state
|
||||
- (UIImage *)imageForState:(ASControlState)state
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
switch (state) {
|
||||
case ASButtonStateNormal:
|
||||
case ASControlStateNormal:
|
||||
return _normalImage;
|
||||
|
||||
case ASButtonStateHighlighted:
|
||||
case ASControlStateHighlighted:
|
||||
return _highlightedImage;
|
||||
|
||||
case ASButtonStateDisabled:
|
||||
case ASControlStateSelected:
|
||||
return _selectedImage;
|
||||
|
||||
case ASControlStateDisabled:
|
||||
return _disabledImage;
|
||||
|
||||
default:
|
||||
return _normalImage;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setImage:(UIImage *)image forState:(ASButtonState)state
|
||||
- (void)setImage:(UIImage *)image forState:(ASControlState)state
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
switch (state) {
|
||||
case ASButtonStateNormal:
|
||||
case ASControlStateNormal:
|
||||
_normalImage = image;
|
||||
break;
|
||||
|
||||
case ASButtonStateHighlighted:
|
||||
case ASControlStateHighlighted:
|
||||
_highlightedImage = image;
|
||||
break;
|
||||
|
||||
case ASButtonStateDisabled:
|
||||
case ASControlStateSelected:
|
||||
_selectedImage = image;
|
||||
break;
|
||||
|
||||
case ASControlStateDisabled:
|
||||
_disabledImage = image;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
[self updateImage];
|
||||
}
|
||||
|
||||
@ -63,13 +63,6 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
//{
|
||||
// _viewControllerNode.frame = (CGRect){{0,0}, constrainedSize.max};
|
||||
// NSLog(@"%f %f", constrainedSize.max.width, constrainedSize.max.height);
|
||||
// return [super layoutSpecThatFits:constrainedSize];
|
||||
//}
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
[super layout];
|
||||
|
||||
@ -7,7 +7,10 @@
|
||||
//
|
||||
|
||||
#import "ASCollectionNode.h"
|
||||
#import "ASCollectionInternal.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASRangeController.h"
|
||||
#include <vector>
|
||||
|
||||
@interface _ASCollectionPendingState : NSObject
|
||||
@property (weak, nonatomic) id <ASCollectionDelegate> delegate;
|
||||
@ -17,14 +20,41 @@
|
||||
@implementation _ASCollectionPendingState
|
||||
@end
|
||||
|
||||
#if 0 // This is not used yet, but will provide a way to avoid creating the view to set range values.
|
||||
@implementation _ASCollectionPendingState
|
||||
{
|
||||
std::vector<ASRangeTuningParameters> _tuningParameters;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
_tuningParameters = std::vector<ASRangeTuningParameters>(ASLayoutRangeTypeCount);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters");
|
||||
return _tuningParameters[rangeType];
|
||||
}
|
||||
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters");
|
||||
ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, @"Must not set Visible range tuning parameters (always 0, 0)");
|
||||
_tuningParameters[rangeType] = tuningParameters;
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@interface ASCollectionNode ()
|
||||
@property (nonatomic) _ASCollectionPendingState *pendingState;
|
||||
@end
|
||||
|
||||
@interface ASCollectionView ()
|
||||
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
|
||||
@end
|
||||
|
||||
@implementation ASCollectionNode
|
||||
|
||||
- (instancetype)init
|
||||
@ -40,10 +70,22 @@
|
||||
return [self initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
}
|
||||
|
||||
- (instancetype)_initWithCollectionView:(ASCollectionView *)collectionView
|
||||
{
|
||||
ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{ return collectionView; };
|
||||
|
||||
if (self = [super initWithViewBlock:collectionViewBlock]) {
|
||||
// ASCollectionView created directly by the app. Trigger -loadView to set up collectionNode pointer.
|
||||
__unused ASCollectionView *collectionView = [self view];
|
||||
return self;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
|
||||
{
|
||||
ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{
|
||||
return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout];
|
||||
return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout ownedByNode:YES];
|
||||
};
|
||||
|
||||
if (self = [super initWithViewBlock:collectionViewBlock]) {
|
||||
@ -56,11 +98,12 @@
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
ASCollectionView *view = self.view;
|
||||
view.collectionNode = self;
|
||||
|
||||
if (_pendingState) {
|
||||
_ASCollectionPendingState *pendingState = _pendingState;
|
||||
self.pendingState = nil;
|
||||
|
||||
ASCollectionView *view = self.view;
|
||||
self.pendingState = nil;
|
||||
view.asyncDelegate = pendingState.delegate;
|
||||
view.asyncDataSource = pendingState.dataSource;
|
||||
}
|
||||
@ -118,10 +161,13 @@
|
||||
return (ASCollectionView *)[super view];
|
||||
}
|
||||
|
||||
#if RangeControllerLoggingEnabled
|
||||
- (void)visibilityDidChange:(BOOL)isVisible
|
||||
{
|
||||
|
||||
[super visibilityDidChange:isVisible];
|
||||
NSLog(@"%@ - visible: %d", self, isVisible);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)clearContents
|
||||
{
|
||||
@ -139,12 +185,12 @@
|
||||
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
return [self.view tuningParametersForRangeType:rangeType];
|
||||
return [self.view.rangeController tuningParametersForRangeType:rangeType];
|
||||
}
|
||||
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
return [self.view setTuningParameters:tuningParameters forRangeType:rangeType];
|
||||
return [self.view.rangeController setTuningParameters:tuningParameters forRangeType:rangeType];
|
||||
}
|
||||
|
||||
- (void)reloadDataWithCompletion:(void (^)())completion
|
||||
@ -15,6 +15,7 @@
|
||||
#import <AsyncDisplayKit/ASCollectionViewFlowLayoutInspector.h>
|
||||
|
||||
@class ASCellNode;
|
||||
@class ASCollectionNode;
|
||||
@protocol ASCollectionDataSource;
|
||||
@protocol ASCollectionDelegate;
|
||||
@protocol ASCollectionViewLayoutInspecting;
|
||||
@ -22,10 +23,16 @@
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Node-based collection view.
|
||||
* Asynchronous UICollectionView with Intelligent Preloading capabilities.
|
||||
*
|
||||
* ASCollectionView is a version of UICollectionView that uses nodes -- specifically, ASCellNode subclasses -- with asynchronous
|
||||
* pre-rendering instead of synchronously loading UICollectionViewCells.
|
||||
* ASCollectionNode is recommended over ASCollectionView. This class exists for adoption convenience.
|
||||
*
|
||||
* ASCollectionView is a true subclass of UICollectionView, meaning it is pointer-compatible
|
||||
* with code that currently uses UICollectionView.
|
||||
*
|
||||
* The main difference is that asyncDataSource expects -nodeForItemAtIndexPath, an ASCellNode, and
|
||||
* the sizeForItemAtIndexPath: method is eliminated (as are the performance problems caused by it).
|
||||
* This is made possible because ASCellNodes can calculate their own size, and preload ahead of time.
|
||||
*/
|
||||
@interface ASCollectionView : UICollectionView
|
||||
|
||||
@ -37,6 +44,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout;
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
|
||||
|
||||
// The corresponding ASCollectionNode, which exists even if directly allocating & handling the view class.
|
||||
@property (nonatomic, weak, readonly) ASCollectionNode *collectionNode;
|
||||
|
||||
@property (nonatomic, weak) id<ASCollectionDelegate> asyncDelegate;
|
||||
@property (nonatomic, weak) id<ASCollectionDataSource> asyncDataSource;
|
||||
|
||||
@ -282,6 +292,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)clearFetchedData;
|
||||
|
||||
/**
|
||||
* Forces the .contentInset to be UIEdgeInsetsZero.
|
||||
*
|
||||
* @discussion By default, UIKit sets the top inset to the navigation bar height, even for horizontally
|
||||
* scrolling views. This can only be disabled by setting a property on the containing UIViewController,
|
||||
* automaticallyAdjustsScrollViewInsets, which may not be accessible. ASPagerNode uses this to ensure
|
||||
* its flow layout behaves predictably and does not log undefined layout warnings.
|
||||
*/
|
||||
@property (nonatomic) BOOL zeroContentInsets;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@ -14,9 +14,11 @@
|
||||
#import "ASCollectionViewLayoutController.h"
|
||||
#import "ASCollectionViewFlowLayoutInspector.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
#import "ASDisplayNode+Beta.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASRangeController.h"
|
||||
#import "UICollectionViewLayout+ASConvenience.h"
|
||||
#import "_ASDisplayLayer.h"
|
||||
|
||||
static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone;
|
||||
static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero};
|
||||
@ -99,38 +101,68 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
|
||||
// Used only when ASCollectionView is created directly rather than through ASCollectionNode.
|
||||
// We create a node so that logic related to appearance, memory management, etc can be located there
|
||||
// for both the node-based and view-based version of the table.
|
||||
// This also permits sharing logic with ASTableNode, as the superclass is not UIKit-controlled.
|
||||
@property (nonatomic, strong) ASCollectionNode *strongCollectionNode;
|
||||
|
||||
// Always set, whether ASCollectionView is created directly or via ASCollectionNode.
|
||||
@property (nonatomic, weak) ASCollectionNode *collectionNode;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionNode ()
|
||||
- (instancetype)_initWithCollectionView:(ASCollectionView *)collectionView;
|
||||
@end
|
||||
|
||||
@implementation ASCollectionView
|
||||
|
||||
// Using _ASDisplayLayer ensures things like -layout are properly forwarded to ASCollectionNode.
|
||||
+ (Class)layerClass
|
||||
{
|
||||
return [_ASDisplayLayer class];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Lifecycle.
|
||||
|
||||
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout
|
||||
{
|
||||
return [self initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
return [self _initWithFrame:CGRectZero collectionViewLayout:layout ownedByNode:NO];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
|
||||
{
|
||||
ASCollectionNode *collectionNode = [[ASCollectionNode alloc] initWithFrame:frame collectionViewLayout:layout];
|
||||
return collectionNode.view;
|
||||
return [self _initWithFrame:frame collectionViewLayout:layout ownedByNode:NO];
|
||||
}
|
||||
|
||||
// FIXME: This method is deprecated and will probably be removed in or shortly after 2.0.
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
{
|
||||
return [self initWithFrame:frame collectionViewLayout:layout];
|
||||
return [self _initWithFrame:frame collectionViewLayout:layout ownedByNode:NO];
|
||||
}
|
||||
|
||||
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
|
||||
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ownedByNode:(BOOL)ownedByNode
|
||||
{
|
||||
if (!(self = [super initWithFrame:frame collectionViewLayout:layout]))
|
||||
return nil;
|
||||
|
||||
_layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self];
|
||||
if (!ownedByNode) {
|
||||
// See commentary at the definition of .strongCollectionNode for why we create an ASCollectionNode.
|
||||
// FIXME: The _view pointer of the node retains us, but the node will die immediately if we don't
|
||||
// retain it. At the moment there isn't a great solution to this, so we can't yet move our core
|
||||
// logic to ASCollectionNode (required to have a shared superclass with ASTable*).
|
||||
ASCollectionNode *collectionNode = nil; //[[ASCollectionNode alloc] _initWithCollectionView:self];
|
||||
self.strongCollectionNode = collectionNode;
|
||||
}
|
||||
|
||||
_rangeController = [[ASRangeController alloc] init];
|
||||
_layoutController = [ASDisplayNode shouldUseNewRenderingRange] ?
|
||||
[[ASCollectionViewLayoutControllerBeta alloc] initWithCollectionView:self] :
|
||||
[[ASCollectionViewLayoutControllerStable alloc] initWithCollectionView:self];
|
||||
|
||||
_rangeController = [ASDisplayNode shouldUseNewRenderingRange] ? [[ASRangeControllerBeta alloc] init]
|
||||
: [[ASRangeControllerStable alloc] init];
|
||||
_rangeController.dataSource = self;
|
||||
_rangeController.delegate = self;
|
||||
_rangeController.layoutController = _layoutController;
|
||||
@ -195,8 +227,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
if (_flowLayoutInspector == nil) {
|
||||
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout;
|
||||
ASDisplayNodeAssertNotNil(layout, @"Collection view layout must be a flow layout to use the built-in inspector");
|
||||
_flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self
|
||||
flowLayout:layout];
|
||||
_flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self flowLayout:layout];
|
||||
}
|
||||
return _flowLayoutInspector;
|
||||
}
|
||||
@ -206,7 +237,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
- (void)reloadDataWithCompletion:(void (^)())completion
|
||||
{
|
||||
ASDisplayNodeAssert(self.asyncDelegate, @"ASCollectionView's asyncDelegate property must be set.");
|
||||
ASPerformBlockOnMainThread(^{
|
||||
_superIsPendingDataLoad = YES;
|
||||
[super reloadData];
|
||||
@ -298,22 +328,23 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
[_layoutController setTuningParameters:tuningParameters forRangeType:rangeType];
|
||||
[_collectionNode setTuningParameters:tuningParameters forRangeType:rangeType];
|
||||
}
|
||||
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
return [_layoutController tuningParametersForRangeType:rangeType];
|
||||
return [_collectionNode tuningParametersForRangeType:rangeType];
|
||||
}
|
||||
|
||||
// These deprecated methods harken back from a time where only one range type existed.
|
||||
- (ASRangeTuningParameters)rangeTuningParameters
|
||||
{
|
||||
return [self tuningParametersForRangeType:ASLayoutRangeTypeRender];
|
||||
return [self tuningParametersForRangeType:ASLayoutRangeTypeDisplay];
|
||||
}
|
||||
|
||||
- (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters
|
||||
{
|
||||
[self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender];
|
||||
[self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeDisplay];
|
||||
}
|
||||
|
||||
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
||||
@ -321,6 +352,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
return [[_dataController nodeAtIndexPath:indexPath] calculatedSize];
|
||||
}
|
||||
|
||||
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes
|
||||
{
|
||||
return [_dataController completedNodes];
|
||||
}
|
||||
|
||||
- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [_dataController nodeAtIndexPath:indexPath];
|
||||
@ -527,7 +563,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath];
|
||||
if (cellNode.neverShowPlaceholders) {
|
||||
[cellNode recursivelyEnsureDisplay];
|
||||
[cellNode recursivelyEnsureDisplaySynchronously:YES];
|
||||
}
|
||||
}
|
||||
|
||||
@ -542,6 +578,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (_zeroContentInsets) {
|
||||
self.contentInset = UIEdgeInsetsZero;
|
||||
}
|
||||
|
||||
if (! CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, self.bounds.size)) {
|
||||
_maxSizeForNodesConstrainedSize = self.bounds.size;
|
||||
|
||||
@ -733,6 +773,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
#pragma mark - ASRangeControllerDataSource
|
||||
|
||||
- (ASRangeController *)rangeController
|
||||
{
|
||||
return _rangeController;
|
||||
}
|
||||
|
||||
- (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
@ -745,11 +790,21 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
return self.bounds.size;
|
||||
}
|
||||
|
||||
- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
return self.collectionNode.interfaceState;
|
||||
}
|
||||
|
||||
- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths
|
||||
{
|
||||
return [_dataController nodesAtIndexPaths:indexPaths];
|
||||
}
|
||||
|
||||
- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [_dataController nodeAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
#pragma mark - ASRangeControllerDelegate
|
||||
|
||||
- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController
|
||||
@ -902,4 +957,54 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - _ASDisplayView behavior substitutions
|
||||
// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element.
|
||||
// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView.
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow
|
||||
{
|
||||
BOOL visible = (newWindow != nil);
|
||||
ASDisplayNode *node = self.collectionNode;
|
||||
if (visible && !node.inHierarchy) {
|
||||
[node __enterHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
BOOL visible = (self.window != nil);
|
||||
ASDisplayNode *node = self.collectionNode;
|
||||
if (!visible && node.inHierarchy) {
|
||||
[node __exitHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionView dead-end intercepts
|
||||
|
||||
#if ASDISPLAYNODE_ASSERTIONS_ENABLED // Remove implementations entirely for efficiency if not asserting.
|
||||
|
||||
// intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage)
|
||||
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0)
|
||||
{
|
||||
ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd));
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0)
|
||||
{
|
||||
ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd));
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this delegate method.", NSStringFromSelector(_cmd));
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this delegate method.", NSStringFromSelector(_cmd));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@ -34,6 +34,13 @@ typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent)
|
||||
ASControlNodeEventAllEvents = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, ASControlState) {
|
||||
ASControlStateNormal = 0,
|
||||
ASControlStateHighlighted = 1 << 0, // used when ASControlNode isHighlighted is set
|
||||
ASControlStateDisabled = 1 << 1,
|
||||
ASControlStateSelected = 1 << 2, // used when ASControlNode isSeleted is set
|
||||
ASControlStateReserved = 0xFF000000 // flags reserved for internal framework use
|
||||
};
|
||||
|
||||
/**
|
||||
@abstract ASControlNode is the base class for control nodes (such as buttons), or nodes that track touches to invoke targets with action messages.
|
||||
@ -53,7 +60,13 @@ typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent)
|
||||
@abstract Indicates whether or not the receiver is highlighted.
|
||||
@discussion This is set automatically when the there is a touch inside the control and removed on exit or touch up. This is different from touchInside in that it includes an area around the control, rather than just for touches inside the control.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign, getter=isHighlighted) BOOL highlighted;
|
||||
@property (nonatomic, assign, getter=isHighlighted) BOOL highlighted;
|
||||
|
||||
/**
|
||||
@abstract Indicates whether or not the receiver is highlighted.
|
||||
@discussion This is set automatically when the receiver is tapped.
|
||||
*/
|
||||
@property (nonatomic, assign, getter=isSelected) BOOL selected;
|
||||
|
||||
#pragma mark - Tracking Touches
|
||||
/**
|
||||
|
||||
@ -46,7 +46,6 @@
|
||||
}
|
||||
|
||||
// Read-write overrides.
|
||||
@property (nonatomic, readwrite, assign, getter=isHighlighted) BOOL highlighted;
|
||||
@property (nonatomic, readwrite, assign, getter=isTracking) BOOL tracking;
|
||||
@property (nonatomic, readwrite, assign, getter=isTouchInside) BOOL touchInside;
|
||||
|
||||
@ -339,7 +338,8 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
|
||||
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^
|
||||
(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)];
|
||||
// Use a copy to itereate, the action perform could call remove causing a mutation crash.
|
||||
NSMapTable *eventDispatchTable = [[_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)] copy];
|
||||
|
||||
// For each target interested in this event...
|
||||
for (id target in eventDispatchTable)
|
||||
|
||||
@ -8,12 +8,16 @@
|
||||
|
||||
@interface ASDisplayNode (Beta)
|
||||
|
||||
+ (BOOL)shouldUseNewRenderingRange;
|
||||
+ (void)setShouldUseNewRenderingRange:(BOOL)shouldUseNewRenderingRange;
|
||||
|
||||
/** @name Layout */
|
||||
|
||||
|
||||
/**
|
||||
* @abstract Recursively ensures node and all subnodes are displayed.
|
||||
* @see Full documentation in ASDisplayNode+FrameworkPrivate.h
|
||||
*/
|
||||
- (void)recursivelyEnsureDisplay;
|
||||
- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously;
|
||||
|
||||
@end
|
||||
|
||||
@ -66,8 +66,6 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState)
|
||||
ASInterfaceStateInHierarchy = ASInterfaceStateMeasureLayout | ASInterfaceStateFetchData | ASInterfaceStateDisplay | ASInterfaceStateVisible,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
|
||||
* hierarchy off the main thread, and could do rendering off the main thread as well.
|
||||
@ -205,6 +203,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@property (nonatomic, readonly) ASInterfaceState interfaceState;
|
||||
|
||||
|
||||
/** @name Managing dimensions */
|
||||
|
||||
/**
|
||||
|
||||
@ -198,25 +198,34 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
return [_ASDisplayLayer class];
|
||||
}
|
||||
|
||||
+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node
|
||||
+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert([ASDisplayNode shouldUseNewRenderingRange], @"+scheduleNodeForRecursiveDisplay: should never be called without the new rendering range enabled");
|
||||
static NSMutableSet *nodesToDisplay = nil;
|
||||
static BOOL displayScheduled = NO;
|
||||
if (!nodesToDisplay) {
|
||||
nodesToDisplay = [[NSMutableSet alloc] init];
|
||||
static ASDN::RecursiveMutex displaySchedulerLock;
|
||||
|
||||
{
|
||||
ASDN::MutexLocker l(displaySchedulerLock);
|
||||
if (!nodesToDisplay) {
|
||||
nodesToDisplay = [[NSMutableSet alloc] init];
|
||||
}
|
||||
[nodesToDisplay addObject:node];
|
||||
}
|
||||
[nodesToDisplay addObject:node];
|
||||
|
||||
if (!displayScheduled) {
|
||||
displayScheduled = YES;
|
||||
// It's essenital that any layout pass that is scheduled during the current
|
||||
// runloop has a chance to be applied / scheduled, so always perform this after the current runloop.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
ASDN::MutexLocker l(displaySchedulerLock);
|
||||
displayScheduled = NO;
|
||||
for (ASDisplayNode *node in nodesToDisplay) {
|
||||
NSSet *displayingNodes = [nodesToDisplay copy];
|
||||
nodesToDisplay = nil;
|
||||
for (ASDisplayNode *node in displayingNodes) {
|
||||
[node __recursivelyTriggerDisplayAndBlock:NO];
|
||||
}
|
||||
nodesToDisplay = nil;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -352,6 +361,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
- (void)__unloadNode
|
||||
{
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
ASDisplayNodeAssert([self isNodeLoaded], @"Implementation shouldn't call __unloadNode if not loaded: %@", self);
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
|
||||
if (_flags.layerBacked)
|
||||
@ -1543,23 +1553,32 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
recursivelyTriggerDisplayForLayer(layer, shouldBlock);
|
||||
}
|
||||
|
||||
- (void)recursivelyEnsureDisplay
|
||||
- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously
|
||||
{
|
||||
[self __recursivelyTriggerDisplayAndBlock:YES];
|
||||
[self __recursivelyTriggerDisplayAndBlock:synchronously];
|
||||
}
|
||||
|
||||
- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay;
|
||||
}
|
||||
|
||||
- (BOOL)shouldBypassEnsureDisplay
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _flags.shouldBypassEnsureDisplay;
|
||||
}
|
||||
|
||||
static BOOL ShouldUseNewRenderingRange = NO;
|
||||
|
||||
+ (BOOL)shouldUseNewRenderingRange
|
||||
{
|
||||
return ShouldUseNewRenderingRange;
|
||||
}
|
||||
+ (void)setShouldUseNewRenderingRange:(BOOL)shouldUseNewRenderingRange
|
||||
{
|
||||
ShouldUseNewRenderingRange = shouldUseNewRenderingRange;
|
||||
}
|
||||
|
||||
#pragma mark - For Subclasses
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
@ -1594,7 +1613,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
return [ASLayoutSpec new];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (ASLayout *)calculatedLayout
|
||||
@ -1759,29 +1778,61 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
// Trigger asynchronous measurement if it is not already cached or being calculated.
|
||||
}
|
||||
|
||||
// For the FetchData and Display ranges, we don't want to call -clear* if not being managed by a range controller.
|
||||
// Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop.
|
||||
// Still, the interfaceState should be updated to the current state of the node; just don't act on the transition.
|
||||
|
||||
// Entered or exited data loading state.
|
||||
if ((newState & ASInterfaceStateFetchData) != (oldState & ASInterfaceStateFetchData)) {
|
||||
if (newState & ASInterfaceStateFetchData) {
|
||||
BOOL nowFetchData = ASInterfaceStateIncludesFetchData(newState);
|
||||
BOOL wasFetchData = ASInterfaceStateIncludesFetchData(oldState);
|
||||
|
||||
if (nowFetchData != wasFetchData) {
|
||||
if (nowFetchData) {
|
||||
[self fetchData];
|
||||
} else {
|
||||
[self clearFetchedData];
|
||||
if ([self supportsRangeManagedInterfaceState]) {
|
||||
[self clearFetchedData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entered or exited contents rendering state.
|
||||
if ((newState & ASInterfaceStateDisplay) != (oldState & ASInterfaceStateDisplay)) {
|
||||
if (newState & ASInterfaceStateDisplay) {
|
||||
// Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here.
|
||||
[self setDisplaySuspended:NO];
|
||||
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState);
|
||||
BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState);
|
||||
|
||||
if (nowDisplay != wasDisplay) {
|
||||
if ([self supportsRangeManagedInterfaceState]) {
|
||||
if (nowDisplay) {
|
||||
// Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here.
|
||||
[self setDisplaySuspended:NO];
|
||||
} else {
|
||||
[self setDisplaySuspended:YES];
|
||||
[self clearContents];
|
||||
}
|
||||
} else {
|
||||
[self setDisplaySuspended:YES];
|
||||
[self clearContents];
|
||||
// NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all
|
||||
// internal use cases are range-managed. When a node is visible, don't mess with display - CA will start it.
|
||||
if ([ASDisplayNode shouldUseNewRenderingRange] && !ASInterfaceStateIncludesVisible(newState)) {
|
||||
// Check __implementsDisplay purely for efficiency - it's faster even than calling -asyncLayer.
|
||||
if ([self __implementsDisplay]) {
|
||||
if (nowDisplay) {
|
||||
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
|
||||
} else {
|
||||
[[self asyncLayer] cancelAsyncDisplay];
|
||||
[self clearContents];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entered or exited data loading state.
|
||||
if ((newState & ASInterfaceStateVisible) != (oldState & ASInterfaceStateVisible)) {
|
||||
if (newState & ASInterfaceStateVisible) {
|
||||
// Became visible or invisible. When range-managed, this represents literal visibility - at least one pixel
|
||||
// is onscreen. If not range-managed, we can't guarantee more than the node being present in an onscreen window.
|
||||
BOOL nowVisible = ASInterfaceStateIncludesVisible(newState);
|
||||
BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState);
|
||||
|
||||
if (nowVisible != wasVisible) {
|
||||
if (nowVisible) {
|
||||
[self visibilityDidChange:YES];
|
||||
} else {
|
||||
[self visibilityDidChange:NO];
|
||||
@ -1815,6 +1866,27 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
});
|
||||
}
|
||||
|
||||
- (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState
|
||||
{
|
||||
ASInterfaceState oldState = self.interfaceState;
|
||||
ASInterfaceState newState = interfaceState;
|
||||
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
|
||||
node.interfaceState = interfaceState;
|
||||
});
|
||||
|
||||
if ([self supportsRangeManagedInterfaceState]) {
|
||||
// Instead of each node in the recursion assuming it needs to schedule itself for display,
|
||||
// setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set).
|
||||
// If our range manager intends for us to be displayed right now, and didn't before, get started!
|
||||
|
||||
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState);
|
||||
BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState);
|
||||
if (nowDisplay && (nowDisplay != wasDisplay)) {
|
||||
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (ASHierarchyState)hierarchyState
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
|
||||
@ -12,6 +12,23 @@
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
|
||||
// Because inline methods can't be extern'd and need to be part of the translation unit of code
|
||||
// that compiles with them to actually inline, we both declare and define these in the header.
|
||||
inline BOOL ASInterfaceStateIncludesVisible(ASInterfaceState interfaceState)
|
||||
{
|
||||
return ((interfaceState & ASInterfaceStateVisible) == ASInterfaceStateVisible);
|
||||
}
|
||||
|
||||
inline BOOL ASInterfaceStateIncludesDisplay(ASInterfaceState interfaceState)
|
||||
{
|
||||
return ((interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay);
|
||||
}
|
||||
|
||||
inline BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState)
|
||||
{
|
||||
return ((interfaceState & ASInterfaceStateFetchData) == ASInterfaceStateFetchData);
|
||||
}
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_BEGIN
|
||||
|
||||
15
AsyncDisplayKit/ASNetworkImageNode.mm
Normal file → Executable file
15
AsyncDisplayKit/ASNetworkImageNode.mm
Normal file → Executable file
@ -188,14 +188,23 @@
|
||||
ASDN::MutexLocker l(_lock);
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_imageLoaded = YES;
|
||||
|
||||
if (self.shouldCacheImage) {
|
||||
self.image = [UIImage imageNamed:_URL.path];
|
||||
self.image = [UIImage imageNamed:_URL.path.lastPathComponent];
|
||||
} else {
|
||||
// First try to load the path directly, for efficiency assuming a developer who
|
||||
// doesn't want caching is trying to be as minimal as possible.
|
||||
self.image = [UIImage imageWithContentsOfFile:_URL.path];
|
||||
if (!self.image) {
|
||||
// If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the
|
||||
// extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage.
|
||||
NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil];
|
||||
if (filename) {
|
||||
self.image = [UIImage imageWithContentsOfFile:filename];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_imageLoaded = YES;
|
||||
[_delegate imageNode:self didLoadImage:self.image];
|
||||
});
|
||||
}
|
||||
|
||||
@ -23,19 +23,19 @@
|
||||
- (instancetype)init;
|
||||
|
||||
// Initializer with custom-configured flow layout properties.
|
||||
- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout;
|
||||
|
||||
// The underlying ASCollectionView object.
|
||||
- (ASCollectionView *)collectionView;
|
||||
|
||||
// Delegate is optional, and uses the same protocol as ASCollectionNode.
|
||||
// This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay...
|
||||
@property (weak, nonatomic) id <ASCollectionDelegate> delegate;
|
||||
- (instancetype)initWithCollectionViewLayout:(UICollectionViewFlowLayout *)flowLayout;
|
||||
|
||||
// Data Source is required, and uses a different protocol from ASCollectionNode.
|
||||
- (void)setDataSource:(id <ASPagerNodeDataSource>)dataSource;
|
||||
- (id <ASPagerNodeDataSource>)dataSource;
|
||||
|
||||
// Delegate is optional, and uses the same protocol as ASCollectionNode.
|
||||
// This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay...
|
||||
@property (nonatomic, weak) id <ASCollectionDelegate> delegate;
|
||||
|
||||
// The underlying ASCollectionView object.
|
||||
@property (nonatomic, readonly) ASCollectionView *view;
|
||||
|
||||
- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
@ -9,8 +9,10 @@
|
||||
#import "ASPagerNode.h"
|
||||
#import "ASDelegateProxy.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "UICollectionViewLayout+ASConvenience.h"
|
||||
|
||||
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionViewDelegateFlowLayout, ASDelegateProxyInterceptor> {
|
||||
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionViewDelegateFlowLayout, ASDelegateProxyInterceptor>
|
||||
{
|
||||
UICollectionViewFlowLayout *_flowLayout;
|
||||
ASPagerNodeProxy *_proxy;
|
||||
id <ASPagerNodeDataSource> _pagerDataSource;
|
||||
@ -19,7 +21,7 @@
|
||||
@end
|
||||
|
||||
@implementation ASPagerNode
|
||||
@dynamic delegate;
|
||||
@dynamic view, delegate, dataSource;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
@ -28,11 +30,12 @@
|
||||
flowLayout.minimumInteritemSpacing = 0;
|
||||
flowLayout.minimumLineSpacing = 0;
|
||||
|
||||
return [self initWithFlowLayout:flowLayout];
|
||||
return [self initWithCollectionViewLayout:flowLayout];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout
|
||||
- (instancetype)initWithCollectionViewLayout:(UICollectionViewFlowLayout *)flowLayout;
|
||||
{
|
||||
ASDisplayNodeAssert([flowLayout asdk_isFlowLayout], @"ASPagerNode requires a flow layout.");
|
||||
self = [super initWithCollectionViewLayout:flowLayout];
|
||||
if (self != nil) {
|
||||
_flowLayout = flowLayout;
|
||||
@ -40,9 +43,62 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASCollectionView *)collectionView
|
||||
- (void)didLoad
|
||||
{
|
||||
return self.view;
|
||||
[super didLoad];
|
||||
|
||||
ASCollectionView *cv = self.view;
|
||||
|
||||
cv.pagingEnabled = YES;
|
||||
cv.allowsSelection = NO;
|
||||
cv.showsVerticalScrollIndicator = NO;
|
||||
cv.showsHorizontalScrollIndicator = NO;
|
||||
cv.scrollsToTop = NO;
|
||||
|
||||
// Zeroing contentInset is important, as UIKit will set the top inset for the navigation bar even though
|
||||
// our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning.
|
||||
// From here we cannot disable this directly (UIViewController's automaticallyAdjustsScrollViewInsets).
|
||||
cv.zeroContentInsets = YES;
|
||||
|
||||
ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 };
|
||||
ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 };
|
||||
[self setTuningParameters:preloadParams forRangeType:ASLayoutRangeTypeFetchData];
|
||||
[self setTuningParameters:renderParams forRangeType:ASLayoutRangeTypeDisplay];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated
|
||||
{
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
|
||||
[self.view scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated];
|
||||
}
|
||||
|
||||
#pragma mark - ASCollectionViewDataSource
|
||||
|
||||
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display");
|
||||
ASCellNode *pageNode = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item];
|
||||
return pageNode;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display");
|
||||
return [_pagerDataSource numberOfPagesInPagerNode:self];
|
||||
}
|
||||
|
||||
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return ASSizeRangeMake(CGSizeZero, self.view.bounds.size);
|
||||
}
|
||||
|
||||
#pragma mark - Data Source Proxy
|
||||
|
||||
- (id <ASPagerNodeDataSource>)dataSource
|
||||
{
|
||||
return _pagerDataSource;
|
||||
}
|
||||
|
||||
- (void)setDataSource:(id <ASPagerNodeDataSource>)pagerDataSource
|
||||
@ -59,56 +115,4 @@
|
||||
[self setDataSource:nil];
|
||||
}
|
||||
|
||||
- (id <ASPagerNodeDataSource>)dataSource
|
||||
{
|
||||
return _pagerDataSource;
|
||||
}
|
||||
|
||||
- (void)didLoad
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
ASCollectionView *cv = self.view;
|
||||
cv.asyncDataSource = self;
|
||||
cv.asyncDelegate = self;
|
||||
|
||||
cv.pagingEnabled = YES;
|
||||
cv.allowsSelection = NO;
|
||||
cv.showsVerticalScrollIndicator = NO;
|
||||
cv.showsHorizontalScrollIndicator = NO;
|
||||
cv.scrollsToTop = NO;
|
||||
|
||||
ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 };
|
||||
ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 };
|
||||
[self setTuningParameters:preloadParams forRangeType:ASLayoutRangeTypePreload];
|
||||
[self setTuningParameters:renderParams forRangeType:ASLayoutRangeTypeRender];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated
|
||||
{
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
|
||||
[self.view scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated];
|
||||
}
|
||||
|
||||
#pragma mark - ASCollectionViewDataSource
|
||||
|
||||
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes");
|
||||
return [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item];
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes");
|
||||
return [_pagerDataSource numberOfPagesInPagerNode:self];
|
||||
}
|
||||
|
||||
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return ASSizeRangeMake(CGSizeZero, self.view.bounds.size);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -7,8 +7,9 @@
|
||||
//
|
||||
|
||||
#import "ASFlowLayoutController.h"
|
||||
#import "ASTableNode.h"
|
||||
#import "ASTableViewInternal.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASRangeController.h"
|
||||
|
||||
@interface _ASTablePendingState : NSObject
|
||||
@property (weak, nonatomic) id <ASTableDelegate> delegate;
|
||||
@ -28,11 +29,24 @@
|
||||
|
||||
@implementation ASTableNode
|
||||
|
||||
- (instancetype)_initWithStyle:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass
|
||||
- (instancetype)_initWithTableView:(ASTableView *)tableView
|
||||
{
|
||||
if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] _initWithFrame:CGRectZero
|
||||
style:style
|
||||
dataControllerClass:dataControllerClass]; }]) {
|
||||
// Avoid a retain cycle. In this case, the ASTableView is creating us, and strongly retains us.
|
||||
ASTableView * __weak weakTableView = tableView;
|
||||
if (self = [super initWithViewBlock:^UIView *{ return weakTableView; }]) {
|
||||
__unused __weak ASTableView *view = [self view];
|
||||
return self;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass
|
||||
{
|
||||
ASDisplayNodeViewBlock tableViewBlock = ^UIView *{
|
||||
return [[ASTableView alloc] _initWithFrame:frame style:style dataControllerClass:dataControllerClass ownedByNode:YES];
|
||||
};
|
||||
|
||||
if (self = [super initWithViewBlock:tableViewBlock]) {
|
||||
return self;
|
||||
}
|
||||
return nil;
|
||||
@ -40,23 +54,24 @@
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewStyle)style
|
||||
{
|
||||
return [self _initWithStyle:style dataControllerClass:nil];
|
||||
return [self _initWithFrame:CGRectZero style:style dataControllerClass:nil];
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self _initWithStyle:UITableViewStylePlain dataControllerClass:nil];
|
||||
return [self _initWithFrame:CGRectZero style:UITableViewStylePlain dataControllerClass:nil];
|
||||
}
|
||||
|
||||
- (void)didLoad
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
ASTableView *view = self.view;
|
||||
view.tableNode = self;
|
||||
|
||||
if (_pendingState) {
|
||||
_ASTablePendingState *pendingState = _pendingState;
|
||||
self.pendingState = nil;
|
||||
|
||||
ASTableView *view = self.view;
|
||||
self.pendingState = nil;
|
||||
view.asyncDelegate = pendingState.delegate;
|
||||
view.asyncDataSource = pendingState.dataSource;
|
||||
}
|
||||
@ -114,6 +129,14 @@
|
||||
return (ASTableView *)[super view];
|
||||
}
|
||||
|
||||
#if RangeControllerLoggingEnabled
|
||||
- (void)visibilityDidChange:(BOOL)isVisible
|
||||
{
|
||||
[super visibilityDidChange:isVisible];
|
||||
NSLog(@"%@ - visible: %d", self, isVisible);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)clearContents
|
||||
{
|
||||
[super clearContents];
|
||||
|
||||
@ -19,10 +19,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@protocol ASTableDelegate;
|
||||
|
||||
/**
|
||||
* Node-based table view.
|
||||
* Asynchronous UITableView with Intelligent Preloading capabilities.
|
||||
*
|
||||
* ASTableView is a version of UITableView that uses nodes -- specifically, ASCellNode subclasses -- with asynchronous
|
||||
* pre-rendering instead of synchronously loading UITableViewCells.
|
||||
* ASTableNode is recommended over ASTableView. This class is provided for adoption convenience.
|
||||
*
|
||||
* ASTableView is a true subclass of UITableView, meaning it is pointer-compatible with code that
|
||||
* currently uses UITableView
|
||||
*
|
||||
* The main difference is that asyncDataSource expects -nodeForRowAtIndexPath, an ASCellNode, and
|
||||
* the heightForRowAtIndexPath: method is eliminated (as are the performance problems caused by it).
|
||||
* This is made possible because ASCellNodes can calculate their own size, and preload ahead of time.
|
||||
*/
|
||||
@interface ASTableView : UITableView
|
||||
|
||||
|
||||
@ -13,11 +13,13 @@
|
||||
#import "ASChangeSetDataController.h"
|
||||
#import "ASCollectionViewLayoutController.h"
|
||||
#import "ASDelegateProxy.h"
|
||||
#import "ASDisplayNode+Beta.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASLayout.h"
|
||||
#import "ASLayoutController.h"
|
||||
#import "ASRangeController.h"
|
||||
#import "_ASDisplayLayer.h"
|
||||
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
@ -81,10 +83,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
#pragma mark ASTableView
|
||||
|
||||
@interface ASTableNode ()
|
||||
- (instancetype)_initWithStyle:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass;
|
||||
- (instancetype)_initWithTableView:(ASTableView *)tableView;
|
||||
@end
|
||||
|
||||
@interface ASTableView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate, ASCellNodeLayoutDelegate, ASDelegateProxyInterceptor> {
|
||||
@interface ASTableView () <ASRangeControllerDataSource, ASRangeControllerDelegate,
|
||||
ASDataControllerSource, _ASTableViewCellDelegate,
|
||||
ASCellNodeLayoutDelegate, ASDelegateProxyInterceptor>
|
||||
{
|
||||
ASTableViewProxy *_proxyDataSource;
|
||||
ASTableViewProxy *_proxyDelegate;
|
||||
|
||||
@ -110,10 +115,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
@property (nonatomic, retain, readwrite) ASDataController *dataController;
|
||||
|
||||
// Used only when ASTableView is created directly rather than through ASTableNode.
|
||||
// We create a node so that logic related to appearance, memory management, etc can be located there
|
||||
// for both the node-based and view-based version of the table.
|
||||
// This also permits sharing logic with ASCollectionNode, as the superclass is not UIKit-controlled.
|
||||
@property (nonatomic, retain) ASTableNode *strongTableNode;
|
||||
|
||||
// Always set, whether ASCollectionView is created directly or via ASCollectionNode.
|
||||
@property (nonatomic, weak) ASTableNode *tableNode;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTableView
|
||||
|
||||
// Using _ASDisplayLayer ensures things like -layout are properly forwarded to ASTableNode.
|
||||
+ (Class)layerClass
|
||||
{
|
||||
return [_ASDisplayLayer class];
|
||||
}
|
||||
|
||||
+ (Class)dataControllerClass
|
||||
{
|
||||
return [ASChangeSetDataController class];
|
||||
@ -122,22 +142,23 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
#pragma mark -
|
||||
#pragma mark Lifecycle
|
||||
|
||||
- (void)configureWithDataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetching
|
||||
- (void)configureWithDataControllerClass:(Class)dataControllerClass
|
||||
{
|
||||
_layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical];
|
||||
|
||||
_rangeController = [[ASRangeController alloc] init];
|
||||
_rangeController = [ASDisplayNode shouldUseNewRenderingRange] ? [[ASRangeControllerBeta alloc] init]
|
||||
: [[ASRangeControllerStable alloc] init];
|
||||
_rangeController.layoutController = _layoutController;
|
||||
_rangeController.dataSource = self;
|
||||
_rangeController.delegate = self;
|
||||
|
||||
_dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:asyncDataFetching];
|
||||
_dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:NO];
|
||||
_dataController.dataSource = self;
|
||||
_dataController.delegate = _rangeController;
|
||||
|
||||
_layoutController.dataSource = _dataController;
|
||||
|
||||
_asyncDataFetchingEnabled = asyncDataFetching;
|
||||
_asyncDataFetchingEnabled = NO;
|
||||
_asyncDataSourceLocked = NO;
|
||||
|
||||
_leadingScreensForBatching = 1.0;
|
||||
@ -161,32 +182,35 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
|
||||
{
|
||||
return [self initWithFrame:frame style:style asyncDataFetching:NO];
|
||||
return [self _initWithFrame:frame style:style dataControllerClass:nil ownedByNode:NO];
|
||||
}
|
||||
|
||||
// FIXME: This method is deprecated and will probably be removed in or shortly after 2.0.
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
{
|
||||
return [self initWithFrame:frame style:style dataControllerClass:[self.class dataControllerClass] asyncDataFetching:asyncDataFetchingEnabled];
|
||||
return [self _initWithFrame:frame style:style dataControllerClass:nil ownedByNode:NO];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass ownedByNode:(BOOL)ownedByNode
|
||||
{
|
||||
// ASTableNode *tableNode = [[ASTableNode alloc] _initWithStyle:style dataControllerClass:dataControllerClass];
|
||||
// tableNode.frame = frame;
|
||||
// return tableNode.view;
|
||||
return [self _initWithFrame:frame style:style dataControllerClass:dataControllerClass];
|
||||
}
|
||||
|
||||
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass
|
||||
{
|
||||
if (!(self = [super initWithFrame:frame style:style]))
|
||||
if (!(self = [super initWithFrame:frame style:style])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (!dataControllerClass) {
|
||||
dataControllerClass = [self.class dataControllerClass];
|
||||
dataControllerClass = [[self class] dataControllerClass];
|
||||
}
|
||||
|
||||
[self configureWithDataControllerClass:dataControllerClass];
|
||||
|
||||
if (!ownedByNode) {
|
||||
// See commentary at the definition of .strongTableNode for why we create an ASTableNode.
|
||||
// FIXME: The _view pointer of the node retains us, but the node will die immediately if we don't
|
||||
// retain it. At the moment there isn't a great solution to this, so we can't yet move our core
|
||||
// logic to ASTableNode (required to have a shared superclass with ASCollection*).
|
||||
ASTableNode *tableNode = nil; //[[ASTableNode alloc] _initWithTableView:self];
|
||||
self.strongTableNode = tableNode;
|
||||
}
|
||||
[self configureWithDataControllerClass:dataControllerClass asyncDataFetching:NO];
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -274,7 +298,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (void)reloadDataWithCompletion:(void (^)())completion
|
||||
{
|
||||
ASDisplayNodeAssert(self.asyncDelegate, @"ASTableView's asyncDelegate property must be set.");
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[super reloadData];
|
||||
});
|
||||
@ -305,12 +328,17 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (ASRangeTuningParameters)rangeTuningParameters
|
||||
{
|
||||
return [self tuningParametersForRangeType:ASLayoutRangeTypeRender];
|
||||
return [self tuningParametersForRangeType:ASLayoutRangeTypeDisplay];
|
||||
}
|
||||
|
||||
- (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters
|
||||
{
|
||||
[self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender];
|
||||
[self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeDisplay];
|
||||
}
|
||||
|
||||
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes
|
||||
{
|
||||
return [_dataController completedNodes];
|
||||
}
|
||||
|
||||
- (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
@ -558,7 +586,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
ASCellNode *cellNode = [self nodeForRowAtIndexPath:indexPath];
|
||||
if (cellNode.neverShowPlaceholders) {
|
||||
[cellNode recursivelyEnsureDisplay];
|
||||
[cellNode recursivelyEnsureDisplaySynchronously:YES];
|
||||
}
|
||||
}
|
||||
|
||||
@ -672,12 +700,22 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
return [_dataController nodesAtIndexPaths:indexPaths];
|
||||
}
|
||||
|
||||
- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [_dataController nodeAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return self.bounds.size;
|
||||
}
|
||||
|
||||
- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
return self.tableNode.interfaceState;
|
||||
}
|
||||
|
||||
#pragma mark - ASRangeControllerDelegate
|
||||
|
||||
- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController
|
||||
@ -921,4 +959,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - _ASDisplayView behavior substitutions
|
||||
// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element.
|
||||
// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView.
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow
|
||||
{
|
||||
BOOL visible = (newWindow != nil);
|
||||
ASDisplayNode *node = self.tableNode;
|
||||
if (visible && !node.inHierarchy) {
|
||||
[node __enterHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
BOOL visible = (self.window != nil);
|
||||
ASDisplayNode *node = self.tableNode;
|
||||
if (!visible && node.inHierarchy) {
|
||||
[node __exitHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
@interface ASTableView (Internal)
|
||||
|
||||
@property (nonatomic, retain, readonly) ASDataController *dataController;
|
||||
@property (nonatomic, weak, readwrite) ASTableNode *tableNode;
|
||||
|
||||
/**
|
||||
* Initializer.
|
||||
@ -26,6 +27,6 @@
|
||||
*
|
||||
* @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass ownedByNode:(BOOL)ownedByNode;
|
||||
|
||||
@end
|
||||
|
||||
@ -413,9 +413,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
if (backgroundColor) {
|
||||
CGContextSetFillColorWithColor(context, backgroundColor);
|
||||
CGContextSetBlendMode(context, kCGBlendModeCopy);
|
||||
// outset the background fill to cover fractional errors when drawing at a
|
||||
// small contentsScale.
|
||||
CGContextFillRect(context, CGRectInset(bounds, -2, -2));
|
||||
CGContextFillRect(context, CGContextGetClipBoundingBox(context));
|
||||
CGContextSetBlendMode(context, kCGBlendModeNormal);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,11 +11,11 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASViewController : UIViewController
|
||||
@interface ASViewController<__covariant DisplayNodeType : ASDisplayNode *> : UIViewController
|
||||
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, strong, readonly) ASDisplayNode *node;
|
||||
@property (nonatomic, strong, readonly) DisplayNodeType node;
|
||||
|
||||
/**
|
||||
* @abstract Passthrough property to the the .interfaceState of the node.
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
{
|
||||
if (_ensureDisplayed && self.neverShowPlaceholders) {
|
||||
_ensureDisplayed = NO;
|
||||
[self.node recursivelyEnsureDisplay];
|
||||
[self.node recursivelyEnsureDisplaySynchronously:YES];
|
||||
}
|
||||
[super viewDidLayoutSubviews];
|
||||
}
|
||||
|
||||
@ -13,10 +13,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASAbstractLayoutController : NSObject <ASLayoutController>
|
||||
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType;
|
||||
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -7,13 +7,12 @@
|
||||
*/
|
||||
|
||||
#import "ASAbstractLayoutController.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#import "ASAssert.h"
|
||||
#include <vector>
|
||||
|
||||
@interface ASAbstractLayoutController () {
|
||||
std::vector<ASRangeTuningParameters> _tuningParameters;
|
||||
CGSize _viewportSize;
|
||||
}
|
||||
@end
|
||||
|
||||
@ -30,11 +29,11 @@
|
||||
.leadingBufferScreenfuls = 0,
|
||||
.trailingBufferScreenfuls = 0
|
||||
};
|
||||
_tuningParameters[ASLayoutRangeTypeRender] = {
|
||||
_tuningParameters[ASLayoutRangeTypeDisplay] = {
|
||||
.leadingBufferScreenfuls = 1.5,
|
||||
.trailingBufferScreenfuls = 0.75
|
||||
};
|
||||
_tuningParameters[ASLayoutRangeTypePreload] = {
|
||||
_tuningParameters[ASLayoutRangeTypeFetchData] = {
|
||||
.leadingBufferScreenfuls = 3,
|
||||
.trailingBufferScreenfuls = 2
|
||||
};
|
||||
@ -60,16 +59,27 @@
|
||||
|
||||
#pragma mark - Abstract Index Path Range Support
|
||||
|
||||
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType
|
||||
// FIXME: This method can be removed once ASRangeControllerBeta becomes the main version.
|
||||
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASDisplayNodeAssertNotSupported();
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASDisplayNodeAssertNotSupported();
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setViewportSize:(CGSize)viewportSize
|
||||
{
|
||||
_viewportSize = viewportSize;
|
||||
}
|
||||
|
||||
- (CGSize)viewportSize
|
||||
{
|
||||
return _viewportSize;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
#import "ASAssert.h"
|
||||
#import "ASMultidimensionalArrayUtils.h"
|
||||
#import "ASDisplayNode.h"
|
||||
#import "ASCellNode.h"
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import "ASDataController+Subclasses.h"
|
||||
|
||||
@ -142,7 +142,9 @@
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections);
|
||||
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
// reinsert the elements
|
||||
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil];
|
||||
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) {
|
||||
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
}];
|
||||
[_pendingNodes removeObjectForKey:kind];
|
||||
[_pendingIndexPaths removeObjectForKey:kind];
|
||||
}];
|
||||
@ -187,7 +189,8 @@
|
||||
for (NSUInteger i = 0; i < rowNum; i++) {
|
||||
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
|
||||
[indexPaths addObject:indexPath];
|
||||
[nodes addObject:[self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]];
|
||||
ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath];
|
||||
[nodes addObject:supplementaryNode];
|
||||
}
|
||||
}];
|
||||
}
|
||||
@ -208,7 +211,17 @@
|
||||
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return [self completedNodesOfKind:kind][indexPath.section][indexPath.item];
|
||||
NSArray *nodesOfKind = [self completedNodesOfKind:kind];
|
||||
NSInteger section = indexPath.section;
|
||||
if (section < nodesOfKind.count) {
|
||||
NSArray *nodesOfKindInSection = nodesOfKind[section];
|
||||
NSInteger itemIndex = indexPath.item;
|
||||
if (itemIndex < nodesOfKindInSection.count) {
|
||||
return nodesOfKindInSection[itemIndex];
|
||||
}
|
||||
}
|
||||
ASDisplayNodeAssert(NO, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self.collectionDataSource);
|
||||
return [[ASCellNode alloc] init];
|
||||
}
|
||||
|
||||
#pragma mark - Private Helpers
|
||||
|
||||
18
AsyncDisplayKit/Details/ASCollectionInternal.h
Normal file
18
AsyncDisplayKit/Details/ASCollectionInternal.h
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// ASCollectionInternal.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Scott Goodson on 1/1/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASCollectionView.h"
|
||||
#import "ASCollectionNode.h"
|
||||
#import "ASRangeController.h"
|
||||
|
||||
@interface ASCollectionView ()
|
||||
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ownedByNode:(BOOL)ownedByNode;
|
||||
|
||||
@property (nonatomic, weak, readwrite) ASCollectionNode *collectionNode;
|
||||
@property (nonatomic, strong, readonly) ASRangeController *rangeController;
|
||||
@end
|
||||
9
AsyncDisplayKit/Details/ASCollectionInternal.m
Normal file
9
AsyncDisplayKit/Details/ASCollectionInternal.m
Normal file
@ -0,0 +1,9 @@
|
||||
//
|
||||
// ASCollectionInternal.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Scott Goodson on 1/1/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASCollectionInternal.h"
|
||||
@ -19,4 +19,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionViewLayoutControllerStable : ASCollectionViewLayoutController
|
||||
@end
|
||||
|
||||
@interface ASCollectionViewLayoutControllerBeta : ASCollectionViewLayoutController
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -15,36 +15,6 @@
|
||||
#import "CGRect+ASConvenience.h"
|
||||
#import "UICollectionViewLayout+ASConvenience.h"
|
||||
|
||||
struct ASDirectionalScreenfulBuffer {
|
||||
CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space.
|
||||
CGFloat negativeDirection;
|
||||
};
|
||||
typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer;
|
||||
|
||||
ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection,
|
||||
ASRangeTuningParameters rangeTuningParameters)
|
||||
{
|
||||
ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0};
|
||||
BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection);
|
||||
horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls :
|
||||
rangeTuningParameters.trailingBufferScreenfuls;
|
||||
horizontalBuffer.negativeDirection = movingRight ? rangeTuningParameters.trailingBufferScreenfuls :
|
||||
rangeTuningParameters.leadingBufferScreenfuls;
|
||||
return horizontalBuffer;
|
||||
}
|
||||
|
||||
ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection,
|
||||
ASRangeTuningParameters rangeTuningParameters)
|
||||
{
|
||||
ASDirectionalScreenfulBuffer verticalBuffer = {0, 0};
|
||||
BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection);
|
||||
verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls :
|
||||
rangeTuningParameters.trailingBufferScreenfuls;
|
||||
verticalBuffer.negativeDirection = movingDown ? rangeTuningParameters.trailingBufferScreenfuls :
|
||||
rangeTuningParameters.leadingBufferScreenfuls;
|
||||
return verticalBuffer;
|
||||
}
|
||||
|
||||
struct ASRangeGeometry {
|
||||
CGRect rangeBounds;
|
||||
CGRect updateBounds;
|
||||
@ -57,9 +27,9 @@ typedef struct ASRangeGeometry ASRangeGeometry;
|
||||
|
||||
@interface ASCollectionViewLayoutController ()
|
||||
{
|
||||
@package
|
||||
ASCollectionView * __weak _collectionView;
|
||||
UICollectionViewLayout * __strong _collectionViewLayout;
|
||||
std::vector<CGRect> _updateRangeBoundsIndexedByRangeType;
|
||||
ASScrollDirection _scrollableDirections;
|
||||
}
|
||||
@end
|
||||
@ -75,63 +45,34 @@ typedef struct ASRangeGeometry ASRangeGeometry;
|
||||
_scrollableDirections = [collectionView scrollableDirections];
|
||||
_collectionView = collectionView;
|
||||
_collectionViewLayout = [collectionView collectionViewLayout];
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionViewLayoutControllerStable
|
||||
{
|
||||
std::vector<CGRect> _updateRangeBoundsIndexedByRangeType;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView
|
||||
{
|
||||
if (!(self = [super initWithCollectionView:collectionView])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_updateRangeBoundsIndexedByRangeType = std::vector<CGRect>(ASLayoutRangeTypeCount);
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Index Paths in Range
|
||||
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection
|
||||
viewportSize:(CGSize)viewportSize
|
||||
rangeType:(ASLayoutRangeType)rangeType
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection
|
||||
rangeTuningParameters:[self tuningParametersForRangeType:rangeType]];
|
||||
ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType];
|
||||
ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection tuningParameters:tuningParameters];
|
||||
_updateRangeBoundsIndexedByRangeType[rangeType] = rangeGeometry.updateBounds;
|
||||
return [self indexPathsForItemsWithinRangeBounds:rangeGeometry.rangeBounds];
|
||||
}
|
||||
|
||||
- (ASRangeGeometry)rangeGeometryWithScrollDirection:(ASScrollDirection)scrollDirection
|
||||
rangeTuningParameters:(ASRangeTuningParameters)rangeTuningParameters
|
||||
{
|
||||
CGRect rangeBounds = _collectionView.bounds;
|
||||
CGRect updateBounds = _collectionView.bounds;
|
||||
|
||||
//scrollable directions can change for non-flow layouts
|
||||
if ([_collectionViewLayout asdk_isFlowLayout] == NO) {
|
||||
_scrollableDirections = [_collectionView scrollableDirections];
|
||||
}
|
||||
|
||||
BOOL canScrollHorizontally = ASScrollDirectionContainsHorizontalDirection(_scrollableDirections);
|
||||
if (canScrollHorizontally) {
|
||||
ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection,
|
||||
rangeTuningParameters);
|
||||
rangeBounds = asdk_CGRectExpandHorizontally(rangeBounds,
|
||||
horizontalBuffer.negativeDirection,
|
||||
horizontalBuffer.positiveDirection);
|
||||
// Update bounds is at most 95% of the next/previous screenful and at least half of tuning parameter value.
|
||||
updateBounds = asdk_CGRectExpandHorizontally(updateBounds,
|
||||
MIN(horizontalBuffer.negativeDirection * 0.5, 0.95),
|
||||
MIN(horizontalBuffer.positiveDirection * 0.5, 0.95));
|
||||
}
|
||||
|
||||
BOOL canScrollVertically = ASScrollDirectionContainsVerticalDirection(_scrollableDirections);
|
||||
if (canScrollVertically) {
|
||||
ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection,
|
||||
rangeTuningParameters);
|
||||
rangeBounds = asdk_CGRectExpandVertically(rangeBounds,
|
||||
verticalBuffer.negativeDirection,
|
||||
verticalBuffer.positiveDirection);
|
||||
// Update bounds is at most 95% of the next/previous screenful and at least half of tuning parameter value.
|
||||
updateBounds = asdk_CGRectExpandVertically(updateBounds,
|
||||
MIN(verticalBuffer.negativeDirection * 0.5, 0.95),
|
||||
MIN(verticalBuffer.positiveDirection * 0.5, 0.95));
|
||||
}
|
||||
|
||||
return {rangeBounds, updateBounds};
|
||||
}
|
||||
|
||||
- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds
|
||||
{
|
||||
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
|
||||
@ -144,13 +85,31 @@ typedef struct ASRangeGeometry ASRangeGeometry;
|
||||
return indexPathSet;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Should Update Range
|
||||
|
||||
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths
|
||||
viewportSize:(CGSize)viewportSize
|
||||
rangeType:(ASLayoutRangeType)rangeType
|
||||
- (ASRangeGeometry)rangeGeometryWithScrollDirection:(ASScrollDirection)scrollDirection
|
||||
tuningParameters:(ASRangeTuningParameters)tuningParameters
|
||||
{
|
||||
CGRect rangeBounds = _collectionView.bounds;
|
||||
CGRect updateBounds = _collectionView.bounds;
|
||||
|
||||
// Scrollable directions can change for non-flow layouts
|
||||
if ([_collectionViewLayout asdk_isFlowLayout] == NO) {
|
||||
_scrollableDirections = [_collectionView scrollableDirections];
|
||||
}
|
||||
|
||||
rangeBounds = CGRectExpandToRangeWithScrollableDirections(rangeBounds, tuningParameters, _scrollableDirections, scrollDirection);
|
||||
|
||||
ASRangeTuningParameters updateTuningParameters = tuningParameters;
|
||||
updateTuningParameters.leadingBufferScreenfuls = MIN(updateTuningParameters.leadingBufferScreenfuls * 0.5, 0.95);
|
||||
updateTuningParameters.trailingBufferScreenfuls = MIN(updateTuningParameters.trailingBufferScreenfuls * 0.5, 0.95);
|
||||
|
||||
updateBounds = CGRectExpandToRangeWithScrollableDirections(updateBounds, updateTuningParameters, _scrollableDirections, scrollDirection);
|
||||
|
||||
return {rangeBounds, updateBounds};
|
||||
}
|
||||
|
||||
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
CGSize viewportSize = [self viewportSize];
|
||||
CGRect updateRangeBounds = _updateRangeBoundsIndexedByRangeType[rangeType];
|
||||
if (CGRectIsEmpty(updateRangeBounds)) {
|
||||
return YES;
|
||||
@ -169,3 +128,40 @@ typedef struct ASRangeGeometry ASRangeGeometry;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ASCollectionViewLayoutControllerBeta
|
||||
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType];
|
||||
CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters];
|
||||
return [self indexPathsForItemsWithinRangeBounds:rangeBounds];
|
||||
}
|
||||
|
||||
- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds
|
||||
{
|
||||
NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds];
|
||||
NSMutableSet *indexPathSet = [NSMutableSet setWithCapacity:layoutAttributes.count];
|
||||
for (UICollectionViewLayoutAttributes *la in layoutAttributes) {
|
||||
//ASDisplayNodeAssert(![indexPathSet containsObject:la.indexPath], @"Shouldn't already contain indexPath");
|
||||
ASDisplayNodeAssert(la.representedElementCategory != UICollectionElementCategoryDecorationView, @"UICollectionView decoration views are not supported by ASCollectionView");
|
||||
[indexPathSet addObject:la.indexPath];
|
||||
}
|
||||
return indexPathSet;
|
||||
}
|
||||
|
||||
- (CGRect)rangeBoundsWithScrollDirection:(ASScrollDirection)scrollDirection
|
||||
rangeTuningParameters:(ASRangeTuningParameters)tuningParameters
|
||||
{
|
||||
CGRect rect = _collectionView.bounds;
|
||||
|
||||
// Scrollable directions can change for non-flow layouts
|
||||
if ([_collectionViewLayout asdk_isFlowLayout] == NO) {
|
||||
_scrollableDirections = [_collectionView scrollableDirections];
|
||||
}
|
||||
|
||||
return CGRectExpandToRangeWithScrollableDirections(rect, tuningParameters, _scrollableDirections, scrollDirection);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class ASDelegateProxy;
|
||||
@protocol ASDelegateProxyInterceptor
|
||||
@protocol ASDelegateProxyInterceptor <NSObject>
|
||||
@required
|
||||
// Called if the target object is discovered to be nil if it had been non-nil at init time.
|
||||
// This happens if the object is deallocated, because the proxy must maintain a weak reference to avoid cycles.
|
||||
@ -25,7 +25,7 @@
|
||||
|
||||
@interface ASDelegateProxy : NSProxy
|
||||
|
||||
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(id <ASDelegateProxyInterceptor>)interceptor;
|
||||
- (instancetype)initWithTarget:(id <NSObject>)target interceptor:(id <ASDelegateProxyInterceptor>)interceptor;
|
||||
|
||||
// This method must be overridden by a subclass.
|
||||
- (BOOL)interceptsSelector:(SEL)selector;
|
||||
|
||||
@ -54,7 +54,13 @@
|
||||
selector == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
|
||||
|
||||
// used for batch fetching API
|
||||
selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)
|
||||
selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) ||
|
||||
|
||||
// intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage)
|
||||
selector == @selector(collectionView:canMoveItemAtIndexPath:) ||
|
||||
selector == @selector(collectionView:moveItemAtIndexPath:toIndexPath:) ||
|
||||
selector == @selector(collectionView:willDisplaySupplementaryView:forElementKind:atIndexPath:) ||
|
||||
selector == @selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:)
|
||||
);
|
||||
}
|
||||
|
||||
@ -97,7 +103,7 @@
|
||||
- (BOOL)respondsToSelector:(SEL)aSelector
|
||||
{
|
||||
if ([self interceptsSelector:aSelector]) {
|
||||
return (_interceptor != nil);
|
||||
return [_interceptor respondsToSelector:aSelector];
|
||||
} else {
|
||||
// Also return NO if _target has become nil due to zeroing weak reference (or placeholder initialization).
|
||||
return [_target respondsToSelector:aSelector];
|
||||
|
||||
@ -39,7 +39,8 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3;
|
||||
|
||||
#pragma mark - Visible Indices
|
||||
|
||||
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType
|
||||
// FIXME: This method can be removed once ASRangeControllerBeta becomes the main version.
|
||||
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
if (!indexPaths.count || rangeType >= _rangesByType.size()) {
|
||||
return NO;
|
||||
@ -73,10 +74,11 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3;
|
||||
* IndexPath array for the element in the working range.
|
||||
*/
|
||||
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
CGFloat viewportScreenMetric;
|
||||
ASScrollDirection leadingDirection;
|
||||
CGSize viewportSize = [self viewportSize];
|
||||
|
||||
if (_layoutDirection == ASFlowLayoutDirectionHorizontal) {
|
||||
ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || scrollDirection == ASScrollDirectionLeft || scrollDirection == ASScrollDirectionRight, @"Invalid scroll direction");
|
||||
|
||||
@ -28,11 +28,14 @@ typedef struct {
|
||||
*
|
||||
* Defaults to a trailing buffer of one screenful and a leading buffer of two screenfuls.
|
||||
*/
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType;
|
||||
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType;
|
||||
|
||||
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray<NSIndexPath *> *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType;
|
||||
// FIXME: This method can be removed once ASRangeControllerBeta becomes the main version.
|
||||
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray<NSIndexPath *> *)indexPaths rangeType:(ASLayoutRangeType)rangeType;
|
||||
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType;
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType;
|
||||
|
||||
@optional
|
||||
|
||||
@ -46,6 +49,9 @@ typedef struct {
|
||||
|
||||
- (void)setVisibleNodeIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
- (void)setViewportSize:(CGSize)viewportSize;
|
||||
- (CGSize)viewportSize;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -9,8 +9,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, ASLayoutRangeType) {
|
||||
ASLayoutRangeTypeVisible,
|
||||
ASLayoutRangeTypeRender,
|
||||
ASLayoutRangeTypePreload,
|
||||
ASLayoutRangeTypeVisible = 0,
|
||||
ASLayoutRangeTypeDisplay,
|
||||
ASLayoutRangeTypeFetchData,
|
||||
ASLayoutRangeTypeCount
|
||||
};
|
||||
|
||||
#define ASLayoutRangeTypeRender ASLayoutRangeTypeDisplay
|
||||
#define ASLayoutRangeTypePreload ASLayoutRangeTypeFetchData
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
#import <AsyncDisplayKit/ASDataController.h>
|
||||
#import <AsyncDisplayKit/ASLayoutController.h>
|
||||
|
||||
#define RangeControllerLoggingEnabled 0
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASRangeControllerDataSource;
|
||||
@ -26,6 +28,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* This includes cancelling those asynchronous operations as cells fall outside of the working ranges.
|
||||
*/
|
||||
@interface ASRangeController : ASDealloc2MainObject <ASDataControllerDelegate>
|
||||
{
|
||||
id<ASLayoutController> _layoutController;
|
||||
__weak id<ASRangeControllerDataSource> _dataSource;
|
||||
__weak id<ASRangeControllerDelegate> _delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the range controller that the visible range has been updated.
|
||||
@ -46,6 +53,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node;
|
||||
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType;
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType;
|
||||
|
||||
/**
|
||||
* An object that describes the layout behavior of the ranged component (table view, collection view, etc.)
|
||||
*
|
||||
@ -66,6 +76,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@end
|
||||
|
||||
@interface ASRangeControllerStable : ASRangeController
|
||||
@end
|
||||
|
||||
@interface ASRangeControllerBeta : ASRangeController
|
||||
@end
|
||||
|
||||
/**
|
||||
* Data source for ASRangeController.
|
||||
*
|
||||
@ -89,14 +105,20 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController;
|
||||
|
||||
/**
|
||||
* Fetch nodes at specific index paths.
|
||||
*
|
||||
* @param rangeController Sender.
|
||||
*
|
||||
* @param indexPaths Index paths.
|
||||
* @returns the ASInterfaceState of the node that this controller is powering. This allows nested range controllers
|
||||
* to collaborate with one another, as an outer controller may set bits in .interfaceState such as Visible.
|
||||
* If this controller is an orthogonally scrolling element, it waits until it is visible to preload outside the viewport.
|
||||
*/
|
||||
- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController;
|
||||
|
||||
- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths;
|
||||
|
||||
- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
|
||||
@ -15,8 +15,33 @@
|
||||
#import "ASRangeHandlerRender.h"
|
||||
#import "ASRangeHandlerPreload.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASLayoutController.h"
|
||||
#import "ASLayoutRangeType.h"
|
||||
|
||||
@interface ASRangeController () {
|
||||
@implementation ASRangeController
|
||||
|
||||
- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection
|
||||
{
|
||||
}
|
||||
|
||||
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
[_layoutController setTuningParameters:tuningParameters forRangeType:rangeType];
|
||||
}
|
||||
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
return [_layoutController tuningParametersForRangeType:rangeType];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASRangeControllerStable ()
|
||||
{
|
||||
BOOL _rangeIsValid;
|
||||
|
||||
// keys should be ASLayoutRangeTypes and values NSSets containing NSIndexPaths
|
||||
@ -29,20 +54,22 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASRangeController
|
||||
@implementation ASRangeControllerStable
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_rangeIsValid = YES;
|
||||
_rangeTypeIndexPaths = [NSMutableDictionary dictionary];
|
||||
_rangeTypeHandlers = @{
|
||||
@(ASLayoutRangeTypeVisible): [[ASRangeHandlerVisible alloc] init],
|
||||
@(ASLayoutRangeTypeRender): [[ASRangeHandlerRender alloc] init],
|
||||
@(ASLayoutRangeTypePreload): [[ASRangeHandlerPreload alloc] init],
|
||||
};
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_rangeIsValid = YES;
|
||||
_rangeTypeIndexPaths = [NSMutableDictionary dictionary];
|
||||
_rangeTypeHandlers = @{
|
||||
@(ASLayoutRangeTypeVisible) : [[ASRangeHandlerVisible alloc] init],
|
||||
@(ASLayoutRangeTypeDisplay) : [[ASRangeHandlerRender alloc] init],
|
||||
@(ASLayoutRangeTypeFetchData): [[ASRangeHandlerPreload alloc] init],
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -111,6 +138,7 @@
|
||||
|
||||
NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths];
|
||||
CGSize viewportSize = [_dataSource viewportSizeForRangeController:self];
|
||||
[_layoutController setViewportSize:viewportSize];
|
||||
|
||||
// the layout controller needs to know what the current visible indices are to calculate range offsets
|
||||
if ([_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]) {
|
||||
@ -124,13 +152,12 @@
|
||||
// this delegate decide what happens when a node is added or removed from a range
|
||||
id<ASRangeHandler> rangeHandler = _rangeTypeHandlers[rangeKey];
|
||||
|
||||
if (!_rangeIsValid || [_layoutController shouldUpdateForVisibleIndexPaths:visibleNodePaths viewportSize:viewportSize rangeType:rangeType]) {
|
||||
NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection
|
||||
viewportSize:viewportSize
|
||||
rangeType:rangeType];
|
||||
if (!_rangeIsValid || [_layoutController shouldUpdateForVisibleIndexPaths:visibleNodePaths rangeType:rangeType]) {
|
||||
NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:rangeType];
|
||||
|
||||
// Notify to remove indexpaths that are leftover that are not visible or included in the _layoutController calculated paths
|
||||
NSMutableSet *removedIndexPaths = _rangeIsValid ? [_rangeTypeIndexPaths[rangeKey] mutableCopy] : [NSMutableSet set];
|
||||
// This value may be nil for the first call of this method.
|
||||
NSMutableSet *removedIndexPaths = [_rangeTypeIndexPaths[rangeKey] mutableCopy];
|
||||
[removedIndexPaths minusSet:indexPaths];
|
||||
[removedIndexPaths minusSet:visibleNodePathsSet];
|
||||
|
||||
@ -171,66 +198,81 @@
|
||||
|
||||
- (BOOL)shouldSkipVisibleNodesForRangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
return rangeType == ASLayoutRangeTypeRender;
|
||||
return rangeType == ASLayoutRangeTypeDisplay;
|
||||
}
|
||||
|
||||
#pragma mark - ASDataControllerDelegete
|
||||
|
||||
- (void)dataControllerBeginUpdates:(ASDataController *)dataController {
|
||||
- (void)dataControllerBeginUpdates:(ASDataController *)dataController
|
||||
{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[_delegate didBeginUpdatesInRangeController:self];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion {
|
||||
- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
|
||||
{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions {
|
||||
- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path");
|
||||
|
||||
NSMutableArray *nodeSizes = [NSMutableArray arrayWithCapacity:nodes.count];
|
||||
[nodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) {
|
||||
[nodeSizes addObject:[NSValue valueWithCGSize:node.calculatedSize]];
|
||||
}];
|
||||
|
||||
ASPerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
[_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions {
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
|
||||
// When removing nodes we need to make sure that removed indexPaths are not left in _rangeTypeIndexPaths,
|
||||
// otherwise _updateVisibleNodeIndexPaths may try to retrieve nodes from dataSource that aren't there anymore
|
||||
for (NSInteger i = 0; i < ASLayoutRangeTypeCount; i++) {
|
||||
id rangeKey = @((ASLayoutRangeType)i);
|
||||
NSMutableSet *rangePaths = [_rangeTypeIndexPaths[rangeKey] mutableCopy];
|
||||
for (NSIndexPath *path in indexPaths) {
|
||||
[rangePaths removeObject:path];
|
||||
}
|
||||
_rangeTypeIndexPaths[rangeKey] = rangePaths;
|
||||
}
|
||||
|
||||
[_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions {
|
||||
- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections");
|
||||
|
||||
NSMutableArray *sectionNodeSizes = [NSMutableArray arrayWithCapacity:sections.count];
|
||||
|
||||
[sections enumerateObjectsUsingBlock:^(NSArray *nodes, NSUInteger idx, BOOL *stop) {
|
||||
NSMutableArray *nodeSizes = [NSMutableArray arrayWithCapacity:nodes.count];
|
||||
[nodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx2, BOOL *stop2) {
|
||||
[nodeSizes addObject:[NSValue valueWithCGSize:node.calculatedSize]];
|
||||
}];
|
||||
[sectionNodeSizes addObject:nodeSizes];
|
||||
}];
|
||||
|
||||
ASPerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions {
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
|
||||
// When removing nodes we need to make sure that removed indexPaths are not left in _rangeTypeIndexPaths,
|
||||
// otherwise _updateVisibleNodeIndexPaths may try to retrieve nodes from dataSource that aren't there anymore
|
||||
for (NSInteger i = 0; i < ASLayoutRangeTypeCount; i++) {
|
||||
id rangeKey = @((ASLayoutRangeType)i);
|
||||
NSMutableSet *rangePaths = [_rangeTypeIndexPaths[rangeKey] mutableCopy];
|
||||
for (NSIndexPath *path in _rangeTypeIndexPaths[rangeKey]) {
|
||||
if ([indexSet containsIndex:path.section]) {
|
||||
[rangePaths removeObject:path];
|
||||
}
|
||||
}
|
||||
_rangeTypeIndexPaths[rangeKey] = rangePaths;
|
||||
}
|
||||
|
||||
[_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
20
AsyncDisplayKit/Details/ASRangeControllerBeta.h
Normal file
20
AsyncDisplayKit/Details/ASRangeControllerBeta.h
Normal file
@ -0,0 +1,20 @@
|
||||
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASRangeController.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASDataController.h>
|
||||
#import <AsyncDisplayKit/ASLayoutController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// ASRangeControllerBeta defined in ASRangeController.h
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
270
AsyncDisplayKit/Details/ASRangeControllerBeta.mm
Normal file
270
AsyncDisplayKit/Details/ASRangeControllerBeta.mm
Normal file
@ -0,0 +1,270 @@
|
||||
/* 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 "ASRangeControllerBeta.h"
|
||||
|
||||
#import "ASAssert.h"
|
||||
#import "ASDisplayNodeExtras.h"
|
||||
#import "ASMultiDimensionalArrayUtils.h"
|
||||
#import "ASRangeHandlerVisible.h"
|
||||
#import "ASRangeHandlerRender.h"
|
||||
#import "ASRangeHandlerPreload.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
|
||||
@interface ASRangeControllerBeta ()
|
||||
{
|
||||
BOOL _rangeIsValid;
|
||||
BOOL _queuedRangeUpdate;
|
||||
ASScrollDirection _scrollDirection;
|
||||
NSSet *_allPreviousIndexPaths;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASRangeControllerBeta
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_rangeIsValid = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Core visible node range managment API
|
||||
|
||||
- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection
|
||||
{
|
||||
_scrollDirection = scrollDirection;
|
||||
|
||||
if (_queuedRangeUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// coalesce these events -- handling them multiple times per runloop is noisy and expensive
|
||||
_queuedRangeUpdate = YES;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self _updateVisibleNodeIndexPaths];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_updateVisibleNodeIndexPaths
|
||||
{
|
||||
if (!_queuedRangeUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Consider if we need to check this separately from the range calculation below.
|
||||
NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self];
|
||||
|
||||
if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)...
|
||||
_queuedRangeUpdate = NO;
|
||||
return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later
|
||||
}
|
||||
|
||||
CGSize viewportSize = [_dataSource viewportSizeForRangeController:self];
|
||||
[_layoutController setViewportSize:viewportSize];
|
||||
|
||||
// the layout controller needs to know what the current visible indices are to calculate range offsets
|
||||
if ([_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]) {
|
||||
[_layoutController setVisibleNodeIndexPaths:visibleNodePaths];
|
||||
}
|
||||
|
||||
NSArray *allNodes = [_dataSource completedNodes];
|
||||
NSArray *currentSectionNodes = nil;
|
||||
NSInteger currentSectionIndex = -1; // Will be unequal to any indexPath.section, so we set currentSectionNodes.
|
||||
|
||||
NSUInteger numberOfSections = [allNodes count];
|
||||
NSUInteger numberOfNodesInSection = 0;
|
||||
|
||||
NSSet *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths];
|
||||
// = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
|
||||
NSSet *displayIndexPaths = nil;
|
||||
NSSet *fetchDataIndexPaths = nil;
|
||||
NSMutableSet *allIndexPaths = nil;
|
||||
NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
|
||||
|
||||
ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self];
|
||||
|
||||
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
|
||||
// If we are already visible, get busy! Better get started on preloading before the user scrolls more...
|
||||
fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData];
|
||||
displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay];
|
||||
|
||||
// Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint.
|
||||
allIndexPaths = [fetchDataIndexPaths mutableCopy];
|
||||
[allIndexPaths unionSet:displayIndexPaths];
|
||||
[allIndexPaths unionSet:visibleIndexPaths];
|
||||
} else {
|
||||
allIndexPaths = [visibleIndexPaths mutableCopy];
|
||||
}
|
||||
|
||||
// Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any
|
||||
// range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic
|
||||
// scroll or major main thread stall could cause entirely disjoint sets, but we must visit all.
|
||||
NSSet *allCurrentIndexPaths = [allIndexPaths copy];
|
||||
[allIndexPaths unionSet:_allPreviousIndexPaths];
|
||||
_allPreviousIndexPaths = allCurrentIndexPaths;
|
||||
|
||||
for (NSIndexPath *indexPath in allIndexPaths) {
|
||||
// Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
|
||||
// For consistency, make sure each node knows that it should measure itself if something changes.
|
||||
ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout;
|
||||
|
||||
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
|
||||
if ([fetchDataIndexPaths containsObject:indexPath]) {
|
||||
interfaceState |= ASInterfaceStateFetchData;
|
||||
}
|
||||
if ([displayIndexPaths containsObject:indexPath]) {
|
||||
interfaceState |= ASInterfaceStateDisplay;
|
||||
}
|
||||
if ([visibleIndexPaths containsObject:indexPath]) {
|
||||
interfaceState |= ASInterfaceStateVisible;
|
||||
}
|
||||
} else {
|
||||
// If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the
|
||||
// instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet.
|
||||
// We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:.
|
||||
|
||||
// Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport",
|
||||
// our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above.
|
||||
if ([allCurrentIndexPaths containsObject:indexPath]) {
|
||||
// We might be looking at an indexPath that was previously in-range, but now we need to clear it.
|
||||
// In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths.
|
||||
interfaceState |= ASInterfaceStateDisplay;
|
||||
interfaceState |= ASInterfaceStateFetchData;
|
||||
}
|
||||
}
|
||||
|
||||
NSInteger section = indexPath.section;
|
||||
NSInteger row = indexPath.row;
|
||||
|
||||
if (section >= 0 && row >= 0 && section < numberOfSections) {
|
||||
if (section != currentSectionIndex) {
|
||||
// Often we'll be dealing with indexPaths in the same section, but the set isn't sorted and we may even bounce
|
||||
// between the same ones. Still, this saves dozens of method calls to access the inner array and count.
|
||||
currentSectionNodes = [allNodes objectAtIndex:section];
|
||||
numberOfNodesInSection = [currentSectionNodes count];
|
||||
currentSectionIndex = section;
|
||||
}
|
||||
|
||||
if (row < numberOfNodesInSection) {
|
||||
ASDisplayNode *node = [currentSectionNodes objectAtIndex:row];
|
||||
|
||||
ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset.");
|
||||
// Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState.
|
||||
if (node.interfaceState != interfaceState) {
|
||||
[modifiedIndexPaths addObject:indexPath];
|
||||
[node recursivelySetInterfaceState:interfaceState];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_rangeIsValid = YES;
|
||||
_queuedRangeUpdate = NO;
|
||||
|
||||
#if RangeControllerLoggingEnabled
|
||||
NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths];
|
||||
BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet];
|
||||
NSLog(@"visible sets are equal: %d", setsAreEqual);
|
||||
if (!setsAreEqual) {
|
||||
NSLog(@"standard: %@", visibleIndexPaths);
|
||||
NSLog(@"custom: %@", visibleNodePathsSet);
|
||||
}
|
||||
|
||||
[modifiedIndexPaths sortUsingSelector:@selector(compare:)];
|
||||
|
||||
for (NSIndexPath *indexPath in modifiedIndexPaths) {
|
||||
ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath];
|
||||
ASInterfaceState interfaceState = node.interfaceState;
|
||||
BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState);
|
||||
BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState);
|
||||
BOOL inFetchData = ASInterfaceStateIncludesFetchData(interfaceState);
|
||||
NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, inVisible, inDisplay, inFetchData);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - Cell node view handling
|
||||
|
||||
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(node, @"Cannot move a nil node to a view");
|
||||
ASDisplayNodeAssert(contentView, @"Cannot move a node to a non-existent view");
|
||||
|
||||
if (node.view.superview == contentView) {
|
||||
// this content view is already correctly configured
|
||||
return;
|
||||
}
|
||||
|
||||
// clean the content view
|
||||
for (UIView *view in contentView.subviews) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
|
||||
[contentView addSubview:node.view];
|
||||
}
|
||||
|
||||
#pragma mark - ASDataControllerDelegete
|
||||
|
||||
- (void)dataControllerBeginUpdates:(ASDataController *)dataController
|
||||
{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[_delegate didBeginUpdatesInRangeController:self];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
|
||||
{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path");
|
||||
ASPerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
[_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
[_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections");
|
||||
ASPerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
[_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@ -14,13 +14,13 @@
|
||||
|
||||
- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges");
|
||||
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeFetchData, @"Preload delegate should not handle other ranges");
|
||||
[node enterInterfaceState:ASInterfaceStateFetchData];
|
||||
}
|
||||
|
||||
- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges");
|
||||
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeFetchData, @"Preload delegate should not handle other ranges");
|
||||
[node exitInterfaceState:ASInterfaceStateFetchData];
|
||||
}
|
||||
|
||||
|
||||
@ -11,15 +11,15 @@
|
||||
#import "ASDisplayNode.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
#import "ASDisplayNode+Beta.h"
|
||||
|
||||
@interface ASRangeHandlerRender ()
|
||||
@property (nonatomic,readonly) UIWindow *workingWindow;
|
||||
@end
|
||||
|
||||
@implementation ASRangeHandlerRender
|
||||
|
||||
#if USE_WORKING_WINDOW
|
||||
@synthesize workingWindow = _workingWindow;
|
||||
|
||||
- (UIWindow *)workingWindow
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
@ -28,7 +28,7 @@
|
||||
// TODO: Replace this with directly triggering display https://github.com/facebook/AsyncDisplayKit/issues/315
|
||||
// Update: Latest attempt is at https://github.com/facebook/AsyncDisplayKit/pull/828
|
||||
|
||||
if (!_workingWindow) {
|
||||
if (!_workingWindow && ![ASDisplayNode shouldUseNewRenderingRange]) {
|
||||
_workingWindow = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
_workingWindow.windowLevel = UIWindowLevelNormal - 1000;
|
||||
_workingWindow.userInteractionEnabled = NO;
|
||||
@ -41,17 +41,18 @@
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
for(CALayer *layer in [self.workingWindow.layer.sublayers copy]) {
|
||||
ASDisplayNode *node = layer.asyncdisplaykit_node;
|
||||
[self node:node exitedRangeOfType:ASLayoutRangeTypeRender];
|
||||
if (![ASDisplayNode shouldUseNewRenderingRange]) {
|
||||
for (CALayer *layer in [self.workingWindow.layer.sublayers copy]) {
|
||||
ASDisplayNode *node = layer.asyncdisplaykit_node;
|
||||
[self node:node exitedRangeOfType:ASLayoutRangeTypeDisplay];
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeRender, @"Render delegate should not handle other ranges");
|
||||
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeDisplay, @"Render delegate should not handle other ranges");
|
||||
|
||||
// If a node had previously been onscreen but now is only in the working range,
|
||||
// ensure its view is not orphaned in a UITableViewCell in the reuse pool.
|
||||
@ -63,22 +64,23 @@
|
||||
[node enterInterfaceState:ASInterfaceStateDisplay];
|
||||
|
||||
|
||||
#if USE_WORKING_WINDOW
|
||||
// Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile.
|
||||
// Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations.
|
||||
// Any view-backed nodes will still create their views in order to assemble the layer heirarchy, and they will
|
||||
// also assemble a view subtree for the node, but we avoid the much more significant expense triggered by a view
|
||||
// being added or removed from an onscreen window (responder chain setup, will/DidMoveToWindow: recursive calls, etc)
|
||||
[[[self workingWindow] layer] addSublayer:node.layer];
|
||||
#else
|
||||
[node recursivelyEnsureDisplay]; // Need to do this without waiting
|
||||
#endif
|
||||
ASDisplayNodeAssert(![ASDisplayNode shouldUseNewRenderingRange], @"It should no longer be possible to reach this point with the new display range enabled");
|
||||
if ([ASDisplayNode shouldUseNewRenderingRange]) {
|
||||
[node recursivelyEnsureDisplaySynchronously:NO];
|
||||
} else {
|
||||
// Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile.
|
||||
// Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations.
|
||||
// Any view-backed nodes will still create their views in order to assemble the layer heirarchy, and they will
|
||||
// also assemble a view subtree for the node, but we avoid the much more significant expense triggered by a view
|
||||
// being added or removed from an onscreen window (responder chain setup, will/DidMoveToWindow: recursive calls, etc)
|
||||
[[[self workingWindow] layer] addSublayer:node.layer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeRender, @"Render delegate should not handle other ranges");
|
||||
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeDisplay, @"Render delegate should not handle other ranges");
|
||||
|
||||
// This code is tricky. There are several possible states a node can be in when it reaches this point.
|
||||
// 1. Layer-backed vs view-backed nodes. AS of this writing, only ASCellNodes arrive here, which are always view-backed —
|
||||
@ -100,25 +102,27 @@
|
||||
// The node calls clearCurrentContents and suspends display
|
||||
[node exitInterfaceState:ASInterfaceStateDisplay];
|
||||
|
||||
#if USE_WORKING_WINDOW
|
||||
if (node.layer.superlayer != [[self workingWindow] layer]) {
|
||||
// In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range.
|
||||
if (![node isLayerBacked]) {
|
||||
// If the node is view-backed, we need to make sure to remove the view (which is now present in the containing cell contentsView).
|
||||
// Layer-backed nodes will be fully handled by the unconditional removal below.
|
||||
[node.view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
ASDisplayNodeAssert(![ASDisplayNode shouldUseNewRenderingRange], @"It should no longer be possible to reach this point with the new display range enabled");
|
||||
|
||||
// At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell.
|
||||
[node.layer removeFromSuperlayer];
|
||||
#else
|
||||
if (![node isLayerBacked]) {
|
||||
[node.view removeFromSuperview];
|
||||
if ([ASDisplayNode shouldUseNewRenderingRange]) {
|
||||
if (![node isLayerBacked]) {
|
||||
[node.view removeFromSuperview];
|
||||
} else {
|
||||
[node.layer removeFromSuperlayer];
|
||||
}
|
||||
} else {
|
||||
if (node.layer.superlayer != [[self workingWindow] layer]) {
|
||||
// In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range.
|
||||
if (![node isLayerBacked]) {
|
||||
// If the node is view-backed, we need to make sure to remove the view (which is now present in the containing cell contentsView).
|
||||
// Layer-backed nodes will be fully handled by the unconditional removal below.
|
||||
[node.view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell.
|
||||
[node.layer removeFromSuperlayer];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -10,13 +10,16 @@
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
#import "ASBaseDefines.h"
|
||||
#import "ASLayoutController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_BEGIN
|
||||
|
||||
CGRect asdk_CGRectExpandHorizontally(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier);
|
||||
CGRect asdk_CGRectExpandVertically(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier);
|
||||
CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect,
|
||||
ASRangeTuningParameters tuningParameters,
|
||||
ASScrollDirection scrollableDirections,
|
||||
ASScrollDirection scrollDirection);
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_END
|
||||
|
||||
|
||||
@ -7,25 +7,74 @@
|
||||
*/
|
||||
|
||||
#import "CGRect+ASConvenience.h"
|
||||
#import "ASScrollDirection.h"
|
||||
#import "ASLayoutController.h"
|
||||
|
||||
CGRect asdk_CGRectExpandHorizontally(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier) {
|
||||
CGFloat negativeDirectionWidth = negativeMultiplier * rect.size.width;
|
||||
CGFloat positiveDirectionWidth = positiveMultiplier * rect.size.width;
|
||||
CGFloat width = negativeDirectionWidth + rect.size.width + positiveDirectionWidth;
|
||||
CGFloat originX = rect.origin.x - negativeDirectionWidth;
|
||||
return CGRectMake(originX,
|
||||
rect.origin.y,
|
||||
width,
|
||||
rect.size.height);
|
||||
struct ASDirectionalScreenfulBuffer {
|
||||
CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space.
|
||||
CGFloat negativeDirection;
|
||||
};
|
||||
typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer;
|
||||
|
||||
ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection,
|
||||
ASRangeTuningParameters rangeTuningParameters)
|
||||
{
|
||||
ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0};
|
||||
BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection);
|
||||
|
||||
horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls
|
||||
: rangeTuningParameters.trailingBufferScreenfuls;
|
||||
horizontalBuffer.negativeDirection = movingRight ? rangeTuningParameters.trailingBufferScreenfuls
|
||||
: rangeTuningParameters.leadingBufferScreenfuls;
|
||||
return horizontalBuffer;
|
||||
}
|
||||
|
||||
CGRect asdk_CGRectExpandVertically(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier) {
|
||||
CGFloat negativeDirectionHeight = negativeMultiplier * rect.size.height;
|
||||
CGFloat positiveDirectionHeight = positiveMultiplier * rect.size.height;
|
||||
CGFloat height = negativeDirectionHeight + rect.size.height + positiveDirectionHeight;
|
||||
CGFloat originY = rect.origin.y - negativeDirectionHeight;
|
||||
return CGRectMake(rect.origin.x,
|
||||
originY,
|
||||
rect.size.width,
|
||||
height);
|
||||
ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection,
|
||||
ASRangeTuningParameters rangeTuningParameters)
|
||||
{
|
||||
ASDirectionalScreenfulBuffer verticalBuffer = {0, 0};
|
||||
BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection);
|
||||
|
||||
verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls
|
||||
: rangeTuningParameters.trailingBufferScreenfuls;
|
||||
verticalBuffer.negativeDirection = movingDown ? rangeTuningParameters.trailingBufferScreenfuls
|
||||
: rangeTuningParameters.leadingBufferScreenfuls;
|
||||
return verticalBuffer;
|
||||
}
|
||||
|
||||
CGRect CGRectExpandHorizontally(CGRect rect, ASDirectionalScreenfulBuffer buffer)
|
||||
{
|
||||
CGFloat negativeDirectionWidth = buffer.negativeDirection * rect.size.width;
|
||||
CGFloat positiveDirectionWidth = buffer.positiveDirection * rect.size.width;
|
||||
rect.size.width = negativeDirectionWidth + rect.size.width + positiveDirectionWidth;
|
||||
rect.origin.x -= negativeDirectionWidth;
|
||||
return rect;
|
||||
}
|
||||
|
||||
CGRect CGRectExpandVertically(CGRect rect, ASDirectionalScreenfulBuffer buffer)
|
||||
{
|
||||
CGFloat negativeDirectionHeight = buffer.negativeDirection * rect.size.height;
|
||||
CGFloat positiveDirectionHeight = buffer.positiveDirection * rect.size.height;
|
||||
rect.size.height = negativeDirectionHeight + rect.size.height + positiveDirectionHeight;
|
||||
rect.origin.y -= negativeDirectionHeight;
|
||||
return rect;
|
||||
}
|
||||
|
||||
CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, ASRangeTuningParameters tuningParameters,
|
||||
ASScrollDirection scrollableDirections, ASScrollDirection scrollDirection)
|
||||
{
|
||||
// Can scroll horizontally - expand the range appropriately
|
||||
if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) {
|
||||
ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters);
|
||||
rect = CGRectExpandHorizontally(rect, horizontalBuffer);
|
||||
}
|
||||
|
||||
// Can scroll vertically - expand the range appropriately
|
||||
if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) {
|
||||
ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters);
|
||||
rect = CGRectExpandVertically(rect, verticalBuffer);
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
|
||||
@ -10,8 +10,9 @@
|
||||
|
||||
@implementation UICollectionViewLayout (ASConvenience)
|
||||
|
||||
- (BOOL)asdk_isFlowLayout {
|
||||
return [self isKindOfClass:UICollectionViewFlowLayout.class];
|
||||
- (BOOL)asdk_isFlowLayout
|
||||
{
|
||||
return [self isKindOfClass:[UICollectionViewFlowLayout class]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -110,6 +110,8 @@
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
ASDN::MutexLocker l(_displaySuspendedLock);
|
||||
// FIXME: Reconsider whether we should cancel a display in progress.
|
||||
// We should definitely cancel a display that is scheduled, but unstarted display.
|
||||
[self cancelAsyncDisplay];
|
||||
|
||||
// Short circuit if display is suspended. When resumed, we will setNeedsDisplay at that time.
|
||||
|
||||
@ -151,8 +151,11 @@
|
||||
needsSupernodeRemoval = YES;
|
||||
}
|
||||
} else {
|
||||
// If supernode is loaded but our superview is nil, the user manually removed us, so disconnect supernode.
|
||||
needsSupernodeRemoval = supernodeLoaded;
|
||||
// If supernode is loaded but our superview is nil, the user likely manually removed us, so disconnect supernode.
|
||||
// The unlikely alternative: we are in __unloadNode, with shouldRasterizeSubnodes just having been turned on.
|
||||
// In the latter case, we don't want to disassemble the node hierarchy because all views are intentionally being destroyed.
|
||||
BOOL nodeIsRasterized = ((_node.hierarchyState & ASHierarchyStateRasterized) == ASHierarchyStateRasterized);
|
||||
needsSupernodeRemoval = (supernodeLoaded && !nodeIsRasterized);
|
||||
}
|
||||
|
||||
if (needsSupernodeRemoval) {
|
||||
|
||||
@ -55,7 +55,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* @return a new instance of ASLayoutOptions
|
||||
*/
|
||||
- (instancetype)initWithLayoutable:(nullable id<ASLayoutable>)layoutable NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithLayoutable:(id<ASLayoutable>)layoutable;
|
||||
|
||||
/**
|
||||
* Copies the values of layoutOptions into self. This is useful when placing a layoutable inside of another. Consider
|
||||
|
||||
@ -56,10 +56,10 @@ static Class gDefaultLayoutOptionsClass = nil;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithLayoutable:nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithLayoutable:(id<ASLayoutable>)layoutable;
|
||||
- (instancetype)initWithLayoutable:(id<ASLayoutable>)layoutable
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
@ -39,7 +39,6 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
_layoutChildren = [NSMutableDictionary dictionary];
|
||||
_isMutable = YES;
|
||||
return self;
|
||||
}
|
||||
@ -56,11 +55,6 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setChild:(id<ASLayoutable>)child;
|
||||
{
|
||||
[self setChild:child forIdentifier:kDefaultChildKey];
|
||||
}
|
||||
|
||||
- (id<ASLayoutable>)layoutableToAddFromLayoutable:(id<ASLayoutable>)child
|
||||
{
|
||||
if (self.isFinalLayoutable == NO) {
|
||||
@ -88,6 +82,19 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
|
||||
return child;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)layoutChildren
|
||||
{
|
||||
if (!_layoutChildren) {
|
||||
_layoutChildren = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _layoutChildren;
|
||||
}
|
||||
|
||||
- (void)setChild:(id<ASLayoutable>)child;
|
||||
{
|
||||
[self setChild:child forIdentifier:kDefaultChildKey];
|
||||
}
|
||||
|
||||
- (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier
|
||||
{
|
||||
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
|
||||
|
||||
@ -20,10 +20,6 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Project-wide control for whether the offscreen UIWindow is used for display, or if
|
||||
// ASDK's internal system for coalescing and triggering display events is used.
|
||||
#define USE_WORKING_WINDOW 1
|
||||
|
||||
/**
|
||||
Hierarchy state is propogated from nodes to all of their children when certain behaviors are required from the subtree.
|
||||
Examples include rasterization and external driving of the .interfaceState property.
|
||||
@ -64,11 +60,19 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState)
|
||||
// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements.
|
||||
- (void)enterInterfaceState:(ASInterfaceState)interfaceState;
|
||||
- (void)exitInterfaceState:(ASInterfaceState)interfaceState;
|
||||
- (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState;
|
||||
|
||||
// These methods are recursive, and either union or remove the provided hierarchyState to all sub-elements.
|
||||
- (void)enterHierarchyState:(ASHierarchyState)hierarchyState;
|
||||
- (void)exitHierarchyState:(ASHierarchyState)hierarchyState;
|
||||
|
||||
// Changed before calling willEnterHierarchy / didExitHierarchy.
|
||||
@property (nonatomic, readwrite, assign, getter = isInHierarchy) BOOL inHierarchy;
|
||||
// Call willEnterHierarchy if necessary and set inHierarchy = YES if visibility notifications are enabled on all of its parents
|
||||
- (void)__enterHierarchy;
|
||||
// Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents
|
||||
- (void)__exitHierarchy;
|
||||
|
||||
/**
|
||||
* @abstract Returns the Hierarchy State of the node.
|
||||
*
|
||||
@ -94,7 +98,7 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState)
|
||||
* In order to guarantee against deadlocks, this method should only be called on the main thread.
|
||||
* It may block on the private queue, [_ASDisplayLayer displayQueue]
|
||||
*/
|
||||
- (void)recursivelyEnsureDisplay;
|
||||
- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously;
|
||||
|
||||
/**
|
||||
* @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO.
|
||||
|
||||
@ -11,8 +11,10 @@
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASAssert.h"
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import "ASDisplayNodeExtras.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
#import "ASDisplayNode+Beta.h"
|
||||
#import "ASEqualityHelpers.h"
|
||||
|
||||
/**
|
||||
@ -248,11 +250,14 @@
|
||||
|
||||
_messageToViewOrLayer(setNeedsDisplay);
|
||||
|
||||
#if !USE_WORKING_WINDOW
|
||||
if (_layer && !self.isSynchronous) {
|
||||
[ASDisplayNode scheduleNodeForDisplay:self];
|
||||
if ([ASDisplayNode shouldUseNewRenderingRange]) {
|
||||
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState);
|
||||
// FIXME: This should not need to recursively display, so create a non-recursive variant.
|
||||
// The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive.
|
||||
if (_layer && !_flags.synchronous && nowDisplay && [self __implementsDisplay]) {
|
||||
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -109,7 +109,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node;
|
||||
+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node;
|
||||
|
||||
// The _ASDisplayLayer backing the node, if any.
|
||||
@property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer;
|
||||
@ -132,20 +132,12 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
- (void)__layout;
|
||||
- (void)__setSupernode:(ASDisplayNode *)supernode;
|
||||
|
||||
// Changed before calling willEnterHierarchy / didExitHierarchy.
|
||||
@property (nonatomic, readwrite, assign, getter = isInHierarchy) BOOL inHierarchy;
|
||||
|
||||
// Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this.
|
||||
- (BOOL)__visibilityNotificationsDisabled;
|
||||
- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled;
|
||||
- (void)__incrementVisibilityNotificationsDisabled;
|
||||
- (void)__decrementVisibilityNotificationsDisabled;
|
||||
|
||||
// Call willEnterHierarchy if necessary and set inHierarchy = YES if visibility notifications are enabled on all of its parents
|
||||
- (void)__enterHierarchy;
|
||||
// Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents
|
||||
- (void)__exitHierarchy;
|
||||
|
||||
// Helper method to summarize whether or not the node run through the display process
|
||||
- (BOOL)__implementsDisplay;
|
||||
|
||||
@ -160,6 +152,20 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
|
||||
@property (nonatomic, assign) CGFloat contentsScaleForDisplay;
|
||||
|
||||
/**
|
||||
* // TODO: NOT YET IMPLEMENTED
|
||||
*
|
||||
* @abstract Prevents interface state changes from affecting the node, until disabled.
|
||||
*
|
||||
* @discussion Useful to avoid flashing after removing a node from the hierarchy and re-adding it.
|
||||
* Removing a node from the hierarchy will cause it to exit the Display state, clearing its contents.
|
||||
* For some animations, it's desirable to be able to remove a node without causing it to re-display.
|
||||
* Once re-enabled, the interface state will be updated to the same value it would have been.
|
||||
*
|
||||
* @see ASInterfaceState
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL interfaceStateSuspended;
|
||||
|
||||
/**
|
||||
* This method has proven helpful in a few rare scenarios, similar to a category extension on UIView,
|
||||
* but it's considered private API for now and its use should not be encouraged.
|
||||
|
||||
@ -798,8 +798,13 @@ static UIColor *defaultTintColor = nil;
|
||||
view.accessibilityIdentifier = accessibilityIdentifier;
|
||||
}
|
||||
|
||||
// FIXME: Make this more efficient by tracking which properties are set rather than reading everything.
|
||||
+ (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer
|
||||
{
|
||||
if (!layer) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_ASPendingState *pendingState = [[_ASPendingState alloc] init];
|
||||
|
||||
pendingState.anchorPoint = layer.anchorPoint;
|
||||
@ -877,8 +882,13 @@ static UIColor *defaultTintColor = nil;
|
||||
return pendingState;
|
||||
}
|
||||
|
||||
// FIXME: Make this more efficient by tracking which properties are set rather than reading everything.
|
||||
+ (_ASPendingState *)pendingViewStateFromView:(UIView *)view
|
||||
{
|
||||
if (!view) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_ASPendingState *pendingState = [[_ASPendingState alloc] init];
|
||||
|
||||
CALayer *layer = view.layer;
|
||||
|
||||
@ -7,10 +7,14 @@
|
||||
*/
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[ASDisplayNode setShouldUseNewRenderingRange:YES];
|
||||
return YES;
|
||||
}
|
||||
|
||||
@ -1702,7 +1702,11 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy);
|
||||
|
||||
[node.view removeFromSuperview];
|
||||
XCTAssert(!node.hasFetchedData);
|
||||
// We don't want to call -clearFetchedData on nodes that aren't being managed by a range controller.
|
||||
// Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop.
|
||||
// Still, the interfaceState should be None to reflect the current state of the node.
|
||||
// We just don't proactively clear contents or fetched data for this state transition.
|
||||
XCTAssert(node.hasFetchedData);
|
||||
XCTAssert(node.interfaceState == ASInterfaceStateNone);
|
||||
}
|
||||
|
||||
|
||||
@ -37,9 +37,9 @@
|
||||
|
||||
@implementation ASTestTableView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
- (instancetype)__initWithFrame:(CGRect)frame style:(UITableViewStyle)style
|
||||
{
|
||||
return [super initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] asyncDataFetching:asyncDataFetchingEnabled];
|
||||
return [super _initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] ownedByNode:NO];
|
||||
}
|
||||
|
||||
- (ASTestDataController *)testDataController
|
||||
@ -124,6 +124,7 @@
|
||||
@end
|
||||
|
||||
@interface ASTableViewTests : XCTestCase
|
||||
@property (atomic, retain) ASTableView *testTableView;
|
||||
@end
|
||||
|
||||
@implementation ASTableViewTests
|
||||
@ -131,7 +132,7 @@
|
||||
// TODO: Convert this to ARC.
|
||||
- (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate
|
||||
{
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
|
||||
__block BOOL tableViewDidDealloc = NO;
|
||||
tableView.willDeallocBlock = ^(ASTableView *v){
|
||||
@ -185,9 +186,8 @@
|
||||
- (void)testReloadData
|
||||
{
|
||||
// Keep the viewport moderately sized so that new cells are loaded on scrolling
|
||||
ASTableView *tableView = [[ASTableView alloc] initWithFrame:CGRectMake(0, 0, 100, 500)
|
||||
style:UITableViewStylePlain
|
||||
asyncDataFetching:YES];
|
||||
ASTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 500)
|
||||
style:UITableViewStylePlain];
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
@ -250,9 +250,8 @@
|
||||
// Any subsequence size change must trigger a relayout.
|
||||
CGSize tableViewFinalSize = CGSizeMake(100, 500);
|
||||
// Width and height are swapped so that a later size change will simulate a rotation
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width)
|
||||
style:UITableViewStylePlain
|
||||
asyncDataFetching:YES];
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width)
|
||||
style:UITableViewStylePlain];
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
@ -270,9 +269,8 @@
|
||||
// Initial width of the table view is 0. The first size change is part of the initial config.
|
||||
// Any subsequence size change after that must trigger a relayout.
|
||||
CGSize tableViewFinalSize = CGSizeMake(100, 500);
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero
|
||||
style:UITableViewStylePlain
|
||||
asyncDataFetching:YES];
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectZero
|
||||
style:UITableViewStylePlain];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
@ -292,9 +290,8 @@
|
||||
- (void)testRelayoutVisibleRowsWhenEditingModeIsChanged
|
||||
{
|
||||
CGSize tableViewSize = CGSizeMake(100, 500);
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
|
||||
style:UITableViewStylePlain
|
||||
asyncDataFetching:YES];
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
|
||||
style:UITableViewStylePlain];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
@ -361,9 +358,8 @@
|
||||
- (void)DISABLED_testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible
|
||||
{
|
||||
CGSize tableViewSize = CGSizeMake(100, 500);
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
|
||||
style:UITableViewStylePlain
|
||||
asyncDataFetching:YES];
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
|
||||
style:UITableViewStylePlain];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
@ -398,9 +394,6 @@
|
||||
style:UITableViewStylePlain
|
||||
asyncDataFetching:YES];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
#if ! __has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
tableView.asyncDataSource = dataSource;
|
||||
@ -414,6 +407,7 @@
|
||||
XCTAssertEqual(indexPath.row, reportedIndexPath.row);
|
||||
}
|
||||
}
|
||||
self.testTableView = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
16
README.md
16
README.md
@ -57,6 +57,22 @@ dispatch_async(_backgroundQueue, ^{
|
||||
});
|
||||
```
|
||||
|
||||
In Swift:
|
||||
|
||||
```swift
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) {
|
||||
let node = ASTextNode()
|
||||
node.attributedString = NSAttributedString(string: "hello")
|
||||
node.measure(CGSize(width: screenWidth, height: CGFloat.max))
|
||||
node.frame = CGRect(origin: CGPointZero, size: node.calculatedSize)
|
||||
|
||||
// self.view isn't a node, so we can only use it on the main thread
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
self.view.addSubview(node.view)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
AsyncDisplayKit at a glance:
|
||||
|
||||
* `ASImageNode` and `ASTextNode` are drop-in replacements for UIImageView and
|
||||
|
||||
@ -13,10 +13,15 @@
|
||||
|
||||
#import "ViewController.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[ASDisplayNode setShouldUseNewRenderingRange:YES];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
self.window.backgroundColor = [UIColor whiteColor];
|
||||
self.window.rootViewController = [[UINavigationController alloc] init];
|
||||
|
||||
@ -130,7 +130,6 @@
|
||||
AC3C4A5B1A11F47200143C57 /* Frameworks */,
|
||||
AC3C4A5C1A11F47200143C57 /* Resources */,
|
||||
A6902C454C7661D0D277AC62 /* Copy Pods Resources */,
|
||||
EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -201,21 +200,6 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
||||
@ -54,7 +54,7 @@ static NSUInteger kNumberOfImages = 14;
|
||||
|
||||
_layoutInspector = [[MosaicCollectionViewLayoutInspector alloc] init];
|
||||
|
||||
_collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:YES];
|
||||
_collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
_collectionView.asyncDataSource = self;
|
||||
_collectionView.asyncDelegate = self;
|
||||
_collectionView.layoutInspector = _layoutInspector;
|
||||
@ -101,11 +101,13 @@ static NSUInteger kNumberOfImages = 14;
|
||||
return [[SupplementaryNode alloc] initWithText:text];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
|
||||
{
|
||||
return _sections.count;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
return [_sections[section] count];
|
||||
}
|
||||
|
||||
|
||||
@ -128,7 +128,6 @@
|
||||
05E2127E19D4DB510098F589 /* Frameworks */,
|
||||
05E2127F19D4DB510098F589 /* Resources */,
|
||||
F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
|
||||
54296444B3B4D82560F3906E /* Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -185,21 +184,6 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
54296444B3B4D82560F3906E /* Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
||||
@ -123,7 +123,6 @@
|
||||
05E2127E19D4DB510098F589 /* Frameworks */,
|
||||
05E2127F19D4DB510098F589 /* Resources */,
|
||||
F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
|
||||
93B7780A33739EF25F20366B /* Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -180,21 +179,6 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
93B7780A33739EF25F20366B /* Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
NSDictionary *attributes = @{NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:22.0f]};
|
||||
NSAttributedString *string = [[NSAttributedString alloc] initWithString:text
|
||||
attributes:attributes];
|
||||
[_buttonNode setAttributedTitle:string forState:ASButtonStateNormal];
|
||||
[_buttonNode setAttributedTitle:string forState:ASControlStateNormal];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
|
||||
@ -134,4 +134,18 @@
|
||||
return _socialAppDataSource.count;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
PostNode *postNode = (PostNode *)[_tableView nodeForRowAtIndexPath:indexPath];
|
||||
Post *post = _socialAppDataSource[indexPath.row];
|
||||
|
||||
BOOL shouldRasterize = postNode.shouldRasterizeDescendants;
|
||||
shouldRasterize = !shouldRasterize;
|
||||
postNode.shouldRasterizeDescendants = shouldRasterize;
|
||||
|
||||
NSLog(@"%@ rasterization for %@'s post: %@", shouldRasterize ? @"Enabling" : @"Disabling", post.name, postNode);
|
||||
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -128,7 +128,6 @@
|
||||
05E2127E19D4DB510098F589 /* Frameworks */,
|
||||
05E2127F19D4DB510098F589 /* Resources */,
|
||||
F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
|
||||
ACCB3408566E7626721EF2D5 /* Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -185,21 +184,6 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
ACCB3408566E7626721EF2D5 /* Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
||||
@ -13,10 +13,15 @@
|
||||
|
||||
#import "ViewController.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[ASDisplayNode setShouldUseNewRenderingRange:YES];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
self.window.backgroundColor = [UIColor whiteColor];
|
||||
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]];
|
||||
|
||||
@ -19,4 +19,6 @@
|
||||
|
||||
- (instancetype)initWithElementSize:(CGSize)size;
|
||||
|
||||
@property (nonatomic) NSInteger pageNumber;
|
||||
|
||||
@end
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
ASRangeTuningParameters rangeTuningParameters;
|
||||
rangeTuningParameters.leadingBufferScreenfuls = 1.0;
|
||||
rangeTuningParameters.trailingBufferScreenfuls = 0.5;
|
||||
[_tableNode.view setTuningParameters:rangeTuningParameters forRangeType:ASLayoutRangeTypeRender];
|
||||
[_tableNode.view setTuningParameters:rangeTuningParameters forRangeType:ASLayoutRangeTypeDisplay];
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
@ -63,9 +63,16 @@
|
||||
{
|
||||
RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init];
|
||||
elementNode.preferredFrameSize = _elementSize;
|
||||
elementNode.indexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:_pageNumber];
|
||||
return elementNode;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
[_tableNode.view reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
}
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
[super layout];
|
||||
|
||||
@ -9,5 +9,10 @@
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface RandomCoreGraphicsNode : ASCellNode
|
||||
{
|
||||
ASTextNode *_indexPathTextNode;
|
||||
}
|
||||
|
||||
@property (nonatomic) NSIndexPath *indexPath;
|
||||
|
||||
@end
|
||||
|
||||
@ -42,4 +42,55 @@
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_indexPathTextNode = [[ASTextNode alloc] init];
|
||||
[self addSubnode:_indexPathTextNode];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
_indexPath = indexPath;
|
||||
_indexPathTextNode.attributedString = [[NSAttributedString alloc] initWithString:[indexPath description] attributes:nil];
|
||||
}
|
||||
|
||||
//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
//{
|
||||
// ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[_indexPathTextNode]];
|
||||
// stackSpec.flexGrow = YES;
|
||||
// return stackSpec;
|
||||
//}
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
_indexPathTextNode.frame = self.bounds;
|
||||
[super layout];
|
||||
}
|
||||
|
||||
#if 0
|
||||
- (void)fetchData
|
||||
{
|
||||
NSLog(@"fetchData - %@, %@", self, self.indexPath);
|
||||
[super fetchData];
|
||||
}
|
||||
|
||||
- (void)clearFetchedData
|
||||
{
|
||||
NSLog(@"clearFetchedData - %@, %@", self, self.indexPath);
|
||||
[super clearFetchedData];
|
||||
}
|
||||
|
||||
- (void)visibilityDidChange:(BOOL)isVisible
|
||||
{
|
||||
NSLog(@"visibilityDidChange:%d - %@, %@", isVisible, self, self.indexPath);
|
||||
[super visibilityDidChange:isVisible];
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@ -10,14 +10,12 @@
|
||||
*/
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
#import "ViewController.h"
|
||||
#import "GradientTableNode.h"
|
||||
|
||||
@interface ViewController () <ASCollectionViewDataSource, ASCollectionViewDelegate>
|
||||
@interface ViewController () <ASPagerNodeDataSource>
|
||||
{
|
||||
ASCollectionView *_pagerView;
|
||||
ASPagerNode *_pagerNode;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -32,22 +30,11 @@
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
|
||||
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
// flowLayout.itemSize = [[UIScreen mainScreen] bounds].size;
|
||||
flowLayout.minimumInteritemSpacing = 0;
|
||||
flowLayout.minimumLineSpacing = 0;
|
||||
_pagerNode = [[ASPagerNode alloc] init];
|
||||
_pagerNode.dataSource = self;
|
||||
|
||||
_pagerView = [[ASCollectionView alloc] initWithCollectionViewLayout:flowLayout];
|
||||
|
||||
ASRangeTuningParameters rangeTuningParameters;
|
||||
rangeTuningParameters.leadingBufferScreenfuls = 1.0;
|
||||
rangeTuningParameters.trailingBufferScreenfuls = 1.0;
|
||||
[_pagerView setTuningParameters:rangeTuningParameters forRangeType:ASLayoutRangeTypeRender];
|
||||
|
||||
_pagerView.pagingEnabled = YES;
|
||||
_pagerView.asyncDataSource = self;
|
||||
_pagerView.asyncDelegate = self;
|
||||
// Could implement ASCollectionDelegate if we wanted extra callbacks, like from UIScrollView.
|
||||
//_pagerNode.delegate = self;
|
||||
|
||||
self.title = @"Paging Table Nodes";
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRedo
|
||||
@ -59,20 +46,19 @@
|
||||
|
||||
- (void)reloadEverything
|
||||
{
|
||||
[_pagerView reloadData];
|
||||
[_pagerNode reloadData];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
[self.view addSubview:_pagerView];
|
||||
[self.view addSubnode:_pagerNode];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
_pagerView.frame = self.view.bounds;
|
||||
_pagerView.contentInset = UIEdgeInsetsZero;
|
||||
_pagerNode.frame = self.view.bounds;
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden
|
||||
@ -81,20 +67,21 @@
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark ASTableView.
|
||||
#pragma mark ASPagerNode.
|
||||
|
||||
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index
|
||||
{
|
||||
CGSize boundsSize = collectionView.bounds.size;
|
||||
CGSize boundsSize = pagerNode.bounds.size;
|
||||
CGSize gradientRowSize = CGSizeMake(boundsSize.width, 100);
|
||||
GradientTableNode *node = [[GradientTableNode alloc] initWithElementSize:gradientRowSize];
|
||||
node.preferredFrameSize = boundsSize;
|
||||
node.pageNumber = index;
|
||||
return node;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode
|
||||
{
|
||||
return (section == 0 ? 10 : 0);
|
||||
return 10;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user