Merge commit '8edc9fe08febed871eab512a24917764c37ddbb0'

# Conflicts:
#	AsyncDisplayKit.xcodeproj/project.pbxproj
#	AsyncDisplayKit/Private/ASImageNode+CGExtras.m
This commit is contained in:
Peter 2016-08-23 16:18:35 +03:00
commit 18d6518f70
142 changed files with 3383 additions and 979 deletions

View File

@ -66,7 +66,7 @@
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A36195D057000B7D73C /* ASTextNodeTests.m */; }; 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A36195D057000B7D73C /* ASTextNodeTests.m */; };
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */; }; 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */; };
05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */; }; 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */; };
18C2ED7F1B9B7DE800F627B3 /* 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.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; };
18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; };
@ -225,6 +225,7 @@
8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; 8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; };
8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; };
81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */; };
81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; };
83A7D95A1D44542100BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; 83A7D95A1D44542100BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; };
83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; };
@ -264,7 +265,7 @@
9C70F20B1CDBE9A4007D6C76 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; }; 9C70F20B1CDBE9A4007D6C76 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; };
9C70F20C1CDBE9B6007D6C76 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; }; 9C70F20C1CDBE9B6007D6C76 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; };
9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; }; 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; };
9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; };
9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; };
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; };
@ -406,10 +407,11 @@
B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; };
B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; };
C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */; };
CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; };
CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; };
CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; };
CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; }; CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; settings = {ATTRIBUTES = (Public, ); }; };
CC3B208B1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; }; CC3B208B1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; };
CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; }; CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; };
CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */; }; CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */; };
@ -419,6 +421,7 @@
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; }; CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; };
CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; }; CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; };
CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; };
CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; };
CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; };
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; };
DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; }; DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; };
@ -437,7 +440,6 @@
DE4843DB1C93EAB100A1F33B /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; }; DE4843DB1C93EAB100A1F33B /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; };
DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; }; DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; };
DE6EA3231C14000600183B10 /* 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 */; };
DE72E2AC1D4D90E000DDB258 /* (null) in CopyFiles */ = {isa = PBXBuildFile; };
DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; };
DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; };
@ -708,7 +710,6 @@
F7CE6C611D2CDB3E00BE4C15 /* ASLayoutable.h in CopyFiles */, F7CE6C611D2CDB3E00BE4C15 /* ASLayoutable.h in CopyFiles */,
F7CE6C621D2CDB3E00BE4C15 /* ASLayoutablePrivate.h in CopyFiles */, F7CE6C621D2CDB3E00BE4C15 /* ASLayoutablePrivate.h in CopyFiles */,
F7CE6C631D2CDB3E00BE4C15 /* ASLayoutSpec.h in CopyFiles */, F7CE6C631D2CDB3E00BE4C15 /* ASLayoutSpec.h in CopyFiles */,
DE72E2AC1D4D90E000DDB258 /* (null) in CopyFiles */,
F7CE6C641D2CDB3E00BE4C15 /* ASOverlayLayoutSpec.h in CopyFiles */, F7CE6C641D2CDB3E00BE4C15 /* ASOverlayLayoutSpec.h in CopyFiles */,
F7CE6C651D2CDB3E00BE4C15 /* ASRatioLayoutSpec.h in CopyFiles */, F7CE6C651D2CDB3E00BE4C15 /* ASRatioLayoutSpec.h in CopyFiles */,
F7CE6C661D2CDB3E00BE4C15 /* ASRelativeLayoutSpec.h in CopyFiles */, F7CE6C661D2CDB3E00BE4C15 /* ASRelativeLayoutSpec.h in CopyFiles */,
@ -874,7 +875,7 @@
058D0A44195D058D00B7D73C /* ASBaseDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBaseDefines.h; sourceTree = "<group>"; }; 058D0A44195D058D00B7D73C /* ASBaseDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBaseDefines.h; sourceTree = "<group>"; };
05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDealloc2MainObject.h; path = ../Details/ASDealloc2MainObject.h; sourceTree = "<group>"; }; 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDealloc2MainObject.h; path = ../Details/ASDealloc2MainObject.h; sourceTree = "<group>"; };
05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = "<group>"; }; 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = "<group>"; };
05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASSnapshotTestCase.mm; sourceTree = "<group>"; }; 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASSnapshotTestCase.m; sourceTree = "<group>"; };
05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; 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>"; }; 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = "<group>"; };
18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = "<group>"; }; 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = "<group>"; };
@ -977,6 +978,7 @@
7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = "<group>"; }; 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = "<group>"; };
8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ASConvenience.h"; sourceTree = "<group>"; }; 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ASConvenience.h"; sourceTree = "<group>"; };
8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = "<group>"; }; 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = "<group>"; };
81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeSnapshotTests.m; sourceTree = "<group>"; };
81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = "<group>"; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = "<group>"; };
81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = "<group>"; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = "<group>"; };
83A7D9581D44542100BF333E /* ASWeakMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakMap.h; sourceTree = "<group>"; }; 83A7D9581D44542100BF333E /* ASWeakMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakMap.h; sourceTree = "<group>"; };
@ -1075,6 +1077,7 @@
B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../AsyncDisplayKit-iOS/Info.plist"; sourceTree = "<group>"; }; B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../AsyncDisplayKit-iOS/Info.plist"; sourceTree = "<group>"; };
BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = "<group>"; }; BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = "<group>"; };
CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASUICollectionViewTests.m; sourceTree = "<group>"; };
CC3B20811C3F76D600798563 /* ASPendingStateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPendingStateController.h; sourceTree = "<group>"; }; CC3B20811C3F76D600798563 /* ASPendingStateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPendingStateController.h; sourceTree = "<group>"; };
CC3B20821C3F76D600798563 /* ASPendingStateController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPendingStateController.mm; sourceTree = "<group>"; }; CC3B20821C3F76D600798563 /* ASPendingStateController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPendingStateController.mm; sourceTree = "<group>"; };
CC3B20871C3F7A5400798563 /* ASWeakSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakSet.h; sourceTree = "<group>"; }; CC3B20871C3F7A5400798563 /* ASWeakSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakSet.h; sourceTree = "<group>"; };
@ -1087,6 +1090,7 @@
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = "<group>"; }; CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = "<group>"; };
CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = "<group>"; }; CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = "<group>"; };
CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = "<group>"; }; CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = "<group>"; };
CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeSnapshotTests.m; sourceTree = "<group>"; };
D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = "<group>"; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = "<group>"; };
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = "<group>"; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = "<group>"; };
@ -1320,6 +1324,8 @@
058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = { 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */,
CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */,
83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */, 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */,
DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */,
DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */,
@ -1327,7 +1333,7 @@
CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */, CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */,
057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */,
056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */,
05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */, 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */,
056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */, 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */,
ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */, ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */,
7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */,
@ -1336,6 +1342,7 @@
ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */,
ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */, ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */,
AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */, AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */,
81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */,
ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */, ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */,
ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */, ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */,
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */, 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */,
@ -1443,6 +1450,10 @@
058D09FF195D050800B7D73C /* UIView+ASConvenience.h */, 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */,
9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */, 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */,
9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */, 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */,
CC3B20871C3F7A5400798563 /* ASWeakSet.h */,
CC3B20881C3F7A5400798563 /* ASWeakSet.m */,
DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */,
DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */,
); );
path = Details; path = Details;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1511,10 +1522,6 @@
ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */, ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */,
83A7D9581D44542100BF333E /* ASWeakMap.h */, 83A7D9581D44542100BF333E /* ASWeakMap.h */,
83A7D9591D44542100BF333E /* ASWeakMap.m */, 83A7D9591D44542100BF333E /* ASWeakMap.m */,
CC3B20871C3F7A5400798563 /* ASWeakSet.h */,
CC3B20881C3F7A5400798563 /* ASWeakSet.m */,
DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */,
DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */,
8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */, 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */,
8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */, 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */,
); );
@ -2180,6 +2187,7 @@
058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */, 058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */,
2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */, 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */,
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */, 058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */,
CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */,
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */, 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */,
CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */, CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */,
058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */, 058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */,
@ -2193,11 +2201,13 @@
058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */,
697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */, 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */,
ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */,
CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */,
ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */,
7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */,
254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */,
05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */, 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */,
ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */,
81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */,
3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */,
AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */, AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */,
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */,
@ -2491,6 +2501,7 @@
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.1; IPHONEOS_DEPLOYMENT_TARGET = 7.1;
OTHER_CFLAGS = "-Wall"; OTHER_CFLAGS = "-Wall";
@ -2512,6 +2523,7 @@
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.1; IPHONEOS_DEPLOYMENT_TARGET = 7.1;
OTHER_CFLAGS = "-Wall"; OTHER_CFLAGS = "-Wall";
@ -2697,6 +2709,7 @@
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.1; IPHONEOS_DEPLOYMENT_TARGET = 7.1;
OTHER_CFLAGS = "-Wall"; OTHER_CFLAGS = "-Wall";

View File

@ -14,7 +14,6 @@
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASBackgroundLayoutSpec.h" #import "ASBackgroundLayoutSpec.h"
#import "ASInsetLayoutSpec.h" #import "ASInsetLayoutSpec.h"
#import "ASDisplayNode+Beta.h"
#import "ASStaticLayoutSpec.h" #import "ASStaticLayoutSpec.h"
@interface ASButtonNode () @interface ASButtonNode ()
@ -56,7 +55,7 @@
- (instancetype)init - (instancetype)init
{ {
if (self = [super init]) { if (self = [super init]) {
self.usesImplicitHierarchyManagement = YES; self.automaticallyManagesSubnodes = YES;
_contentSpacing = 8.0; _contentSpacing = 8.0;
_laysOutHorizontally = YES; _laysOutHorizontally = YES;

View File

@ -115,13 +115,35 @@
[self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize];
} }
- (void)transitionLayoutWithAnimation:(BOOL)animated - (void)transitionLayoutAnimated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(void (^)())completion
measurementCompletion:(void(^)())completion
{ {
CGSize oldSize = self.calculatedSize; CGSize oldSize = self.calculatedSize;
[super transitionLayoutWithAnimation:animated [super transitionLayoutAnimated:animated
shouldMeasureAsync:shouldMeasureAsync measurementCompletion:^{
[self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize];
if (completion) {
completion();
}
}
];
}
//Deprecated
- (void)transitionLayoutWithAnimation:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion
{
[self transitionLayoutAnimated:animated measurementCompletion:completion];
}
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated
measurementCompletion:(void (^)())completion
{
CGSize oldSize = self.calculatedSize;
[super transitionLayoutWithSizeRange:constrainedSize
animated:animated
measurementCompletion:^{ measurementCompletion:^{
[self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize];
if (completion) { if (completion) {
@ -131,22 +153,13 @@
]; ];
} }
//Deprecated
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated animated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion measurementCompletion:(void(^)())completion
{ {
CGSize oldSize = self.calculatedSize; [self transitionLayoutWithSizeRange:constrainedSize animated:animated measurementCompletion:completion];
[super transitionLayoutWithSizeRange:constrainedSize
animated:animated
shouldMeasureAsync:shouldMeasureAsync
measurementCompletion:^{
[self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize];
if (completion) {
completion();
}
}
];
} }
- (void)didRelayoutFromOldSize:(CGSize)oldSize toNewSize:(CGSize)newSize - (void)didRelayoutFromOldSize:(CGSize)oldSize toNewSize:(CGSize)newSize

View File

@ -17,6 +17,7 @@
#import "ASEnvironmentInternal.h" #import "ASEnvironmentInternal.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASCellNode+Internal.h" #import "ASCellNode+Internal.h"
#import "AsyncDisplayKit+Debug.h"
#pragma mark - _ASCollectionPendingState #pragma mark - _ASCollectionPendingState
@ -171,6 +172,12 @@
[self.view clearFetchedData]; [self.view clearFetchedData];
} }
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
{
[super interfaceStateDidChange:newState fromState:oldState];
[ASRangeController layoutDebugOverlayIfNeeded];
}
#if ASRangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
- (void)visibleStateDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {

View File

@ -400,17 +400,6 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
* Provides the constrained size range for measuring the node at the index path.
*
* @param collectionView The sender.
*
* @param indexPath The index path of the node.
*
* @returns A constrained size range for layout the node at this index path.
*/
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
/** /**
* Indicator to lock the data source for data fetching in async mode. * Indicator to lock the data source for data fetching in async mode.
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception
@ -442,6 +431,17 @@ NS_ASSUME_NONNULL_BEGIN
@optional @optional
/**
* Provides the constrained size range for measuring the node at the index path.
*
* @param collectionView The sender.
*
* @param indexPath The index path of the node.
*
* @returns A constrained size range for layout the node at this index path.
*/
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
/** /**
* Informs the delegate that the collection view will add the node * Informs the delegate that the collection view will add the node
* at the given index path to the view hierarchy. * at the given index path to the view hierarchy.

View File

@ -18,7 +18,6 @@
#import "ASCollectionViewFlowLayoutInspector.h" #import "ASCollectionViewFlowLayoutInspector.h"
#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Beta.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "UICollectionViewLayout+ASConvenience.h" #import "UICollectionViewLayout+ASConvenience.h"
#import "ASRangeController.h" #import "ASRangeController.h"
@ -151,11 +150,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
} _asyncDelegateFlags; } _asyncDelegateFlags;
struct { struct {
unsigned int asyncDataSourceConstrainedSizeForNode:1;
unsigned int asyncDataSourceNodeForItemAtIndexPath:1; unsigned int asyncDataSourceNodeForItemAtIndexPath:1;
unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1; unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1;
unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1; unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1;
unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1;
} _asyncDataSourceFlags; } _asyncDataSourceFlags;
struct { struct {
@ -354,11 +351,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_asyncDataSource = asyncDataSource; _asyncDataSource = asyncDataSource;
_proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
_asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
// Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath:
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath); ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath);
@ -1038,11 +1033,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section]; return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section];
} }
- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
{
return [self.layoutInspector collectionView:self numberOfSectionsForSupplementaryNodeOfKind:kind];
}
#pragma mark - ASRangeControllerDataSource #pragma mark - ASRangeControllerDataSource
- (ASRangeController *)rangeController - (ASRangeController *)rangeController
@ -1080,6 +1070,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return [_dataController nodeAtIndexPath:indexPath]; return [_dataController nodeAtIndexPath:indexPath];
} }
- (NSString *)nameForRangeControllerDataSource
{
return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]);
}
#pragma mark - ASRangeControllerDelegate #pragma mark - ASRangeControllerDelegate
- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController - (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController

View File

@ -10,7 +10,10 @@
// of patent rights can be found in the PATENTS file in the same directory. // of patent rights can be found in the PATENTS file in the same directory.
// //
#import <AsyncDisplayKit/ASDisplayNode.h> #import <AsyncDisplayKit/ASDimension.h>
@class ASDisplayNode;
@class ASLayout;
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN

View File

@ -8,7 +8,7 @@
// of patent rights can be found in the PATENTS file in the same directory. // of patent rights can be found in the PATENTS file in the same directory.
// //
#import "ASContextTransitioning.h" #import "ASDisplayNode.h"
#import "ASLayoutRangeType.h" #import "ASLayoutRangeType.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -20,9 +20,6 @@ ASDISPLAYNODE_EXTERN_C_END
@interface ASDisplayNode (Beta) @interface ASDisplayNode (Beta)
+ (BOOL)usesImplicitHierarchyManagement;
+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled;
/** /**
* ASTableView and ASCollectionView now throw exceptions on invalid updates * ASTableView and ASCollectionView now throw exceptions on invalid updates
* like their UIKit counterparts. If YES, these classes will log messages * like their UIKit counterparts. If YES, these classes will log messages
@ -63,65 +60,12 @@ ASDISPLAYNODE_EXTERN_C_END
/** @name Layout Transitioning */ /** @name Layout Transitioning */
@property (nonatomic) BOOL usesImplicitHierarchyManagement;
/**
* @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy.
*/
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context;
/**
* @discussion A place to clean up your nodes after the transition
*/
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context;
/**
* @abstract Transitions the current layout with a new constrained size. Must be called on main thread.
*
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
*
* @param shouldMeasureAsync Measure the layout asynchronously.
*
* @param measurementCompletion Optional completion block called only if a new layout is calculated.
* It is called on main, right after the measurement and before -animateLayoutTransition:.
*
* @discussion If the passed constrainedSize is the the same as the node's current constrained size, this method is noop.
*
* @see animateLayoutTransition:
*/
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(nullable void(^)())completion;
/**
* @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread.
*
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
*
* @param shouldMeasureAsync Measure the layout asynchronously.
*
* @param measurementCompletion Optional completion block called only if a new layout is calculated.
* It is called right after the measurement and before -animateLayoutTransition:.
*
* @see animateLayoutTransition:
*/
- (void)transitionLayoutWithAnimation:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(nullable void(^)())completion;
/** /**
* @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network. * @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network.
* Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished. * Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished.
*/ */
- (BOOL)placeholderShouldPersist; - (BOOL)placeholderShouldPersist;
/**
* @abstract Cancels all performing layout transitions. Can be called on any thread.
*/
- (void)cancelLayoutTransitionsInProgress;
/** /**
* @abstract Indicates that the receiver and all subnodes have finished displaying. May be called more than once, for example if the receiver has * @abstract Indicates that the receiver and all subnodes have finished displaying. May be called more than once, for example if the receiver has
* a network image node. This is called after the first display pass even if network image nodes have not downloaded anything (text would be done, * a network image node. This is called after the first display pass even if network image nodes have not downloaded anything (text would be done,

View File

@ -16,6 +16,7 @@
#import <AsyncDisplayKit/ASDimension.h> #import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASAsciiArtBoxCreator.h> #import <AsyncDisplayKit/ASAsciiArtBoxCreator.h>
#import <AsyncDisplayKit/ASLayoutable.h> #import <AsyncDisplayKit/ASLayoutable.h>
#import <AsyncDisplayKit/ASContextTransitioning.h>
#define ASDisplayNodeLoggingEnabled 0 #define ASDisplayNodeLoggingEnabled 0
@ -615,7 +616,7 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
NS_ASSUME_NONNULL_END
/** /**
* ## UIView bridge * ## UIView bridge
* *
@ -744,11 +745,92 @@ NS_ASSUME_NONNULL_END
// Accessibility identification support // Accessibility identification support
@property (nonatomic, copy, nullable) NSString *accessibilityIdentifier; @property (nonatomic, copy, nullable) NSString *accessibilityIdentifier;
@end
@interface ASDisplayNode (LayoutTransitioning)
/**
* @abstract The amount of time it takes to complete the default transition animation. Default is 0.2.
*/
@property (nonatomic, assign) NSTimeInterval defaultLayoutTransitionDuration;
/**
* @abstract The amount of time (measured in seconds) to wait before beginning the default transition animation.
* Default is 0.0.
*/
@property (nonatomic, assign) NSTimeInterval defaultLayoutTransitionDelay;
/**
* @abstract A mask of options indicating how you want to perform the default transition animations.
* For a list of valid constants, see UIViewAnimationOptions.
*/
@property (nonatomic, assign) UIViewAnimationOptions defaultLayoutTransitionOptions;
/**
* @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy.
*/
- (void)animateLayoutTransition:(nonnull id<ASContextTransitioning>)context;
/**
* @discussion A place to clean up your nodes after the transition
*/
- (void)didCompleteLayoutTransition:(nonnull id<ASContextTransitioning>)context;
/**
* @abstract Transitions the current layout with a new constrained size. Must be called on main thread.
*
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
* @param shouldMeasureAsync Measure the layout asynchronously.
* @param measurementCompletion Optional completion block called only if a new layout is calculated.
*
* @discussion It is called on main, right after the measurement and before -animateLayoutTransition:. If the passed constrainedSize is the the same as the node's current constrained size, this method is noop.
*
* @see animateLayoutTransition:
*/
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated
measurementCompletion:(nullable void(^)())completion;
/**
* @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread.
*
* @discussion It is called right after the measurement and before -animateLayoutTransition:.
*
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
* @param measurementCompletion Optional completion block called only if a new layout is calculated.
*
* @see animateLayoutTransition:
*
*/
- (void)transitionLayoutAnimated:(BOOL)animated measurementCompletion:(nullable void(^)())completion;
/**
* @abstract Cancels all performing layout transitions. Can be called on any thread.
*/
- (void)cancelLayoutTransition;
@end @end
/* /*
ASDisplayNode participates in ASAsyncTransactions, so you can determine when your subnodes are done rendering. * ASDisplayNode support for automatic subnode management.
See: -(void)asyncdisplaykit_asyncTransactionContainerStateDidChange in ASDisplayNodeSubclass.h */
@interface ASDisplayNode (AutomaticSubnodeManagement)
/**
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
* absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method.
*
* @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence
* or absence of subnodes is completely determined in its layoutSpecThatFits: method.
*/
@property (nonatomic, assign) BOOL automaticallyManagesSubnodes;
@end
/*
* ASDisplayNode participates in ASAsyncTransactions, so you can determine when your subnodes are done rendering.
* See: -(void)asyncdisplaykit_asyncTransactionContainerStateDidChange in ASDisplayNodeSubclass.h
*/ */
@interface ASDisplayNode (ASDisplayNodeAsyncTransactionContainer) <ASDisplayNodeAsyncTransactionContainer> @interface ASDisplayNode (ASDisplayNodeAsyncTransactionContainer) <ASDisplayNodeAsyncTransactionContainer>
@end @end
@ -763,7 +845,9 @@ NS_ASSUME_NONNULL_END
- (void)addSubnode:(nonnull ASDisplayNode *)node; - (void)addSubnode:(nonnull ASDisplayNode *)node;
@end @end
/** CALayer(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to a CALayer. */ /*
* CALayer(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to a CALayer.
*/
@interface CALayer (AsyncDisplayKit) @interface CALayer (AsyncDisplayKit)
/** /**
* Convenience method, equivalent to [layer addSublayer:node.layer]. * Convenience method, equivalent to [layer addSublayer:node.layer].
@ -776,8 +860,69 @@ NS_ASSUME_NONNULL_END
@interface ASDisplayNode (Deprecated) @interface ASDisplayNode (Deprecated)
/**
* @abstract Transitions the current layout with a new constrained size. Must be called on main thread.
*
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
* @param shouldMeasureAsync Measure the layout asynchronously.
* @param measurementCompletion Optional completion block called only if a new layout is calculated.
* It is called on main, right after the measurement and before -animateLayoutTransition:.
*
* @discussion If the passed constrainedSize is the the same as the node's current constrained size, this method is noop.
*
* @see animateLayoutTransition:
*
* @deprecated Deprecated in version 2.0: Use transitionLayoutWithSizeRange:animated:measurementCompletion:.
* shouldMeasureAsync is enabled by default now.
*
*/
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(nullable void(^)())completion ASDISPLAYNODE_DEPRECATED;
/**
* @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread.
*
* @discussion It is called right after the measurement and before -animateLayoutTransition:.
*
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
* @param shouldMeasureAsync Measure the layout asynchronously.
* @param measurementCompletion Optional completion block called only if a new layout is calculated.
*
* @see animateLayoutTransition:
*
* @deprecated Deprecated in version 2.0: Use transitionLayoutAnimated:measurementCompletion:
* shouldMeasureAsync is enabled by default now.
*
*/
- (void)transitionLayoutWithAnimation:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(nullable void(^)())completion ASDISPLAYNODE_DEPRECATED;
/**
* @abstract Cancels all performing layout transitions. Can be called on any thread.
*
* @deprecated Deprecated in version 2.0: Use cancelLayoutTransition
*/
- (void)cancelLayoutTransitionsInProgress ASDISPLAYNODE_DEPRECATED;
/**
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
* absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method.
*
* @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence
* or absence of subnodes is completely determined in its layoutSpecThatFits: method.
*
* @deprecated Deprecated in version 2.0: Use automaticallyManagesSubnodes
*/
@property (nonatomic, assign) BOOL usesImplicitHierarchyManagement ASDISPLAYNODE_DEPRECATED;
- (void)reclaimMemory ASDISPLAYNODE_DEPRECATED; - (void)reclaimMemory ASDISPLAYNODE_DEPRECATED;
- (void)recursivelyReclaimMemory ASDISPLAYNODE_DEPRECATED; - (void)recursivelyReclaimMemory ASDISPLAYNODE_DEPRECATED;
@property (nonatomic, assign) BOOL placeholderFadesOut ASDISPLAYNODE_DEPRECATED; @property (nonatomic, assign) BOOL placeholderFadesOut ASDISPLAYNODE_DEPRECATED;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -77,18 +77,6 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS
@synthesize isFinalLayoutable = _isFinalLayoutable; @synthesize isFinalLayoutable = _isFinalLayoutable;
@synthesize threadSafeBounds = _threadSafeBounds; @synthesize threadSafeBounds = _threadSafeBounds;
static BOOL usesImplicitHierarchyManagement = NO;
+ (BOOL)usesImplicitHierarchyManagement
{
return usesImplicitHierarchyManagement;
}
+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled
{
usesImplicitHierarchyManagement = enabled;
}
static BOOL suppressesInvalidCollectionUpdateExceptions = YES; static BOOL suppressesInvalidCollectionUpdateExceptions = YES;
+ (BOOL)suppressesInvalidCollectionUpdateExceptions + (BOOL)suppressesInvalidCollectionUpdateExceptions
@ -291,8 +279,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
_environmentState = ASEnvironmentStateMakeDefault(); _environmentState = ASEnvironmentStateMakeDefault();
_defaultLayoutTransitionDuration = 0.2;
_defaultLayoutTransitionDelay = 0.0;
_defaultLayoutTransitionOptions = UIViewAnimationOptionBeginFromCurrentState;
_flags.canClearContentsOfLayer = YES; _flags.canClearContentsOfLayer = YES;
_flags.canCallNeedsDisplayOfLayer = NO; _flags.canCallSetNeedsDisplayOfLayer = YES;
} }
- (instancetype)init - (instancetype)init
@ -462,12 +454,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
} }
// Update flags related to special handling of UIImageView layers. More details on the flags // Update flags related to special handling of UIImageView layers. More details on the flags
if (_flags.synchronous) { if (_flags.synchronous && [_viewClass isSubclassOfClass:[UIImageView class]]) {
if ([view isKindOfClass:[UIImageView class]]) { _flags.canClearContentsOfLayer = NO;
_flags.canClearContentsOfLayer = NO; _flags.canCallSetNeedsDisplayOfLayer = NO;
} else {
_flags.canCallNeedsDisplayOfLayer = YES;
}
} }
return view; return view;
@ -646,7 +635,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return _calculatedLayout ? : [ASLayout layoutWithLayoutableObject:self constrainedSizeRange:constrainedSize size:CGSizeZero]; return _calculatedLayout ? : [ASLayout layoutWithLayoutableObject:self constrainedSizeRange:constrainedSize size:CGSizeZero];
} }
[self cancelLayoutTransitionsInProgress]; [self cancelLayoutTransition];
ASLayout *previousLayout = _calculatedLayout; ASLayout *previousLayout = _calculatedLayout;
ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize]; ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize];
@ -699,11 +688,23 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return !self.isNodeLoaded; return !self.isNodeLoaded;
} }
#pragma mark - Automatic Hierarchy
- (BOOL)automaticallyManagesSubnodes
{
ASDN::MutexLocker l(__instanceLock__);
return _automaticallyManagesSubnodes;
}
- (void)setAutomaticallyManagesSubnodes:(BOOL)automaticallyManagesSubnodes
{
ASDN::MutexLocker l(__instanceLock__);
_automaticallyManagesSubnodes = automaticallyManagesSubnodes;
}
#pragma mark - Layout Transition #pragma mark - Layout Transition
- (void)transitionLayoutWithAnimation:(BOOL)animated - (void)transitionLayoutAnimated:(BOOL)animated measurementCompletion:(void (^)())completion
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion
{ {
if (_calculatedLayout == nil) { if (_calculatedLayout == nil) {
// constrainedSizeRange returns a struct and is invalid to call on nil. // constrainedSizeRange returns a struct and is invalid to call on nil.
@ -714,14 +715,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
[self invalidateCalculatedLayout]; [self invalidateCalculatedLayout];
[self transitionLayoutWithSizeRange:_calculatedLayout.constrainedSizeRange [self transitionLayoutWithSizeRange:_calculatedLayout.constrainedSizeRange
animated:animated animated:animated
shouldMeasureAsync:shouldMeasureAsync
measurementCompletion:completion]; measurementCompletion:completion];
} }
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated animated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(void (^)())completion
measurementCompletion:(void(^)())completion
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
if (! [self shouldMeasureWithSizeRange:constrainedSize]) { if (! [self shouldMeasureWithSizeRange:constrainedSize]) {
@ -741,7 +740,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
node.pendingTransitionID = transitionID; node.pendingTransitionID = transitionID;
}); });
void (^transitionBlock)() = ^{ ASPerformBlockOnBackgroundThread(^{
if ([self _shouldAbortTransitionWithID:transitionID]) { if ([self _shouldAbortTransitionWithID:transitionID]) {
return; return;
} }
@ -751,11 +750,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
ASLayoutableSetCurrentContext(ASLayoutableContextMake(transitionID, NO)); ASLayoutableSetCurrentContext(ASLayoutableContextMake(transitionID, NO));
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
BOOL disableImplicitHierarchyManagement = self.usesImplicitHierarchyManagement == NO; BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO);
self.usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x
newLayout = [self calculateLayoutThatFits:constrainedSize]; newLayout = [self calculateLayoutThatFits:constrainedSize];
if (disableImplicitHierarchyManagement) { if (automaticallyManagesSubnodesDisabled) {
self.usesImplicitHierarchyManagement = NO; // Temporary flag for 1.9.x self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x
} }
ASLayoutableClearCurrentContext(); ASLayoutableClearCurrentContext();
@ -789,12 +788,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
} }
// Setup pending layout transition for animation // Setup pending layout transition for animation
// The pending layout transition needs to stay alive at least until applySubnodeInsertions did finish execute as _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
// it can happen that with Implicit Hierarchy Management new nodes gonna be added that internally call setNeedsLayout pendingLayout:newLayout
// what will invalidate and deallocate the transition in the middle of inserting nodes previousLayout:previousLayout];
NS_VALID_UNTIL_END_OF_SCOPE ASLayoutTransition *pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self pendingLayout:newLayout previousLayout:previousLayout];
_pendingLayoutTransition = pendingLayoutTransition;
// Setup context for pending layout transition. we need to hold a strong reference to the context // Setup context for pending layout transition. we need to hold a strong reference to the context
_pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated _pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated
layoutDelegate:_pendingLayoutTransition layoutDelegate:_pendingLayoutTransition
@ -806,12 +802,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
// Kick off animating the layout transition // Kick off animating the layout transition
[self animateLayoutTransition:_pendingLayoutTransitionContext]; [self animateLayoutTransition:_pendingLayoutTransitionContext];
}); });
}; });
ASPerformBlockOnBackgroundThread(transitionBlock);
} }
- (void)cancelLayoutTransitionsInProgress - (void)cancelLayoutTransition
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if ([self _isTransitionInProgress]) { if ([self _isTransitionInProgress]) {
@ -825,18 +819,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
} }
} }
- (BOOL)usesImplicitHierarchyManagement
{
ASDN::MutexLocker l(__instanceLock__);
return _usesImplicitHierarchyManagement ? : [[self class] usesImplicitHierarchyManagement];
}
- (void)setUsesImplicitHierarchyManagement:(BOOL)value
{
ASDN::MutexLocker l(__instanceLock__);
_usesImplicitHierarchyManagement = value;
}
- (BOOL)_isTransitionInProgress - (BOOL)_isTransitionInProgress
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
@ -864,16 +846,126 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return (!_transitionInProgress || _transitionID != transitionID); return (!_transitionInProgress || _transitionID != transitionID);
} }
#pragma mark - Layout Transition API / ASDisplayNode (Beta) #pragma mark Layout Transition API
- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration
{
ASDN::MutexLocker l(__instanceLock__);
_defaultLayoutTransitionDuration = defaultLayoutTransitionDuration;
}
- (NSTimeInterval)defaultLayoutTransitionDuration
{
ASDN::MutexLocker l(__instanceLock__);
return _defaultLayoutTransitionDuration;
}
- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay
{
ASDN::MutexLocker l(__instanceLock__);
_defaultLayoutTransitionDelay = defaultLayoutTransitionDelay;
}
- (NSTimeInterval)defaultLayoutTransitionDelay
{
ASDN::MutexLocker l(__instanceLock__);
return _defaultLayoutTransitionDelay;
}
- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions
{
ASDN::MutexLocker l(__instanceLock__);
_defaultLayoutTransitionOptions = defaultLayoutTransitionOptions;
}
- (UIViewAnimationOptions)defaultLayoutTransitionOptions
{
ASDN::MutexLocker l(__instanceLock__);
return _defaultLayoutTransitionOptions;
}
/* /*
* Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default this just layouts * Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default a fade in and out
* applies all subnodes without animation and calls completes the transition on the context. * animation is provided.
*/ */
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context - (void)animateLayoutTransition:(id<ASContextTransitioning>)context
{ {
[self __layoutSublayouts]; if ([context isAnimated] == NO) {
[context completeTransition:YES]; [self __layoutSublayouts];
[context completeTransition:YES];
return;
}
ASDisplayNode *node = self;
NSAssert(node.isNodeLoaded == YES, @"Invalid node state");
NSAssert([context isAnimated] == YES, @"Can't animate a non-animatable context");
NSArray<ASDisplayNode *> *removedSubnodes = [context removedSubnodes];
NSMutableArray<UIView *> *removedViews = [NSMutableArray array];
NSMutableArray<ASDisplayNode *> *insertedSubnodes = [[context insertedSubnodes] mutableCopy];
NSMutableArray<ASDisplayNode *> *movedSubnodes = [NSMutableArray array];
for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) {
if ([insertedSubnodes containsObject:subnode] == NO) {
// This is an existing subnode, check if it is resized, moved or both
CGRect fromFrame = [context initialFrameForNode:subnode];
CGRect toFrame = [context finalFrameForNode:subnode];
if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) {
// To crossfade resized subnodes, show a snapshot of it on top.
// The node itself can then be treated as a newly-inserted one.
UIView *snapshotView = [subnode.view snapshotViewAfterScreenUpdates:YES];
snapshotView.frame = [context initialFrameForNode:subnode];
snapshotView.alpha = 1;
[node.view insertSubview:snapshotView aboveSubview:subnode.view];
[removedViews addObject:snapshotView];
[insertedSubnodes addObject:subnode];
}
if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) {
[movedSubnodes addObject:subnode];
}
}
}
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
insertedSubnode.frame = [context finalFrameForNode:insertedSubnode];
insertedSubnode.alpha = 0;
}
[UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{
// Fade removed subnodes and views out
for (ASDisplayNode *removedSubnode in removedSubnodes) {
removedSubnode.alpha = 0;
}
for (UIView *removedView in removedViews) {
removedView.alpha = 0;
}
// Fade inserted subnodes in
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
insertedSubnode.alpha = 1;
}
// Update frame of self and moved subnodes
CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size;
CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size;
BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO);
if (isResized == YES) {
CGPoint position = node.frame.origin;
node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height);
}
for (ASDisplayNode *movedSubnode in movedSubnodes) {
movedSubnode.frame = [context finalFrameForNode:movedSubnode];
}
} completion:^(BOOL finished) {
for (UIView *removedView in removedViews) {
[removedView removeFromSuperview];
}
// Subnode removals are automatically performed
[context completeTransition:finished];
}];
} }
/* /*
@ -885,7 +977,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
[_pendingLayoutTransition applySubnodeRemovals]; [_pendingLayoutTransition applySubnodeRemovals];
} }
#pragma mark - _ASTransitionContextCompletionDelegate #pragma mark _ASTransitionContextCompletionDelegate
/* /*
* After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this
@ -920,8 +1012,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
*/ */
- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition - (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition
{ {
// Layout transition is not supported for non implicit hierarchy managed nodes yet // Layout transition is not supported for nodes that are not have automatic subnode management enabled
if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) { if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) {
return; return;
} }
@ -1087,7 +1179,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
// FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
// but implicit hierarchy management would require us to modify the node tree // but automatic subnode management would require us to modify the node tree
// in the background on a loaded node, which isn't currently supported. // in the background on a loaded node, which isn't currently supported.
if (_pendingViewState.hasSetNeedsLayout) { if (_pendingViewState.hasSetNeedsLayout) {
[self __setNeedsLayout]; [self __setNeedsLayout];
@ -1883,7 +1975,7 @@ static NSInteger incrementIfFound(NSInteger i) {
// If a node was added to a supernode, the supernode could be in a layout pending state. All of the hierarchy state // If a node was added to a supernode, the supernode could be in a layout pending state. All of the hierarchy state
// properties related to the transition need to be copied over as well as propagated down the subtree. // properties related to the transition need to be copied over as well as propagated down the subtree.
// This is especially important as with Implicit Hierarchy Management adding subnodes can happen while a transition // This is especially important as with automatic subnode management, adding subnodes can happen while a transition
// is in fly // is in fly
if (ASHierarchyStateIncludesLayoutPending(stateToEnterOrExit)) { if (ASHierarchyStateIncludesLayoutPending(stateToEnterOrExit)) {
int32_t pendingTransitionId = newSupernode.pendingTransitionID; int32_t pendingTransitionId = newSupernode.pendingTransitionID;
@ -1961,11 +2053,11 @@ static NSInteger incrementIfFound(NSInteger i) {
_flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay; _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay;
} }
// Helper method to determine if it's save to call setNeedsDisplay on a layer without throwing away the content. // Helper method to determine if it's safe to call setNeedsDisplay on a layer without throwing away the content.
// For details look at the comment on the canCallNeedsDisplayOfLayer flag // For details look at the comment on the canCallSetNeedsDisplayOfLayer flag
- (BOOL)__canCallNeedsDisplayOfLayer - (BOOL)__canCallSetNeedsDisplayOfLayer
{ {
return _flags.canCallNeedsDisplayOfLayer; return _flags.canCallSetNeedsDisplayOfLayer;
} }
- (BOOL)placeholderShouldPersist - (BOOL)placeholderShouldPersist
@ -2016,9 +2108,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
ASDisplayNode *node = [layer asyncdisplaykit_node]; ASDisplayNode *node = [layer asyncdisplaykit_node];
if ([node __canCallNeedsDisplayOfLayer]) { if (node.isSynchronous && [node __canCallSetNeedsDisplayOfLayer]) {
// Layers for UIKit components that are wrapped wtihin a node needs to be set to be displayed as the contents of // Layers for UIKit components that are wrapped within a node needs to be set to be displayed as the contents of
// the layer get's cleared and would not be recreated otherwise // the layer get's cleared and would not be recreated otherwise.
// We do not call this for _ASDisplayLayer as an optimization.
[layer setNeedsDisplay]; [layer setNeedsDisplay];
} }
@ -2602,7 +2695,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
ASDisplayNodeAssertTrue(layout.size.width >= 0.0); ASDisplayNodeAssertTrue(layout.size.width >= 0.0);
ASDisplayNodeAssertTrue(layout.size.height >= 0.0); ASDisplayNodeAssertTrue(layout.size.height >= 0.0);
if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) { if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) {
return; return;
} }
@ -3175,6 +3268,36 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
@implementation ASDisplayNode (Deprecated) @implementation ASDisplayNode (Deprecated)
- (void)transitionLayoutWithAnimation:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion
{
[self transitionLayoutAnimated:animated measurementCompletion:completion];
}
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion
{
[self transitionLayoutWithSizeRange:constrainedSize animated:animated measurementCompletion:completion];
}
- (void)cancelLayoutTransitionsInProgress
{
[self cancelLayoutTransition];
}
- (BOOL)usesImplicitHierarchyManagement
{
return self.automaticallyManagesSubnodes;
}
- (void)setUsesImplicitHierarchyManagement:(BOOL)enabled
{
self.automaticallyManagesSubnodes = enabled;
}
- (void)setPlaceholderFadesOut:(BOOL)placeholderFadesOut - (void)setPlaceholderFadesOut:(BOOL)placeholderFadesOut
{ {
self.placeholderFadeDuration = placeholderFadesOut ? 0.1 : 0.0; self.placeholderFadeDuration = placeholderFadesOut ? 0.1 : 0.0;

View File

@ -11,6 +11,7 @@
#import "ASEditableTextNode.h" #import "ASEditableTextNode.h"
#import <objc/message.h> #import <objc/message.h>
#import <tgmath.h>
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASEqualityHelpers.h" #import "ASEqualityHelpers.h"
@ -238,9 +239,9 @@
{ {
ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents;
CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width];
CGFloat width = ceilf(textSize.width + _textContainerInset.left + _textContainerInset.right); CGFloat width = std::ceil(textSize.width + _textContainerInset.left + _textContainerInset.right);
CGFloat height = ceilf(textSize.height + _textContainerInset.top + _textContainerInset.bottom); CGFloat height = std::ceil(textSize.height + _textContainerInset.top + _textContainerInset.bottom);
return CGSizeMake(fminf(width, constrainedSize.width), fminf(height, constrainedSize.height)); return CGSizeMake(std::fmin(width, constrainedSize.width), std::fmin(height, constrainedSize.height));
} }
- (void)layout - (void)layout

View File

@ -12,7 +12,10 @@
#if TARGET_OS_TV #if TARGET_OS_TV
#import "ASImageNode+tvOS.h" #import "ASImageNode+tvOS.h"
#import <GLKit/GLKit.h> #import <GLKit/GLKit.h>
#import <tgmath.h>
#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeExtras.h"
@implementation ASImageNode (tvOS) @implementation ASImageNode (tvOS)
@ -75,8 +78,8 @@
// BUT we apply our transforms to *view since we want to apply // BUT we apply our transforms to *view since we want to apply
// the transforms to the root view (L: 107) // the transforms to the root view (L: 107)
CGPoint point = [touch locationInView:self.view]; CGPoint point = [touch locationInView:self.view];
float pitch = 0; CGFloat pitch = 0;
float yaw = 0; CGFloat yaw = 0;
BOOL topHalf = NO; BOOL topHalf = NO;
if (point.y > CGRectGetHeight(self.view.frame)) { if (point.y > CGRectGetHeight(self.view.frame)) {
pitch = 15; pitch = 15;
@ -100,7 +103,7 @@
if (yaw > 0) { if (yaw > 0) {
yaw = -yaw; yaw = -yaw;
} else { } else {
yaw = fabsf(yaw); yaw = fabs(yaw);
} }
} }

View File

@ -85,7 +85,7 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image);
* *
* @discussion This value defines a rectangle that is to be featured by the * @discussion This value defines a rectangle that is to be featured by the
* receiver. The rectangle is specified as a "unit rectangle," using * receiver. The rectangle is specified as a "unit rectangle," using
* percentages of the source image's width and height, e.g. CGRectMake(0.5, 0, * fractions of the source image's width and height, e.g. CGRectMake(0.5, 0,
* 0.5, 1.0) will feature the full right half a photo. If the cropRect is * 0.5, 1.0) will feature the full right half a photo. If the cropRect is
* empty, the content mode of the receiver will be used to determine its * empty, the content mode of the receiver will be used to determine its
* dimensions, and only the cropRect's origin will be used for positioning. The * dimensions, and only the cropRect's origin will be used for positioning. The

View File

@ -10,6 +10,8 @@
#import "ASImageNode.h" #import "ASImageNode.h"
#import <tgmath.h>
#import "_ASDisplayLayer.h" #import "_ASDisplayLayer.h"
#import "ASAssert.h" #import "ASAssert.h"
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
@ -52,9 +54,9 @@ struct ASImageNodeDrawParameters {
@property CGRect imageDrawRect; @property CGRect imageDrawRect;
@property BOOL isOpaque; @property BOOL isOpaque;
@property (nonatomic, strong) UIColor *backgroundColor; @property (nonatomic, strong) UIColor *backgroundColor;
@property ASDisplayNodeContextModifier preContextBlock; @property (nonatomic, copy) ASDisplayNodeContextModifier preContextBlock;
@property ASDisplayNodeContextModifier postContextBlock; @property (nonatomic, copy) ASDisplayNodeContextModifier postContextBlock;
@property asimagenode_modification_block_t imageModificationBlock; @property (nonatomic, copy) asimagenode_modification_block_t imageModificationBlock;
@end @end
@ -317,7 +319,7 @@ struct ASImageNodeDrawParameters {
CGSize imageSize = image.size; CGSize imageSize = image.size;
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale)); CGSize boundsSizeInPixels = CGSizeMake(std::floor(bounds.size.width * contentsScale), std::floor(bounds.size.height * contentsScale));
if (_debugLabelNode) { if (_debugLabelNode) {
CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height); CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height);

View File

@ -70,8 +70,14 @@ typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions)
*/ */
@property (nonatomic, assign) ASMapNodeShowAnnotationsOptions showAnnotationsOptions; @property (nonatomic, assign) ASMapNodeShowAnnotationsOptions showAnnotationsOptions;
/**
* @abstract The block which should return annotation image for static map based on provided annotation.
* @discussion This block is executed on an arbitrary serial queue. If this block is nil, standard pin is used.
*/
@property (nonatomic, copy, nullable) UIImage * _Nullable (^imageForStaticMapAnnotationBlock)(id<MKAnnotation> annotation, CGPoint *centerOffset);
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
#endif #endif

View File

@ -10,6 +10,9 @@
#if TARGET_OS_IOS #if TARGET_OS_IOS
#import "ASMapNode.h" #import "ASMapNode.h"
#import <tgmath.h>
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeExtras.h"
@ -164,6 +167,14 @@
self.options = options; self.options = options;
} }
- (void)setMapDelegate:(id<MKMapViewDelegate>)mapDelegate {
_mapDelegate = mapDelegate;
if (_mapView) {
_mapView.delegate = mapDelegate;
}
}
#pragma mark - Snapshotter #pragma mark - Snapshotter
- (void)takeSnapshot - (void)takeSnapshot
@ -206,15 +217,27 @@
UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
[image drawAtPoint:CGPointZero]; [image drawAtPoint:CGPointZero];
// Get a standard annotation view pin. Future implementations should use a custom annotation image property. UIImage *pinImage;
MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; CGPoint pinCenterOffset = CGPointZero;
UIImage *pinImage = pin.image;
CGSize pinSize = pin.bounds.size; // Get a standard annotation view pin if there is no custom annotation block.
if (!strongSelf.imageForStaticMapAnnotationBlock) {
pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset];
}
for (id<MKAnnotation> annotation in annotations) { for (id<MKAnnotation> annotation in annotations) {
if (strongSelf.imageForStaticMapAnnotationBlock) {
// Get custom annotation image from custom annotation block.
pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset);
if (!pinImage) {
// just for case block returned nil, which can happen
pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset];
}
}
CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; CGPoint point = [snapshot pointForCoordinate:annotation.coordinate];
if (CGRectContainsPoint(finalImageRect, point)) { if (CGRectContainsPoint(finalImageRect, point)) {
CGPoint pinCenterOffset = pin.centerOffset; CGSize pinSize = pinImage.size;
point.x -= pinSize.width / 2.0; point.x -= pinSize.width / 2.0;
point.y -= pinSize.height / 2.0; point.y -= pinSize.height / 2.0;
point.x += pinCenterOffset.x; point.x += pinCenterOffset.x;
@ -232,6 +255,17 @@
}]; }];
} }
+ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset
{
static MKAnnotationView *pin;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""];
});
*centerOffset = pin.centerOffset;
return pin.image;
}
- (void)setUpSnapshotter - (void)setUpSnapshotter
{ {
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options];
@ -320,16 +354,16 @@
CLLocationCoordinate2D bottomRightCoord = CLLocationCoordinate2DMake(90, -180); CLLocationCoordinate2D bottomRightCoord = CLLocationCoordinate2DMake(90, -180);
for (id<MKAnnotation> annotation in annotations) { for (id<MKAnnotation> annotation in annotations) {
topLeftCoord = CLLocationCoordinate2DMake(fmax(topLeftCoord.latitude, annotation.coordinate.latitude), topLeftCoord = CLLocationCoordinate2DMake(std::fmax(topLeftCoord.latitude, annotation.coordinate.latitude),
fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); std::fmin(topLeftCoord.longitude, annotation.coordinate.longitude));
bottomRightCoord = CLLocationCoordinate2DMake(fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), bottomRightCoord = CLLocationCoordinate2DMake(std::fmin(bottomRightCoord.latitude, annotation.coordinate.latitude),
fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); std::fmax(bottomRightCoord.longitude, annotation.coordinate.longitude));
} }
MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5, MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5,
topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5), topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5),
MKCoordinateSpanMake(fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, MKCoordinateSpanMake(std::fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2,
fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); std::fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2));
return region; return region;
} }
@ -400,4 +434,4 @@
} }
} }
@end @end
#endif #endif

View File

@ -16,6 +16,8 @@
@class ASPagerNode; @class ASPagerNode;
@class ASPagerFlowLayout; @class ASPagerFlowLayout;
NS_ASSUME_NONNULL_BEGIN
#define ASPagerNodeDataSource ASPagerDataSource #define ASPagerNodeDataSource ASPagerDataSource
@protocol ASPagerDataSource <NSObject> @protocol ASPagerDataSource <NSObject>
@ -52,19 +54,21 @@
*/ */
- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; - (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index;
/**
* Provides the constrained size range for measuring the node at the index path.
*
* @param pagerNode The sender.
* @param indexPath The index path of the node.
* @returns A constrained size range for layout the node at this index path.
*/
- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
@end @end
@protocol ASPagerDelegate <ASCollectionDelegate> @protocol ASPagerDelegate <ASCollectionDelegate>
@optional
/**
* Provides the constrained size range for measuring the node at the index.
*
* @param pagerNode The sender.
* @param index The index of the node.
* @returns A constrained size range for layout the node at this index.
*/
- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndex:(NSInteger)index;
@end @end
@interface ASPagerNode : ASCollectionNode @interface ASPagerNode : ASCollectionNode
@ -82,14 +86,15 @@
/** /**
* Data Source is required, and uses a different protocol from ASCollectionNode. * Data Source is required, and uses a different protocol from ASCollectionNode.
*/ */
- (void)setDataSource:(id <ASPagerDataSource>)dataSource; - (void)setDataSource:(nullable id <ASPagerDataSource>)dataSource;
- (id <ASPagerDataSource>)dataSource; - (nullable id <ASPagerDataSource>)dataSource;
/** /**
* Delegate is optional, and uses the same protocol as ASCollectionNode. * Delegate is optional.
* This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... * This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay...
*/ */
@property (nonatomic, weak) id <ASPagerDelegate> delegate; - (void)setDelegate:(nullable id <ASPagerDelegate>)delegate;
- (nullable id <ASPagerDelegate>)delegate;
/** /**
* The underlying ASCollectionView object. * The underlying ASCollectionView object.
@ -112,3 +117,5 @@
- (ASCellNode *)nodeForPageAtIndex:(NSInteger)index; - (ASCellNode *)nodeForPageAtIndex:(NSInteger)index;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -15,18 +15,23 @@
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASPagerFlowLayout.h" #import "ASPagerFlowLayout.h"
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionViewDelegateFlowLayout, ASDelegateProxyInterceptor> @interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionViewDelegateFlowLayout, ASDelegateProxyInterceptor>
{ {
ASPagerFlowLayout *_flowLayout; ASPagerFlowLayout *_flowLayout;
ASPagerNodeProxy *_proxy;
__weak id <ASPagerNodeDataSource> _pagerDataSource; __weak id <ASPagerDataSource> _pagerDataSource;
ASPagerNodeProxy *_proxyDataSource;
BOOL _pagerDataSourceImplementsNodeBlockAtIndex; BOOL _pagerDataSourceImplementsNodeBlockAtIndex;
BOOL _pagerDataSourceImplementsConstrainedSizeForNode;
__weak id <ASPagerDelegate> _pagerDelegate;
ASPagerNodeProxy *_proxyDelegate;
BOOL _pagerDelegateImplementsConstrainedSizeForNode;
} }
@end @end
@implementation ASPagerNode @implementation ASPagerNode
@dynamic view, delegate, dataSource; @dynamic view, delegate, dataSource;
#pragma mark - Lifecycle #pragma mark - Lifecycle
@ -58,6 +63,8 @@
[super didLoad]; [super didLoad];
ASCollectionView *cv = self.view; ASCollectionView *cv = self.view;
cv.asyncDataSource = (id<ASCollectionViewDataSource>)_proxyDataSource ?: self;
cv.asyncDelegate = (id<ASCollectionViewDelegate>)_proxyDelegate ?: self;
#if TARGET_OS_IOS #if TARGET_OS_IOS
cv.pagingEnabled = YES; cv.pagingEnabled = YES;
cv.scrollsToTop = NO; cv.scrollsToTop = NO;
@ -122,9 +129,10 @@
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{ {
if (_pagerDataSourceImplementsConstrainedSizeForNode) { if (_pagerDelegateImplementsConstrainedSizeForNode) {
return [_pagerDataSource pagerNode:self constrainedSizeForNodeAtIndexPath:indexPath]; return [_pagerDelegate pagerNode:self constrainedSizeForNodeAtIndex:indexPath.item];
} }
return ASSizeRangeMake(CGSizeZero, self.view.bounds.size); return ASSizeRangeMake(CGSizeZero, self.view.bounds.size);
} }
@ -135,26 +143,38 @@
return _pagerDataSource; return _pagerDataSource;
} }
- (void)setDataSource:(id <ASPagerDataSource>)pagerDataSource - (void)setDataSource:(id <ASPagerDataSource>)dataSource
{ {
if (pagerDataSource != _pagerDataSource) { if (dataSource != _pagerDataSource) {
_pagerDataSource = pagerDataSource; _pagerDataSource = dataSource;
_pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; _pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)];
// Data source must implement pagerNode:nodeBlockAtIndex: or pagerNode:nodeAtIndex: // Data source must implement pagerNode:nodeBlockAtIndex: or pagerNode:nodeAtIndex:
ASDisplayNodeAssertTrue(_pagerDataSourceImplementsNodeBlockAtIndex || [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]); ASDisplayNodeAssertTrue(_pagerDataSourceImplementsNodeBlockAtIndex || [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]);
_pagerDataSourceImplementsConstrainedSizeForNode = [_pagerDataSource respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndexPath:)]; _proxyDataSource = dataSource ? [[ASPagerNodeProxy alloc] initWithTarget:dataSource interceptor:self] : nil;
_proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; super.dataSource = (id <ASCollectionDataSource>)_proxyDataSource;
}
}
- (void)setDelegate:(id<ASPagerDelegate>)delegate
{
if (delegate != _pagerDelegate) {
_pagerDelegate = delegate;
super.dataSource = (id <ASCollectionDataSource>)_proxy; _pagerDelegateImplementsConstrainedSizeForNode = [_pagerDelegate respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndex:)];
_proxyDelegate = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil;
super.delegate = (id <ASCollectionDelegate>)_proxyDelegate;
} }
} }
- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy
{ {
[self setDataSource:nil]; [self setDataSource:nil];
[self setDelegate:nil];
} }
@end @end

View File

@ -24,4 +24,4 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -16,6 +16,7 @@
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASCellNode+Internal.h" #import "ASCellNode+Internal.h"
#import "AsyncDisplayKit+Debug.h"
#pragma mark - _ASTablePendingState #pragma mark - _ASTablePendingState
@ -125,6 +126,12 @@
[self.view clearFetchedData]; [self.view clearFetchedData];
} }
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
{
[super interfaceStateDidChange:newState fromState:oldState];
[ASRangeController layoutDebugOverlayIfNeeded];
}
#if ASRangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
- (void)visibleStateDidChange:(BOOL)isVisible - (void)visibleStateDidChange:(BOOL)isVisible
{ {

View File

@ -17,7 +17,6 @@
#import "ASChangeSetDataController.h" #import "ASChangeSetDataController.h"
#import "ASDelegateProxy.h" #import "ASDelegateProxy.h"
#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+Beta.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASLayout.h" #import "ASLayout.h"
@ -937,6 +936,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
return ASInterfaceStateForDisplayNode(self.tableNode, self.window); return ASInterfaceStateForDisplayNode(self.tableNode, self.window);
} }
- (NSString *)nameForRangeControllerDataSource
{
return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]);
}
#pragma mark - ASRangeControllerDelegate #pragma mark - ASRangeControllerDelegate
- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController - (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController
@ -1159,8 +1163,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell - (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell
{ {
CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width;
ASCellNode *node = tableViewCell.node; ASCellNode *node = tableViewCell.node;
if (node == nil) {
return;
}
CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width;
ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout;
// Table view cells should always fill its content view width. // Table view cells should always fill its content view width.

View File

@ -31,6 +31,14 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nullable, nonatomic, copy) NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *_Nullable attributedString); @property (nullable, nonatomic, copy) NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *_Nullable attributedString);
/**
@abstract Text margins for text laid out in the text node.
@discussion defaults to UIEdgeInsetsZero.
This property can be useful for handling text which does not fit within the view by default. An example: like UILabel,
ASTextNode will clip the left and right of the string "judar" if it's rendered in an italicised font.
*/
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -12,6 +12,7 @@
#import "ASTextNode+Beta.h" #import "ASTextNode+Beta.h"
#include <mutex> #include <mutex>
#import <tgmath.h>
#import "_ASDisplayLayer.h" #import "_ASDisplayLayer.h"
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
@ -26,6 +27,8 @@
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASLayout.h" #import "ASLayout.h"
#import "CGRect+ASConvenience.h"
static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15; static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15;
static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1; static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1;
static const CGFloat ASTextNodeHighlightLightOpacity = 0.11; static const CGFloat ASTextNodeHighlightLightOpacity = 0.11;
@ -44,8 +47,11 @@ struct ASTextNodeDrawParameter {
@implementation ASTextNode { @implementation ASTextNode {
CGSize _shadowOffset; CGSize _shadowOffset;
CGColorRef _shadowColor; CGColorRef _shadowColor;
UIColor *_cachedShadowUIColor;
CGFloat _shadowOpacity; CGFloat _shadowOpacity;
CGFloat _shadowRadius; CGFloat _shadowRadius;
UIEdgeInsets _textContainerInset;
NSArray *_exclusionPaths; NSArray *_exclusionPaths;
@ -211,7 +217,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if (_renderer == nil) { if (_renderer == nil) {
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size; CGSize constrainedSize;
if (_constrainedSize.width != -INFINITY) {
constrainedSize = _constrainedSize;
} else {
constrainedSize = bounds.size;
constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right);
constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
}
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes] _renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
constrainedSize:constrainedSize]; constrainedSize:constrainedSize];
} }
@ -232,6 +246,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
.pointSizeScaleFactors = self.pointSizeScaleFactors, .pointSizeScaleFactors = self.pointSizeScaleFactors,
.layoutManagerCreationBlock = self.layoutManagerCreationBlock, .layoutManagerCreationBlock = self.layoutManagerCreationBlock,
.textStorageCreationBlock = self.textStorageCreationBlock, .textStorageCreationBlock = self.textStorageCreationBlock,
.shadowOffset = _shadowOffset,
.shadowColor = _cachedShadowUIColor,
.shadowOpacity = _shadowOpacity,
.shadowRadius = _shadowRadius
}; };
} }
@ -273,6 +291,24 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Layout and Sizing #pragma mark - Layout and Sizing
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
{
ASDN::MutexLocker l(__instanceLock__);
BOOL needsUpdate = !UIEdgeInsetsEqualToEdgeInsets(textContainerInset, _textContainerInset);
if (needsUpdate) {
_textContainerInset = textContainerInset;
[self invalidateCalculatedLayout];
[self setNeedsLayout];
}
}
- (UIEdgeInsets)textContainerInset
{
ASDN::MutexLocker l(__instanceLock__);
return _textContainerInset;
}
- (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize - (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
@ -285,6 +321,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
// a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated. // a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated.
CGSize rendererConstrainedSize = _renderer.constrainedSize; CGSize rendererConstrainedSize = _renderer.constrainedSize;
//inset bounds
boundsSize.width -= _textContainerInset.left + _textContainerInset.right;
boundsSize.height -= _textContainerInset.top + _textContainerInset.bottom;
if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) { if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) {
return NO; return NO;
} else { } else {
@ -315,9 +355,14 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
if (layout != nil) { if (layout != nil) {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if (CGSizeEqualToSize(_constrainedSize, layout.size) == NO) { CGSize layoutSize = layout.size;
_constrainedSize = layout.size; //Apply textContainerInset
_renderer.constrainedSize = layout.size; layoutSize.width -= (_textContainerInset.left + _textContainerInset.right);
layoutSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
if (CGSizeEqualToSize(_constrainedSize, layoutSize) == NO) {
_constrainedSize = layoutSize;
_renderer.constrainedSize = layoutSize;
} }
} }
} }
@ -329,6 +374,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
//remove textContainerInset
constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right);
constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
_constrainedSize = constrainedSize; _constrainedSize = constrainedSize;
// Instead of invalidating the renderer, in case this is a new call with a different constrained size, // Instead of invalidating the renderer, in case this is a new call with a different constrained size,
@ -347,6 +396,11 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
self.descender *= _renderer.currentScaleFactor; self.descender *= _renderer.currentScaleFactor;
} }
} }
//add textContainerInset
size.width += (_textContainerInset.left + _textContainerInset.right);
size.height += (_textContainerInset.top + _textContainerInset.bottom);
return size; return size;
} }
@ -460,6 +514,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
CGContextSaveGState(context); CGContextSaveGState(context);
CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top);
ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds]; ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds];
UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer]; UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer];
CGPoint boundsOrigin = drawParameterBounds.origin; CGPoint boundsOrigin = drawParameterBounds.origin;
@ -519,7 +575,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[renderer enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { [renderer enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) {
CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect)); CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect));
CGFloat currentDistance = sqrtf(powf(point.x - glyphLocation.x, 2.f) + powf(point.y - glyphLocation.y, 2.f)); CGFloat currentDistance = std::sqrt(std::pow(point.x - glyphLocation.x, 2.f) + std::pow(point.y - glyphLocation.y, 2.f));
if (currentDistance >= minimumGlyphDistance) { if (currentDistance >= minimumGlyphDistance) {
// If the distance computed from the touch to the glyph location is // If the distance computed from the touch to the glyph location is
// not the minimum among the located link attributes, we can just skip // not the minimum among the located link attributes, we can just skip
@ -727,6 +783,13 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
for (NSValue *rectValue in highlightRects) { for (NSValue *rectValue in highlightRects) {
UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding; UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding;
CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding); CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding);
// The rects returned from renderer don't have `textContainerInset`,
// as well as they are using the `constrainedSize` for layout,
// so we can simply increase the rect by insets to get the full blown layout.
rendererRect.size.width += _textContainerInset.left + _textContainerInset.right;
rendererRect.size.height += _textContainerInset.top + _textContainerInset.bottom;
CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer]; CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer];
// We set our overlay layer's frame to the bounds of the highlight target layer. // We set our overlay layer's frame to the bounds of the highlight target layer.
@ -849,7 +912,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
// FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set.
// This would completely eliminate the memory and performance cost of the backing store. // This would completely eliminate the memory and performance cost of the backing store.
CGSize size = self.calculatedSize; CGSize size = self.calculatedSize;
if (CGSizeEqualToSize(size, CGSizeZero)) { if ((size.width * size.height) < CGFLOAT_EPSILON) {
return nil; return nil;
} }
@ -1043,7 +1106,11 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
if (shadowColor != NULL) { if (shadowColor != NULL) {
CGColorRetain(shadowColor); CGColorRetain(shadowColor);
} }
if (_shadowColor != NULL) {
CGColorRelease(_shadowColor);
}
_shadowColor = shadowColor; _shadowColor = shadowColor;
_cachedShadowUIColor = [UIColor colorWithCGColor:shadowColor];
[self _invalidateRenderer]; [self _invalidateRenderer];
[self setNeedsDisplay]; [self setNeedsDisplay];
} }

View File

@ -12,7 +12,7 @@
#import <AsyncDisplayKit/ASButtonNode.h> #import <AsyncDisplayKit/ASButtonNode.h>
#import <AsyncDisplayKit/ASNetworkImageNode.h> #import <AsyncDisplayKit/ASNetworkImageNode.h>
@class AVAsset, AVPlayer, AVPlayerItem, AVVideoComposition, AVAudioMix; @class AVAsset, AVPlayer, AVPlayerLayer, AVPlayerItem, AVVideoComposition, AVAudioMix;
@protocol ASVideoNodeDelegate; @protocol ASVideoNodeDelegate;
typedef enum { typedef enum {
@ -51,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nullable, nonatomic, strong, readwrite) AVAudioMix *audioMix; @property (nullable, nonatomic, strong, readwrite) AVAudioMix *audioMix;
@property (nullable, nonatomic, strong, readonly) AVPlayer *player; @property (nullable, nonatomic, strong, readonly) AVPlayer *player;
@property (nullable, nonatomic, strong, readonly) AVPlayerLayer *playerLayer;
@property (nullable, nonatomic, strong, readonly) AVPlayerItem *currentItem; @property (nullable, nonatomic, strong, readonly) AVPlayerItem *currentItem;

View File

@ -37,6 +37,7 @@ static void *ASVideoNodeContext = &ASVideoNodeContext;
static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp"; static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp";
static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty"; static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty";
static NSString * const kStatus = @"status"; static NSString * const kStatus = @"status";
static NSString * const kRate = @"rate";
@interface ASVideoNode () @interface ASVideoNode ()
{ {
@ -208,6 +209,25 @@ static NSString * const kStatus = @"status";
[notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem];
} }
- (void)addPlayerObservers:(AVPlayer *)player
{
if (player == nil) {
return;
}
[player addObserver:self forKeyPath:kRate options:NSKeyValueObservingOptionNew context:ASVideoNodeContext];
}
- (void) removePlayerObservers:(AVPlayer *)player
{
@try {
[player removeObserver:self forKeyPath:kRate context:ASVideoNodeContext];
}
@catch (NSException * __unused exception) {
NSLog(@"Unnecessary KVO removal");
}
}
- (void)layout - (void)layout
{ {
[super layout]; [super layout];
@ -267,6 +287,7 @@ static NSString * const kStatus = @"status";
AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
previewImageGenerator.appliesPreferredTrackTransform = YES; previewImageGenerator.appliesPreferredTrackTransform = YES;
previewImageGenerator.videoComposition = _videoComposition;
[previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]] [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]]
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
@ -291,29 +312,47 @@ static NSString * const kStatus = @"status";
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if (object != _currentPlayerItem) { if (object == _currentPlayerItem) {
return; if ([keyPath isEqualToString:kStatus]) {
} if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) {
if (self.playerState != ASVideoNodePlayerStatePlaying) {
if ([keyPath isEqualToString:kStatus]) { self.playerState = ASVideoNodePlayerStateReadyToPlay;
if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { }
self.playerState = ASVideoNodePlayerStateReadyToPlay; // If we don't yet have a placeholder image update it now that we should have data available for it
// If we don't yet have a placeholder image update it now that we should have data available for it if (self.image == nil && self.URL == nil) {
if (self.image == nil && self.URL == nil) { [self generatePlaceholderImage];
[self generatePlaceholderImage]; }
}
} else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) {
BOOL likelyToKeepUp = [change[NSKeyValueChangeNewKey] boolValue];
if (likelyToKeepUp && self.playerState == ASVideoNodePlayerStatePlaying) {
return;
}
if (!likelyToKeepUp) {
self.playerState = ASVideoNodePlayerStateLoading;
} else if (self.playerState != ASVideoNodePlayerStateFinished) {
self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying;
}
if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) {
if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) {
[_delegate videoNodeDidRecoverFromStall:self];
}
[self play]; // autoresume after buffer catches up
}
} else if ([keyPath isEqualToString:kplaybackBufferEmpty]) {
if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) {
self.playerState = ASVideoNodePlayerStateLoading;
} }
} }
} else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { } else if (object == _player) {
self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; if ([keyPath isEqualToString:kRate]) {
if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || [change[NSKeyValueChangeNewKey] boolValue]) && ASInterfaceStateIncludesVisible(self.interfaceState)) { if ([change[NSKeyValueChangeNewKey] floatValue] == 0.0) {
if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { if (self.playerState == ASVideoNodePlayerStatePlaying) {
[_delegate videoNodeDidRecoverFromStall:self]; self.playerState = ASVideoNodePlayerStatePaused;
}
} else {
self.playerState = ASVideoNodePlayerStatePlaying;
} }
[self play]; // autoresume after buffer catches up
}
} else if ([keyPath isEqualToString:kplaybackBufferEmpty]) {
if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) {
self.playerState = ASVideoNodePlayerStateLoading;
} }
} }
} }
@ -345,14 +384,11 @@ static NSString * const kStatus = @"status";
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
AVAsset *asset = self.asset; AVAsset *asset = self.asset;
// Return immediately if the asset is nil; // Return immediately if the asset is nil;
if (asset == nil || self.playerState == ASVideoNodePlayerStateInitialLoading) { if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) {
return; return;
} }
// FIXME: Nothing appears to prevent this method from sending the delegate notification / calling load on the asset self.playerState = ASVideoNodePlayerStateLoading;
// multiple times, even after the asset is fully loaded and ready to play. There should probably be a playerState
// for NotLoaded or such, besides Unknown, so this can be easily checked before proceeding.
self.playerState = ASVideoNodePlayerStateInitialLoading;
if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) { if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) {
[_delegate videoNodeDidStartInitialLoading:self]; [_delegate videoNodeDidStartInitialLoading:self];
} }
@ -396,6 +432,7 @@ static NSString * const kStatus = @"status";
self.player = nil; self.player = nil;
self.currentItem = nil; self.currentItem = nil;
self.playerState = ASVideoNodePlayerStateUnknown;
} }
} }
@ -514,6 +551,12 @@ static NSString * const kStatus = @"status";
return _player; return _player;
} }
- (AVPlayerLayer *)playerLayer
{
ASDN::MutexLocker l(__instanceLock__);
return (AVPlayerLayer *)_playerNode.layer;
}
- (id<ASVideoNodeDelegate>)delegate{ - (id<ASVideoNodeDelegate>)delegate{
return _delegate; return _delegate;
} }
@ -603,12 +646,6 @@ static NSString * const kStatus = @"status";
[_player play]; [_player play];
_shouldBePlaying = YES; _shouldBePlaying = YES;
if (![self ready]) {
self.playerState = ASVideoNodePlayerStateLoading;
} else {
self.playerState = ASVideoNodePlayerStatePlaying;
}
} }
- (BOOL)ready - (BOOL)ready
@ -622,7 +659,6 @@ static NSString * const kStatus = @"status";
if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) { if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) {
return; return;
} }
self.playerState = ASVideoNodePlayerStatePaused;
[_player pause]; [_player pause];
_shouldBePlaying = NO; _shouldBePlaying = NO;
} }
@ -731,9 +767,16 @@ static NSString * const kStatus = @"status";
- (void)setPlayer:(AVPlayer *)player - (void)setPlayer:(AVPlayer *)player
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
[self removePlayerObservers:_player];
_player = player; _player = player;
player.muted = _muted; player.muted = _muted;
((AVPlayerLayer *)_playerNode.layer).player = player; ((AVPlayerLayer *)_playerNode.layer).player = player;
if (player != nil) {
[self addPlayerObservers:player];
}
} }
- (BOOL)shouldBePlaying - (BOOL)shouldBePlaying
@ -755,6 +798,7 @@ static NSString * const kStatus = @"status";
[_player removeTimeObserver:_timeObserver]; [_player removeTimeObserver:_timeObserver];
_timeObserver = nil; _timeObserver = nil;
[self removePlayerItemObservers:_currentPlayerItem]; [self removePlayerItemObservers:_currentPlayerItem];
[self removePlayerObservers:_player];
} }
@end @end

View File

@ -78,7 +78,7 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil AS_UNAVAILABLE("ASViewController requires using -initWithNode:");
- (instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:");
@end @end

View File

@ -313,12 +313,13 @@ ASVisibilityDepthImplementation;
{ {
[super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator]; [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
// here we take the new UITraitCollection and use it to create a new ASEnvironmentTraitCollection on self.node ASEnvironmentTraitCollection environmentTraitCollection = ASEnvironmentTraitCollectionFromUITraitCollection(newCollection);
// We will propagate when the corresponding viewWillTransitionToSize:withTransitionCoordinator: is called and we have the // A call to willTransitionToTraitCollection:withTransitionCoordinator: is always followed by a call to viewWillTransitionToSize:withTransitionCoordinator:.
// new windowSize. There are cases when viewWillTransitionToSize: is called when willTransitionToTraitCollection: is not. // However, it is not immediate and we may need the new trait collection before viewWillTransitionToSize:withTransitionCoordinator: is called. Our view already has the
// Since we do the propagation on viewWillTransitionToSize: our subnodes should always get the proper trait collection. // new bounds, so go ahead and set the containerSize and propagate the traits here. As long as nothing else in the traits changes (which it shouldn't)
ASEnvironmentTraitCollection asyncTraitCollection = ASEnvironmentTraitCollectionFromUITraitCollection(newCollection); // then we won't we will skip the propagation in viewWillTransitionToSize:withTransitionCoordinator:.
self.node.environmentTraitCollection = asyncTraitCollection; environmentTraitCollection.containerSize = self.view.bounds.size;
[self progagateNewEnvironmentTraitCollection:environmentTraitCollection];
} }
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator

View File

@ -22,4 +22,4 @@ ASLayoutRangeMode ASLayoutRangeModeForVisibilityDepth(NSUInteger visibilityDepth
return ASLayoutRangeModeVisibleOnly; return ASLayoutRangeModeVisibleOnly;
} }
return ASLayoutRangeModeLowMemory; return ASLayoutRangeModeLowMemory;
} }

View File

@ -12,6 +12,7 @@
#import "ASControlNode.h" #import "ASControlNode.h"
#import "ASImageNode.h" #import "ASImageNode.h"
#import "ASRangeController.h"
@interface ASImageNode (Debugging) @interface ASImageNode (Debugging)
@ -29,16 +30,40 @@
@interface ASControlNode (Debugging) @interface ASControlNode (Debugging)
/** /**
Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only. * Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only.
NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!! * NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!!
Overlay = translucent GREEN color, * Overlay = translucent GREEN color,
edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE, * edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE,
edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond * edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond
overlay rect, but can't be visualized). * overlay rect, but can't be visualized).
@param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class. * @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class.
*/ */
+ (void)setEnableHitTestDebug:(BOOL)enable; + (void)setEnableHitTestDebug:(BOOL)enable;
+ (BOOL)enableHitTestDebug; + (BOOL)enableHitTestDebug;
@end @end
@interface ASRangeController (Debugging)
/**
* Class method to enable a visualization overlay of the all ASRangeController's tuning parameters. For dev purposes only.
* To use, message ASRangeController in the AppDelegate --> [ASRangeController setShouldShowRangeDebugOverlay:YES];
* @param enable Specify YES to make this debug feature enabled when messaging the ASRangeController class.
*/
+ (void)setShouldShowRangeDebugOverlay:(BOOL)show;
+ (BOOL)shouldShowRangeDebugOverlay;
+ (void)layoutDebugOverlayIfNeeded;
- (void)addRangeControllerToRangeDebugOverlay;
- (void)updateRangeController:(ASRangeController *)controller
withScrollableDirections:(ASScrollDirection)scrollableDirections
scrollDirection:(ASScrollDirection)direction
rangeMode:(ASLayoutRangeMode)mode
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
interfaceState:(ASInterfaceState)interfaceState;
@end

View File

@ -13,6 +13,16 @@
#import "AsyncDisplayKit+Debug.h" #import "AsyncDisplayKit+Debug.h"
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeExtras.h"
#import "ASWeakSet.h"
#import "UIImage+ASConvenience.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/CGRect+ASConvenience.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASRangeController.h>
#pragma mark - ASImageNode (Debugging)
static BOOL __shouldShowImageScalingOverlay = NO; static BOOL __shouldShowImageScalingOverlay = NO;
@ -30,6 +40,8 @@ static BOOL __shouldShowImageScalingOverlay = NO;
@end @end
#pragma mark - ASControlNode (DebuggingInternal)
static BOOL __enableHitTestDebug = NO; static BOOL __enableHitTestDebug = NO;
@interface ASControlNode (DebuggingInternal) @interface ASControlNode (DebuggingInternal)
@ -191,3 +203,551 @@ static BOOL __enableHitTestDebug = NO;
} }
@end @end
#pragma mark - ASRangeController (Debugging)
@interface _ASRangeDebugOverlayView : UIView
+ (instancetype)sharedInstance;
- (void)addRangeController:(ASRangeController *)rangeController;
- (void)updateRangeController:(ASRangeController *)controller
withScrollableDirections:(ASScrollDirection)scrollableDirections
scrollDirection:(ASScrollDirection)direction
rangeMode:(ASLayoutRangeMode)mode
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
interfaceState:(ASInterfaceState)interfaceState;
@end
@interface _ASRangeDebugBarView : UIView
@property (nonatomic, weak) ASRangeController *rangeController;
@property (nonatomic, assign) BOOL destroyOnLayout;
@property (nonatomic, strong) NSString *debugString;
- (instancetype)initWithRangeController:(ASRangeController *)rangeController;
- (void)updateWithVisibleRatio:(CGFloat)visibleRatio
displayRatio:(CGFloat)displayRatio
leadingDisplayRatio:(CGFloat)leadingDisplayRatio
fetchDataRatio:(CGFloat)fetchDataRatio
leadingFetchDataRatio:(CGFloat)leadingFetchDataRatio
direction:(ASScrollDirection)direction;
@end
static BOOL __shouldShowRangeDebugOverlay = NO;
@implementation ASRangeController (Debugging)
+ (void)setShouldShowRangeDebugOverlay:(BOOL)show
{
__shouldShowRangeDebugOverlay = show;
}
+ (BOOL)shouldShowRangeDebugOverlay
{
return __shouldShowRangeDebugOverlay;
}
+ (void)layoutDebugOverlayIfNeeded
{
[[_ASRangeDebugOverlayView sharedInstance] setNeedsLayout];
}
- (void)addRangeControllerToRangeDebugOverlay
{
[[_ASRangeDebugOverlayView sharedInstance] addRangeController:self];
}
- (void)updateRangeController:(ASRangeController *)controller
withScrollableDirections:(ASScrollDirection)scrollableDirections
scrollDirection:(ASScrollDirection)direction
rangeMode:(ASLayoutRangeMode)mode
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
interfaceState:(ASInterfaceState)interfaceState
{
[[_ASRangeDebugOverlayView sharedInstance] updateRangeController:controller
withScrollableDirections:scrollableDirections
scrollDirection:direction
rangeMode:mode
displayTuningParameters:displayTuningParameters
fetchDataTuningParameters:fetchDataTuningParameters
interfaceState:interfaceState];
}
@end
#pragma mark _ASRangeDebugOverlayView
@interface _ASRangeDebugOverlayView () <UIGestureRecognizerDelegate>
@end
@implementation _ASRangeDebugOverlayView
{
NSMutableArray *_rangeControllerViews;
NSInteger _newControllerCount;
NSInteger _removeControllerCount;
BOOL _animating;
}
+ (UIWindow *)keyWindow
{
// hack to work around app extensions not having UIApplication...not sure of a better way to do this?
return [[NSClassFromString(@"UIApplication") sharedApplication] keyWindow];
}
+ (instancetype)sharedInstance
{
static _ASRangeDebugOverlayView *__rangeDebugOverlay = nil;
if (!__rangeDebugOverlay && [ASRangeController shouldShowRangeDebugOverlay]) {
__rangeDebugOverlay = [[self alloc] initWithFrame:CGRectZero];
[[self keyWindow] addSubview:__rangeDebugOverlay];
}
return __rangeDebugOverlay;
}
#define OVERLAY_INSET 10
#define OVERLAY_SCALE 3
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_rangeControllerViews = [[NSMutableArray alloc] init];
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
self.layer.zPosition = 1000;
self.clipsToBounds = YES;
CGSize windowSize = [[[self class] keyWindow] bounds].size;
self.frame = CGRectMake(windowSize.width - (windowSize.width / OVERLAY_SCALE) - OVERLAY_INSET, windowSize.height - OVERLAY_INSET,
windowSize.width / OVERLAY_SCALE, 0.0);
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(rangeDebugOverlayWasPanned:)];
[self addGestureRecognizer:panGR];
}
return self;
}
#define BAR_THICKNESS 24
- (void)layoutSubviews
{
[super layoutSubviews];
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self layoutToFitAllBarsExcept:0];
} completion:^(BOOL finished) {
}];
}
- (void)layoutToFitAllBarsExcept:(NSInteger)barsToClip
{
CGSize boundsSize = self.bounds.size;
CGFloat totalHeight = 0.0;
CGRect barRect = CGRectMake(0, boundsSize.height - BAR_THICKNESS, self.bounds.size.width, BAR_THICKNESS);
NSMutableArray *displayedBars = [NSMutableArray array];
for (_ASRangeDebugBarView *barView in [_rangeControllerViews copy]) {
barView.frame = barRect;
ASInterfaceState interfaceState = [barView.rangeController.dataSource interfaceStateForRangeController:barView.rangeController];
if (!(interfaceState & (ASInterfaceStateVisible))) {
if (barView.destroyOnLayout && barView.alpha == 0.0) {
[_rangeControllerViews removeObjectIdenticalTo:barView];
[barView removeFromSuperview];
} else {
barView.alpha = 0.0;
}
} else {
assert(!barView.destroyOnLayout); // In this case we should not have a visible interfaceState
barView.alpha = 1.0;
totalHeight += BAR_THICKNESS;
barRect.origin.y -= BAR_THICKNESS;
[displayedBars addObject:barView];
}
}
if (totalHeight > 0) {
totalHeight -= (BAR_THICKNESS * barsToClip);
}
if (barsToClip == 0) {
CGRect overlayFrame = self.frame;
CGFloat heightChange = (overlayFrame.size.height - totalHeight);
overlayFrame.origin.y += heightChange;
overlayFrame.size.height = totalHeight;
self.frame = overlayFrame;
for (_ASRangeDebugBarView *barView in displayedBars) {
[self offsetYOrigin:-heightChange forView:barView];
}
}
}
- (void)setOrigin:(CGPoint)origin forView:(UIView *)view
{
CGRect newFrame = view.frame;
newFrame.origin = origin;
view.frame = newFrame;
}
- (void)offsetYOrigin:(CGFloat)offset forView:(UIView *)view
{
CGRect newFrame = view.frame;
newFrame.origin = CGPointMake(newFrame.origin.x, newFrame.origin.y + offset);
view.frame = newFrame;
}
- (void)addRangeController:(ASRangeController *)rangeController
{
for (_ASRangeDebugBarView *rangeView in _rangeControllerViews) {
if (rangeView.rangeController == rangeController) {
return;
}
}
_ASRangeDebugBarView *rangeView = [[_ASRangeDebugBarView alloc] initWithRangeController:rangeController];
[_rangeControllerViews addObject:rangeView];
[self addSubview:rangeView];
if (!_animating) {
[self layoutToFitAllBarsExcept:1];
}
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
_animating = YES;
[self layoutToFitAllBarsExcept:0];
} completion:^(BOOL finished) {
_animating = NO;
}];
}
- (void)updateRangeController:(ASRangeController *)controller
withScrollableDirections:(ASScrollDirection)scrollableDirections
scrollDirection:(ASScrollDirection)scrollDirection
rangeMode:(ASLayoutRangeMode)rangeMode
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
interfaceState:(ASInterfaceState)interfaceState;
{
_ASRangeDebugBarView *viewToUpdate = [self barViewForRangeController:controller];
CGRect boundsRect = self.bounds;
CGRect visibleRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, ASRangeTuningParametersZero, scrollableDirections, scrollDirection);
CGRect displayRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, displayTuningParameters, scrollableDirections, scrollDirection);
CGRect fetchDataRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, fetchDataTuningParameters, scrollableDirections, scrollDirection);
// figure out which is biggest and assume that is full bounds
BOOL displayRangeLargerThanFetch = NO;
CGFloat visibleRatio = 0;
CGFloat displayRatio = 0;
CGFloat fetchDataRatio = 0;
CGFloat leadingDisplayTuningRatio = 0;
CGFloat leadingFetchDataTuningRatio = 0;
if (!((displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls) == 0)) {
leadingDisplayTuningRatio = displayTuningParameters.leadingBufferScreenfuls / (displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls);
}
if (!((fetchDataTuningParameters.leadingBufferScreenfuls + fetchDataTuningParameters.trailingBufferScreenfuls) == 0)) {
leadingFetchDataTuningRatio = fetchDataTuningParameters.leadingBufferScreenfuls / (fetchDataTuningParameters.leadingBufferScreenfuls + fetchDataTuningParameters.trailingBufferScreenfuls);
}
if (ASScrollDirectionContainsVerticalDirection(scrollDirection)) {
if (displayRect.size.height >= fetchDataRect.size.height) {
displayRangeLargerThanFetch = YES;
} else {
displayRangeLargerThanFetch = NO;
}
if (displayRangeLargerThanFetch) {
visibleRatio = visibleRect.size.height / displayRect.size.height;
displayRatio = 1.0;
fetchDataRatio = fetchDataRect.size.height / displayRect.size.height;
} else {
visibleRatio = visibleRect.size.height / fetchDataRect.size.height;
displayRatio = displayRect.size.height / fetchDataRect.size.height;
fetchDataRatio = 1.0;
}
} else {
if (displayRect.size.width >= fetchDataRect.size.width) {
displayRangeLargerThanFetch = YES;
} else {
displayRangeLargerThanFetch = NO;
}
if (displayRangeLargerThanFetch) {
visibleRatio = visibleRect.size.width / displayRect.size.width;
displayRatio = 1.0;
fetchDataRatio = fetchDataRect.size.width / displayRect.size.width;
} else {
visibleRatio = visibleRect.size.width / fetchDataRect.size.width;
displayRatio = displayRect.size.width / fetchDataRect.size.width;
fetchDataRatio = 1.0;
}
}
[viewToUpdate updateWithVisibleRatio:visibleRatio
displayRatio:displayRatio
leadingDisplayRatio:leadingDisplayTuningRatio
fetchDataRatio:fetchDataRatio
leadingFetchDataRatio:leadingFetchDataTuningRatio
direction:scrollDirection];
[self setNeedsLayout];
}
- (_ASRangeDebugBarView *)barViewForRangeController:(ASRangeController *)controller
{
_ASRangeDebugBarView *rangeControllerBarView = nil;
for (_ASRangeDebugBarView *rangeView in [[_rangeControllerViews reverseObjectEnumerator] allObjects]) {
// remove barView if its rangeController has been deleted
if (!rangeView.rangeController) {
rangeView.destroyOnLayout = YES;
[self setNeedsLayout];
}
ASInterfaceState interfaceState = [rangeView.rangeController.dataSource interfaceStateForRangeController:rangeView.rangeController];
if (!(interfaceState & (ASInterfaceStateVisible | ASInterfaceStateDisplay))) {
[self setNeedsLayout];
}
if ([rangeView.rangeController isEqual:controller]) {
rangeControllerBarView = rangeView;
}
}
return rangeControllerBarView;
}
#define MIN_VISIBLE_INSET 40
- (void)rangeDebugOverlayWasPanned:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view];
CGFloat newCenterX = recognizer.view.center.x + translation.x;
CGFloat newCenterY = recognizer.view.center.y + translation.y;
CGSize boundsSize = recognizer.view.bounds.size;
CGSize superBoundsSize = recognizer.view.superview.bounds.size;
CGFloat minAllowableX = -boundsSize.width / 2.0 + MIN_VISIBLE_INSET;
CGFloat maxAllowableX = superBoundsSize.width + boundsSize.width / 2.0 - MIN_VISIBLE_INSET;
if (newCenterX > maxAllowableX) {
newCenterX = maxAllowableX;
} else if (newCenterX < minAllowableX) {
newCenterX = minAllowableX;
}
CGFloat minAllowableY = -boundsSize.height / 2.0 + MIN_VISIBLE_INSET;
CGFloat maxAllowableY = superBoundsSize.height + boundsSize.height / 2.0 - MIN_VISIBLE_INSET;
if (newCenterY > maxAllowableY) {
newCenterY = maxAllowableY;
} else if (newCenterY < minAllowableY) {
newCenterY = minAllowableY;
}
recognizer.view.center = CGPointMake(newCenterX, newCenterY);
[recognizer setTranslation:CGPointMake(0, 0) inView:recognizer.view];
}
@end
#pragma mark _ASRangeDebugBarView
@implementation _ASRangeDebugBarView
{
ASTextNode *_debugText;
ASTextNode *_leftDebugText;
ASTextNode *_rightDebugText;
ASImageNode *_visibleRect;
ASImageNode *_displayRect;
ASImageNode *_fetchDataRect;
CGFloat _visibleRatio;
CGFloat _displayRatio;
CGFloat _fetchDataRatio;
CGFloat _leadingDisplayRatio;
CGFloat _leadingFetchDataRatio;
ASScrollDirection _scrollDirection;
BOOL _firstLayoutOfRects;
}
- (instancetype)initWithRangeController:(ASRangeController *)rangeController
{
self = [super initWithFrame:CGRectZero];
if (self) {
_firstLayoutOfRects = YES;
_rangeController = rangeController;
_debugText = [self createDebugTextNode];
_leftDebugText = [self createDebugTextNode];
_rightDebugText = [self createDebugTextNode];
_fetchDataRect = [self createRangeNodeWithColor:[UIColor orangeColor]];
_displayRect = [self createRangeNodeWithColor:[UIColor yellowColor]];
_visibleRect = [self createRangeNodeWithColor:[UIColor greenColor]];
}
return self;
}
#define HORIZONTAL_INSET 10
- (void)layoutSubviews
{
[super layoutSubviews];
CGSize boundsSize = self.bounds.size;
CGFloat subCellHeight = 9.0;
[self setBarDebugLabelsWithSize:subCellHeight];
[self setBarSubviewOrder];
CGRect rect = CGRectIntegral(CGRectMake(0, 0, boundsSize.width, floorf(boundsSize.height / 2.0)));
rect.size = [_debugText measure:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
rect.origin.x = (boundsSize.width - rect.size.width) / 2.0;
_debugText.frame = rect;
rect.origin.y += rect.size.height;
rect.origin.x = 0;
rect.size = CGSizeMake(HORIZONTAL_INSET, boundsSize.height / 2.0);
_leftDebugText.frame = rect;
rect.origin.x = boundsSize.width - HORIZONTAL_INSET;
_rightDebugText.frame = rect;
CGFloat visibleDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _visibleRatio;
CGFloat displayDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _displayRatio;
CGFloat fetchDataDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _fetchDataRatio;
CGFloat visiblePoint = 0;
CGFloat displayPoint = 0;
CGFloat fetchDataPoint = 0;
BOOL displayLargerThanFetchData = (_displayRatio == 1.0) ? YES : NO;
if (ASScrollDirectionContainsLeft(_scrollDirection) || ASScrollDirectionContainsUp(_scrollDirection)) {
if (displayLargerThanFetchData) {
visiblePoint = (displayDimension - visibleDimension) * _leadingDisplayRatio;
fetchDataPoint = visiblePoint - (fetchDataDimension - visibleDimension) * _leadingFetchDataRatio;
} else {
visiblePoint = (fetchDataDimension - visibleDimension) * _leadingFetchDataRatio;
displayPoint = visiblePoint - (displayDimension - visibleDimension) * _leadingDisplayRatio;
}
} else if (ASScrollDirectionContainsRight(_scrollDirection) || ASScrollDirectionContainsDown(_scrollDirection)) {
if (displayLargerThanFetchData) {
visiblePoint = (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio);
fetchDataPoint = visiblePoint - (fetchDataDimension - visibleDimension) * (1 - _leadingFetchDataRatio);
} else {
visiblePoint = (fetchDataDimension - visibleDimension) * (1 - _leadingFetchDataRatio);
displayPoint = visiblePoint - (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio);
}
}
BOOL animate = !_firstLayoutOfRects;
[UIView animateWithDuration:animate ? 0.3 : 0.0 delay:0.0 options:UIViewAnimationOptionLayoutSubviews animations:^{
_visibleRect.frame = CGRectMake(HORIZONTAL_INSET + visiblePoint, rect.origin.y, visibleDimension, subCellHeight);
_displayRect.frame = CGRectMake(HORIZONTAL_INSET + displayPoint, rect.origin.y, displayDimension, subCellHeight);
_fetchDataRect.frame = CGRectMake(HORIZONTAL_INSET + fetchDataPoint, rect.origin.y, fetchDataDimension, subCellHeight);
} completion:^(BOOL finished) {}];
if (!animate) {
_visibleRect.alpha = _displayRect.alpha = _fetchDataRect.alpha = 0;
[UIView animateWithDuration:0.3 animations:^{
_visibleRect.alpha = _displayRect.alpha = _fetchDataRect.alpha = 1;
}];
}
_firstLayoutOfRects = NO;
}
- (void)updateWithVisibleRatio:(CGFloat)visibleRatio
displayRatio:(CGFloat)displayRatio
leadingDisplayRatio:(CGFloat)leadingDisplayRatio
fetchDataRatio:(CGFloat)fetchDataRatio
leadingFetchDataRatio:(CGFloat)leadingFetchDataRatio
direction:(ASScrollDirection)scrollDirection
{
_visibleRatio = visibleRatio;
_displayRatio = displayRatio;
_leadingDisplayRatio = leadingDisplayRatio;
_fetchDataRatio = fetchDataRatio;
_leadingFetchDataRatio = leadingFetchDataRatio;
_scrollDirection = scrollDirection;
[self setNeedsLayout];
}
- (void)setBarSubviewOrder
{
if (_fetchDataRatio == 1.0) {
[self sendSubviewToBack:_fetchDataRect.view];
} else {
[self sendSubviewToBack:_displayRect.view];
}
[self bringSubviewToFront:_visibleRect.view];
}
- (void)setBarDebugLabelsWithSize:(CGFloat)size
{
if (!_debugString) {
_debugString = [[_rangeController dataSource] nameForRangeControllerDataSource];
}
if (_debugString) {
_debugText.attributedString = [_ASRangeDebugBarView whiteAttributedStringFromString:_debugString withSize:size];
}
if (ASScrollDirectionContainsVerticalDirection(_scrollDirection)) {
_leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▲" withSize:size];
_rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▼" withSize:size];
} else if (ASScrollDirectionContainsHorizontalDirection(_scrollDirection)) {
_leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"◀︎" withSize:size];
_rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▶︎" withSize:size];
}
_leftDebugText.hidden = (_scrollDirection != ASScrollDirectionLeft && _scrollDirection != ASScrollDirectionUp);
_rightDebugText.hidden = (_scrollDirection != ASScrollDirectionRight && _scrollDirection != ASScrollDirectionDown);
}
- (ASTextNode *)createDebugTextNode
{
ASTextNode *label = [[ASTextNode alloc] init];
[self addSubnode:label];
return label;
}
#define RANGE_BAR_CORNER_RADIUS 3
#define RANGE_BAR_BORDER_WIDTH 1
- (ASImageNode *)createRangeNodeWithColor:(UIColor *)color
{
ASImageNode *rangeBarImageNode = [[ASImageNode alloc] init];
rangeBarImageNode.image = [UIImage as_resizableRoundedImageWithCornerRadius:RANGE_BAR_CORNER_RADIUS
cornerColor:[UIColor clearColor]
fillColor:[color colorWithAlphaComponent:0.5]
borderColor:[[UIColor blackColor] colorWithAlphaComponent:0.9]
borderWidth:RANGE_BAR_BORDER_WIDTH
roundedCorners:UIRectCornerAllCorners
scale:[[UIScreen mainScreen] scale]];
[self addSubnode:rangeBarImageNode];
return rangeBarImageNode;
}
+ (NSAttributedString *)whiteAttributedStringFromString:(NSString *)string withSize:(CGFloat)size
{
NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor],
NSFontAttributeName : [UIFont systemFontOfSize:size]};
return [[NSAttributedString alloc] initWithString:string attributes:attributes];
}
@end

View File

@ -16,4 +16,4 @@
// search path e.g. they've dragged in the framework (technically this will not be able to detect if // search path e.g. they've dragged in the framework (technically this will not be able to detect if
// a user does not include the framework in the link binary with build step). // a user does not include the framework in the link binary with build step).
#define PIN_REMOTE_IMAGE __has_include(<PINRemoteImage/PINRemoteImage.h>) #define PIN_REMOTE_IMAGE __has_include(<PINRemoteImage/PINRemoteImage.h>)
#endif #endif

View File

@ -84,6 +84,18 @@
return batchUpdating; return batchUpdating;
} }
- (void)waitUntilAllUpdatesAreCommitted
{
ASDisplayNodeAssertMainThread();
if (self.batchUpdating) {
// This assertion will be enabled soon.
// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd));
return;
}
[super waitUntilAllUpdatesAreCommitted];
}
#pragma mark - Section Editing (External API) #pragma mark - Section Editing (External API)
- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions

View File

@ -26,8 +26,6 @@
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController; - (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController;
- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
@optional @optional
@ -44,4 +42,4 @@
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
@end @end

View File

@ -43,17 +43,18 @@
return self; return self;
} }
- (void)prepareForReloadData - (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount
{ {
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)];
for (NSString *kind in [self supplementaryKinds]) { for (NSString *kind in [self supplementaryKinds]) {
LOG(@"Populating elements of kind: %@", kind); LOG(@"Populating elements of kind: %@", kind);
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array]; NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind withMutableContexts:contexts]; [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts];
_pendingContexts[kind] = contexts; _pendingContexts[kind] = contexts;
} }
} }
- (void)willReloadData - (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount
{ {
[_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, __unused BOOL * _Nonnull stop) { [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, __unused BOOL * _Nonnull stop) {
// Remove everything that existed before the reload, now that we're ready to insert replacements // Remove everything that existed before the reload, now that we're ready to insert replacements
@ -65,12 +66,11 @@
[self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil];
// Insert each section // Insert each section
NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount];
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < newSectionCount; i++) {
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[NSMutableArray array]]; [sections addObject:[NSMutableArray array]];
} }
[self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)] completion:nil];
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) { [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
@ -188,22 +188,6 @@
[_pendingContexts removeAllObjects]; [_pendingContexts removeAllObjects];
} }
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
{
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
id<ASCollectionDataControllerSource> source = self.collectionDataSource;
NSUInteger sectionCount = [source dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind];
for (NSUInteger i = 0; i < sectionCount; i++) {
NSUInteger rowCount = [source dataController:self supplementaryNodesOfKind:kind inSection:i];
for (NSUInteger j = 0; j < rowCount; j++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
[self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection];
}
}
}
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
{ {
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment]; id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];

View File

@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ASCollectionViewLayoutInspecting <NSObject> @protocol ASCollectionViewLayoutInspecting <NSObject>
/** /**
* Asks the inspector to provide a constarained size range for the given collection view node. * Asks the inspector to provide a constrained size range for the given collection view node.
*/ */
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
@ -33,11 +33,6 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
* Asks the inspector for the number of supplementary sections in the collection view for the given kind.
*/
- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
/** /**
* Asks the inspector for the number of supplementary views for the given kind in the specified section. * Asks the inspector for the number of supplementary views for the given kind in the specified section.
*/ */
@ -57,6 +52,16 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)didChangeCollectionViewDataSource:(nullable id<ASCollectionDataSource>)dataSource; - (void)didChangeCollectionViewDataSource:(nullable id<ASCollectionDataSource>)dataSource;
#pragma mark Deprecated Methods
/**
* Asks the inspector for the number of supplementary sections in the collection view for the given kind.
*
* @deprecated This method will not be called, and it is only deprecated as a reminder to remove it.
* Supplementary elements must exist in the same sections as regular collection view items i.e. -numberOfSectionsInCollectionView:
*/
- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind ASDISPLAYNODE_DEPRECATED;
@end @end
/** /**

View File

@ -34,7 +34,7 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView
@implementation ASCollectionViewLayoutInspector { @implementation ASCollectionViewLayoutInspector {
struct { struct {
unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; unsigned int implementsConstrainedSizeForNodeAtIndexPath:1;
} _dataSourceFlags; } _delegateFlags;
} }
#pragma mark Lifecycle #pragma mark Lifecycle
@ -43,26 +43,26 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView
{ {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
[self didChangeCollectionViewDataSource:collectionView.asyncDataSource]; [self didChangeCollectionViewDelegate:collectionView.asyncDelegate];
} }
return self; return self;
} }
#pragma mark ASCollectionViewLayoutInspecting #pragma mark ASCollectionViewLayoutInspecting
- (void)didChangeCollectionViewDataSource:(id<ASCollectionDataSource>)dataSource - (void)didChangeCollectionViewDelegate:(id<ASCollectionDelegate>)delegate
{ {
if (dataSource == nil) { if (delegate == nil) {
memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags)); memset(&_delegateFlags, 0, sizeof(_delegateFlags));
} else { } else {
_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath = [dataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; _delegateFlags.implementsConstrainedSizeForNodeAtIndexPath = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
} }
} }
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{ {
if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath) {
return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath];
} }
return NodeConstrainedSizeForScrollDirection(collectionView); return NodeConstrainedSizeForScrollDirection(collectionView);
@ -74,12 +74,6 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView
return ASSizeRangeMake(CGSizeZero, CGSizeZero); return ASSizeRangeMake(CGSizeZero, CGSizeZero);
} }
- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind
{
ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return 0;
}
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section - (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
{ {
ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
@ -99,10 +93,10 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView
struct { struct {
unsigned int implementsReferenceSizeForHeader:1; unsigned int implementsReferenceSizeForHeader:1;
unsigned int implementsReferenceSizeForFooter:1; unsigned int implementsReferenceSizeForFooter:1;
unsigned int implementsConstrainedSizeForNodeAtIndexPath:1;
} _delegateFlags; } _delegateFlags;
struct { struct {
unsigned int implementsConstrainedSizeForNodeAtIndexPath:1;
unsigned int implementsNumberOfSectionsInCollectionView:1; unsigned int implementsNumberOfSectionsInCollectionView:1;
} _dataSourceFlags; } _dataSourceFlags;
} }
@ -132,6 +126,7 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView
} else { } else {
_delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; _delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)];
_delegateFlags.implementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; _delegateFlags.implementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)];
_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
} }
} }
@ -140,15 +135,14 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView
if (dataSource == nil) { if (dataSource == nil) {
memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags)); memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags));
} else { } else {
_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath = [dataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
_dataSourceFlags.implementsNumberOfSectionsInCollectionView = [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; _dataSourceFlags.implementsNumberOfSectionsInCollectionView = [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
} }
} }
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{ {
if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath) {
return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath];
} }
CGSize itemSize = _layout.itemSize; CGSize itemSize = _layout.itemSize;
@ -171,15 +165,6 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView
return ASSizeRangeMake(CGSizeZero, constrainedSize); return ASSizeRangeMake(CGSizeZero, constrainedSize);
} }
- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind
{
if (_dataSourceFlags.implementsNumberOfSectionsInCollectionView) {
return [collectionView.asyncDataSource numberOfSectionsInCollectionView:collectionView];
} else {
return 1;
}
}
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section - (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
{ {
return [self layoutHasSupplementaryViewOfKind:kind inSection:section collectionView:collectionView] ? 1 : 0; return [self layoutHasSupplementaryViewOfKind:kind inSection:section collectionView:collectionView] ? 1 : 0;

View File

@ -49,8 +49,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
std::vector<NSInteger> _itemCountsFromDataSource; // Main thread only. std::vector<NSInteger> _itemCountsFromDataSource; // Main thread only.
ASMainSerialQueue *_mainSerialQueue; ASMainSerialQueue *_mainSerialQueue;
NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking.
dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes.
dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting.
@ -62,8 +61,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
BOOL _delegateDidDeleteSections; BOOL _delegateDidDeleteSections;
} }
@property (nonatomic, assign) NSUInteger batchUpdateCounter;
@end @end
@implementation ASDataController @implementation ASDataController
@ -87,15 +84,11 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
_mainSerialQueue = [[ASMainSerialQueue alloc] init]; _mainSerialQueue = [[ASMainSerialQueue alloc] init];
_pendingEditCommandBlocks = [NSMutableArray array];
const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding];
_editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL); dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL);
_editingTransactionGroup = dispatch_group_create(); _editingTransactionGroup = dispatch_group_create();
_batchUpdateCounter = 0;
return self; return self;
} }
@ -395,60 +388,55 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion - (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion
{ {
ASDisplayNodeAssertMainThread();
_initialReloadDataHasBeenCalled = YES; _initialReloadDataHasBeenCalled = YES;
[self performEditCommandWithBlock:^{ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
ASDisplayNodeAssertMainThread();
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
[self invalidateDataSourceItemCounts]; [self invalidateDataSourceItemCounts];
NSUInteger sectionCount = [self itemCountsFromDataSource].size(); NSUInteger sectionCount = [self itemCountsFromDataSource].size();
NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet];
// Allow subclasses to perform setup before going into the edit transaction
[self prepareForReloadDataWithSectionCount:sectionCount];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
LOG(@"Edit Transaction - reloadData");
// Allow subclasses to perform setup before going into the edit transaction // Remove everything that existed before the reload, now that we're ready to insert replacements
[self prepareForReloadData]; NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSUInteger editingNodesSectionCount = editingNodes.count;
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ if (editingNodesSectionCount) {
LOG(@"Edit Transaction - reloadData"); NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
[self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions];
// Remove everything that existed before the reload, now that we're ready to insert replacements [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSUInteger editingNodesSectionCount = editingNodes.count;
if (editingNodesSectionCount) {
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
[self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
}
[self willReloadData];
// Insert empty sections
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]];
}
[self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions];
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
if (completion) {
[_mainSerialQueue performBlockOnMainThread:completion];
}
});
if (synchronously) {
[self waitUntilAllUpdatesAreCommitted];
} }
}];
[self willReloadDataWithSectionCount:sectionCount];
// Insert empty sections
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]];
}
[self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions];
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
if (completion) {
[_mainSerialQueue performBlockOnMainThread:completion];
}
});
if (synchronously) {
[self waitUntilAllUpdatesAreCommitted];
}
} }
- (void)waitUntilAllUpdatesAreCommitted - (void)waitUntilAllUpdatesAreCommitted
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(_batchUpdateCounter == 0, @"Should not be called between beginUpdate or endUpdate");
// This should never be called in a batch update, return immediately therefore
if (_batchUpdateCounter > 0) { return; }
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
@ -516,10 +504,20 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)beginUpdates - (void)beginUpdates
{ {
ASDisplayNodeAssertMainThread();
// TODO: make this -waitUntilAllUpdatesAreCommitted?
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
// Begin queuing up edit calls that happen on the main thread.
// This will prevent further operations from being scheduled on _editingTransactionQueue. dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
_batchUpdateCounter++; [_mainSerialQueue performBlockOnMainThread:^{
// Deep copy _completedNodes to _externalCompletedNodes.
// Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate.
_externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]);
LOG(@"beginUpdates - begin updates call to delegate");
[_delegate dataControllerBeginUpdates:self];
}];
});
} }
- (void)endUpdates - (void)endUpdates
@ -529,118 +527,72 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
{ {
_batchUpdateCounter--; LOG(@"endUpdatesWithCompletion - beginning");
ASDisplayNodeAssertMainThread();
if (_batchUpdateCounter == 0) { // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates.
LOG(@"endUpdatesWithCompletion - beginning"); // Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction.
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ [_mainSerialQueue performBlockOnMainThread:^{
[_mainSerialQueue performBlockOnMainThread:^{ // Now that the transaction is done, _completedNodes can be accessed externally again.
// Deep copy _completedNodes to _externalCompletedNodes. _externalCompletedNodes = nil;
// Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate.
_externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); LOG(@"endUpdatesWithCompletion - calling delegate end");
[_delegate dataController:self endUpdatesAnimated:animated completion:completion];
LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); }];
[_delegate dataControllerBeginUpdates:self]; });
}];
});
// Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates.
// Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction.
LOG(@"endUpdatesWithCompletion - %zd blocks to run", _pendingEditCommandBlocks.count);
NSUInteger i = 0;
for (dispatch_block_t block in _pendingEditCommandBlocks) {
LOG(@"endUpdatesWithCompletion - running block #%zd", i);
block();
i += 1;
}
[_pendingEditCommandBlocks removeAllObjects];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[_mainSerialQueue performBlockOnMainThread:^{
// Now that the transaction is done, _completedNodes can be accessed externally again.
_externalCompletedNodes = nil;
LOG(@"endUpdatesWithCompletion - calling delegate end");
[_delegate dataController:self endUpdatesAnimated:animated completion:completion];
}];
});
}
}
/**
* Queues the given operation until an `endUpdates` synchronize update is completed.
*
* If this method is called outside of a begin/endUpdates batch update, the block is
* executed immediately.
*/
- (void)performEditCommandWithBlock:(void (^)(void))block
{
// This method needs to block the thread and synchronously perform the operation if we are not
// queuing commands for begin/endUpdates. If we are queuing, it needs to return immediately.
if (!_initialReloadDataHasBeenCalled) {
return;
}
if (block == nil) {
return;
}
// If we have never performed a reload, there is no value in executing edit operations as the initial
// reload will directly re-query the latest state of the datasource - so completely skip the block in this case.
if (_batchUpdateCounter == 0) {
block();
} else {
[_pendingEditCommandBlocks addObject:block];
}
} }
#pragma mark - Section Editing (External API) #pragma mark - Section Editing (External API)
- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
[self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertMainThread(); LOG(@"Edit Command - insertSections: %@", sections);
if (!_initialReloadDataHasBeenCalled) {
return;
}
LOG(@"Edit Command - insertSections: %@", sections); dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections];
[self prepareForInsertSections:sections];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willInsertSections:sections];
LOG(@"Edit Transaction - insertSections: %@", sections);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections]; [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
});
[self prepareForInsertSections:sections];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willInsertSections:sections];
LOG(@"Edit Transaction - insertSections: %@", sections);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
});
}];
} }
- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
[self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertMainThread(); LOG(@"Edit Command - deleteSections: %@", sections);
LOG(@"Edit Command - deleteSections: %@", sections); if (!_initialReloadDataHasBeenCalled) {
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); return;
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ }
[self willDeleteSections:sections];
// remove elements dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
LOG(@"Edit Transaction - deleteSections: %@", sections); dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); [self willDeleteSections:sections];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // remove elements
[self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; LOG(@"Edit Transaction - deleteSections: %@", sections);
}); NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
}];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
});
} }
- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@ -650,44 +602,46 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
[self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertMainThread(); LOG(@"Edit Command - moveSection");
LOG(@"Edit Command - moveSection");
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); if (!_initialReloadDataHasBeenCalled) {
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ return;
[self willMoveSection:section toSection:newSection]; }
// remove elements dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
LOG(@"Edit Transaction - moveSection"); [self willMoveSection:section toSection:newSection];
NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// update the section of indexpaths // remove elements
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) { LOG(@"Edit Transaction - moveSection");
NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection]; NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind];
[updatedIndexPaths addObject:updatedIndexPath]; NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]);
} NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// Don't re-calculate size for moving // update the section of indexpaths
[self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
}); for (NSIndexPath *indexPath in indexPaths) {
}]; NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection];
[updatedIndexPaths addObject:updatedIndexPath];
}
// Don't re-calculate size for moving
[self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
});
} }
#pragma mark - Backing store manipulation optional hooks (Subclass API) #pragma mark - Backing store manipulation optional hooks (Subclass API)
- (void)prepareForReloadData - (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount
{ {
// Optional template hook for subclasses (See ASDataController+Subclasses.h) // Optional template hook for subclasses (See ASDataController+Subclasses.h)
} }
- (void)willReloadData - (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount
{ {
// Optional template hook for subclasses (See ASDataController+Subclasses.h) // Optional template hook for subclasses (See ASDataController+Subclasses.h)
} }
@ -736,59 +690,64 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
[self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertMainThread(); if (!_initialReloadDataHasBeenCalled) {
LOG(@"Edit Command - insertRows: %@", indexPaths); return;
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); }
// Sort indexPath to avoid messing up the index when inserting in several batches LOG(@"Edit Command - insertRows: %@", indexPaths);
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment]; // Sort indexPath to avoid messing up the index when inserting in several batches
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in sortedIndexPaths) {
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
indexPath:indexPath
constrainedSize:constrainedSize
environmentTraitCollection:environmentTraitCollection]];
}
[self prepareForInsertRowsAtIndexPaths:indexPaths]; id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
for (NSIndexPath *indexPath in sortedIndexPaths) {
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
indexPath:indexPath
constrainedSize:constrainedSize
environmentTraitCollection:environmentTraitCollection]];
}
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ [self prepareForInsertRowsAtIndexPaths:indexPaths];
[self willInsertRowsAtIndexPaths:indexPaths];
LOG(@"Edit Transaction - insertRows: %@", indexPaths); dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; [self willInsertRowsAtIndexPaths:indexPaths];
});
}]; LOG(@"Edit Transaction - insertRows: %@", indexPaths);
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
});
} }
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
[self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - deleteRows: %@", indexPaths);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); if (!_initialReloadDataHasBeenCalled) {
return;
// Sort indexPath in order to avoid messing up the index when deleting in several batches. }
// FIXME: Shouldn't deletes be sorted in descending order?
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
[self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; LOG(@"Edit Command - deleteRows: %@", indexPaths);
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
[self willDeleteRowsAtIndexPaths:sortedIndexPaths];
// Sort indexPath in order to avoid messing up the index when deleting in several batches.
// FIXME: Shouldn't deletes be sorted in descending order?
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
LOG(@"Edit Transaction - deleteRows: %@", indexPaths); [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths];
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
}); dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
}]; [self willDeleteRowsAtIndexPaths:sortedIndexPaths];
LOG(@"Edit Transaction - deleteRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
});
} }
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@ -798,22 +757,24 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)relayoutAllNodes - (void)relayoutAllNodes
{ {
[self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertMainThread(); if (!_initialReloadDataHasBeenCalled) {
LOG(@"Edit Command - relayoutRows"); return;
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); }
// Can't relayout right away because _completedNodes may not be up-to-date, LOG(@"Edit Command - relayoutRows");
// i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
// (see _layoutNodes:atIndexPaths:withAnimationOptions:).
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ // Can't relayout right away because _completedNodes may not be up-to-date,
[_mainSerialQueue performBlockOnMainThread:^{ // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes
for (NSString *kind in _completedNodes) { // (see _layoutNodes:atIndexPaths:withAnimationOptions:).
[self _relayoutNodesOfKind:kind]; dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
} [_mainSerialQueue performBlockOnMainThread:^{
}]; for (NSString *kind in _completedNodes) {
}); [self _relayoutNodesOfKind:kind];
}]; }
}];
});
} }
- (void)_relayoutNodesOfKind:(NSString *)kind - (void)_relayoutNodesOfKind:(NSString *)kind
@ -840,22 +801,24 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
[self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertMainThread(); if (!_initialReloadDataHasBeenCalled) {
LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath); return;
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); }
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *indexPaths = @[indexPath];
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// Don't re-calculate size for moving LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *newIndexPaths = @[newIndexPath]; dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
[self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions];
}); dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
}]; LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *indexPaths = @[indexPath];
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// Don't re-calculate size for moving
NSArray *newIndexPaths = @[newIndexPath];
[self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions];
});
} }
#pragma mark - Data Querying (Subclass API) #pragma mark - Data Querying (Subclass API)

View File

@ -91,8 +91,7 @@
selector == @selector(collectionView:nodeForItemAtIndexPath:) || selector == @selector(collectionView:nodeForItemAtIndexPath:) ||
selector == @selector(collectionView:nodeBlockForItemAtIndexPath:) || selector == @selector(collectionView:nodeBlockForItemAtIndexPath:) ||
selector == @selector(collectionView:numberOfItemsInSection:) || selector == @selector(collectionView:numberOfItemsInSection:) ||
selector == @selector(collectionView:constrainedSizeForNodeAtIndexPath:) || selector == @selector(collectionView:constrainedSizeForNodeAtIndexPath:)
selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)
); );
} }

View File

@ -36,17 +36,17 @@ ASEnvironmentTraitCollection ASEnvironmentTraitCollectionMakeDefault()
ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection)
{ {
ASEnvironmentTraitCollection asyncTraitCollection; ASEnvironmentTraitCollection environmentTraitCollection = ASEnvironmentTraitCollectionMakeDefault();
if (AS_AT_LEAST_IOS8) { if (AS_AT_LEAST_IOS8) {
asyncTraitCollection.displayScale = traitCollection.displayScale; environmentTraitCollection.displayScale = traitCollection.displayScale;
asyncTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass;
asyncTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass;
asyncTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom;
if (AS_AT_LEAST_IOS9) { if (AS_AT_LEAST_IOS9) {
asyncTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability;
} }
} }
return asyncTraitCollection; return environmentTraitCollection;
} }
BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs) BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs)

View File

@ -11,6 +11,7 @@
#import "ASHighlightOverlayLayer.h" #import "ASHighlightOverlayLayer.h"
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <tgmath.h>
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
@ -86,7 +87,7 @@ static const UIEdgeInsets padding = {2, 4, 1.5, 4};
if (targetLayer != nil) { if (targetLayer != nil) {
rect = [self convertRect:rect fromLayer:targetLayer]; rect = [self convertRect:rect fromLayer:targetLayer];
} }
rect = CGRectMake(roundf(rect.origin.x), roundf(rect.origin.y), roundf(rect.size.width), roundf(rect.size.height)); rect = CGRectMake(std::round(rect.origin.x), std::round(rect.origin.y), std::round(rect.size.width), std::round(rect.size.height));
CGFloat minX = rect.origin.x - padding.left; CGFloat minX = rect.origin.x - padding.left;
CGFloat maxX = CGRectGetMaxX(rect) + padding.right; CGFloat maxX = CGRectGetMaxX(rect) + padding.right;

View File

@ -61,4 +61,4 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -271,4 +271,4 @@
} }
@end @end
#endif #endif

View File

@ -68,4 +68,4 @@ extern NSString *const ASPhotosURLScheme;
@end @end
// NS_ASSUME_NONNULL_END // NS_ASSUME_NONNULL_END
#endif #endif

View File

@ -163,4 +163,4 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h";
} }
@end @end
#endif #endif

View File

@ -134,6 +134,8 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes; - (NSArray<NSArray <ASCellNode *> *> *)completedNodes;
- (NSString *)nameForRangeControllerDataSource;
@end @end
/** /**

View File

@ -11,13 +11,16 @@
#import "ASRangeController.h" #import "ASRangeController.h"
#import "ASAssert.h" #import "ASAssert.h"
#import "ASWeakSet.h" #import "ASCellNode.h"
#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeExtras.h"
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASMultidimensionalArrayUtils.h" #import "ASMultidimensionalArrayUtils.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASMultiDimensionalArrayUtils.h"
#import "ASWeakSet.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#import "ASCellNode.h" #import "AsyncDisplayKit+Debug.h"
#define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0 #define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0
@ -64,6 +67,10 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
#endif #endif
if ([ASRangeController shouldShowRangeDebugOverlay]) {
[self addRangeControllerToRangeDebugOverlay];
}
return self; return self;
} }
@ -335,6 +342,25 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
} }
} }
// TODO: This code is for debugging only, but would be great to clean up with a delegate method implementation.
if ([ASRangeController shouldShowRangeDebugOverlay]) {
ASScrollDirection scrollableDirections = ASScrollDirectionUp | ASScrollDirectionDown;
if ([_dataSource isKindOfClass:NSClassFromString(@"ASCollectionView")]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
scrollableDirections = (ASScrollDirection)[_dataSource performSelector:@selector(scrollableDirections)];
#pragma clang diagnostic pop
}
[self updateRangeController:self
withScrollableDirections:scrollableDirections
scrollDirection:scrollDirection
rangeMode:rangeMode
displayTuningParameters:parametersDisplay
fetchDataTuningParameters:parametersFetchData
interfaceState:selfInterfaceState];
}
_rangeIsValid = YES; _rangeIsValid = YES;
#if ASRangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled

View File

@ -34,6 +34,8 @@ static inline BOOL ASDisplayNodeThreadIsMain()
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
#endif #endif
#include <memory>
/** /**
For use with ASDN::StaticMutex only. For use with ASDN::StaticMutex only.
*/ */
@ -53,7 +55,7 @@ static inline BOOL ASDisplayNodeThreadIsMain()
namespace ASDN { namespace ASDN {
template<class T> template<class T>
class Locker class Locker
{ {
@ -98,17 +100,72 @@ namespace ASDN {
}; };
template<class T>
class SharedLocker
{
std::shared_ptr<T> _l;
#if TIME_LOCKER
CFTimeInterval _ti;
const char *_name;
#endif
public:
#if !TIME_LOCKER
SharedLocker (std::shared_ptr<T> const& l) ASDISPLAYNODE_NOTHROW : _l (l) {
assert(_l != nullptr);
_l->lock ();
}
~SharedLocker () {
_l->unlock ();
}
// non-copyable.
SharedLocker(const SharedLocker<T>&) = delete;
SharedLocker &operator=(const SharedLocker<T>&) = delete;
#else
SharedLocker (std::shared_ptr<T> const& l, const char *name = NULL) ASDISPLAYNODE_NOTHROW : _l (l), _name(name) {
_ti = CACurrentMediaTime();
_l->lock ();
}
~SharedLocker () {
_l->unlock ();
if (_name) {
printf(_name, NULL);
printf(" dt:%f\n", CACurrentMediaTime() - _ti);
}
}
#endif
};
template<class T> template<class T>
class Unlocker class Unlocker
{ {
T &_l; T &_l;
public: public:
Unlocker (T &l) ASDISPLAYNODE_NOTHROW : _l (l) {_l.unlock ();} Unlocker (T &l) ASDISPLAYNODE_NOTHROW : _l (l) { _l.unlock (); }
~Unlocker () {_l.lock ();} ~Unlocker () {_l.lock ();}
Unlocker(Unlocker<T>&) = delete; Unlocker(Unlocker<T>&) = delete;
Unlocker &operator=(Unlocker<T>&) = delete; Unlocker &operator=(Unlocker<T>&) = delete;
}; };
template<class T>
class SharedUnlocker
{
std::shared_ptr<T> _l;
public:
SharedUnlocker (std::shared_ptr<T> const& l) ASDISPLAYNODE_NOTHROW : _l (l) { _l->unlock (); }
~SharedUnlocker () { _l->lock (); }
SharedUnlocker(SharedUnlocker<T>&) = delete;
SharedUnlocker &operator=(SharedUnlocker<T>&) = delete;
};
struct Mutex struct Mutex
{ {
@ -164,7 +221,9 @@ namespace ASDN {
}; };
typedef Locker<Mutex> MutexLocker; typedef Locker<Mutex> MutexLocker;
typedef SharedLocker<Mutex> MutexSharedLocker;
typedef Unlocker<Mutex> MutexUnlocker; typedef Unlocker<Mutex> MutexUnlocker;
typedef SharedUnlocker<Mutex> MutexSharedUnlocker;
/** /**
If you are creating a static mutex, use StaticMutex and specify its default value as one of ASDISPLAYNODE_MUTEX_INITIALIZER If you are creating a static mutex, use StaticMutex and specify its default value as one of ASDISPLAYNODE_MUTEX_INITIALIZER

View File

@ -12,7 +12,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@interface ASWeakProxy : NSObject @interface ASWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target; @property (nonatomic, weak, readonly) id target;

View File

@ -16,7 +16,7 @@
- (instancetype)initWithTarget:(id)target - (instancetype)initWithTarget:(id)target
{ {
if (self = [super init]) { if (self) {
_target = target; _target = target;
} }
return self; return self;
@ -32,4 +32,24 @@
return _target; return _target;
} }
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
// Check for a compiled definition for the selector
NSMethodSignature *methodSignature = [[_target class] instanceMethodSignatureForSelector:aSelector];
// Unfortunately, in order to get this object to work properly, the use of a method which creates an NSMethodSignature
// from a C string. -methodSignatureForSelector is called when a compiled definition for the selector cannot be found.
// This is the place where we have to create our own dud NSMethodSignature. This is necessary because if this method
// returns nil, a selector not found exception is raised. The string argument to -signatureWithObjCTypes: outlines
// the return type and arguments to the message. To return a dud NSMethodSignature, pretty much any signature will
// suffice. Since the -forwardInvocation call will do nothing if the delegate does not respond to the selector,
// the dud NSMethodSignature simply gets us around the exception.
return methodSignature ?: [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
// If we are down here this means _target where nil. Just don't do anything to prevent a crash
}
@end @end

View File

@ -43,4 +43,4 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -14,6 +14,14 @@
#import "ASBaseDefines.h" #import "ASBaseDefines.h"
#import "ASLayoutController.h" #import "ASLayoutController.h"
#ifndef CGFLOAT_EPSILON
#if CGFLOAT_IS_DOUBLE
#define CGFLOAT_EPSILON DBL_EPSILON
#else
#define CGFLOAT_EPSILON FLT_EPSILON
#endif
#endif
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN

View File

@ -11,6 +11,7 @@
// //
#import "NSArray+Diffing.h" #import "NSArray+Diffing.h"
#import "ASAssert.h"
@implementation NSArray (Diffing) @implementation NSArray (Diffing)
@ -58,12 +59,24 @@
NSInteger selfCount = self.count; NSInteger selfCount = self.count;
NSInteger arrayCount = array.count; NSInteger arrayCount = array.count;
NSInteger lengths[selfCount+1][arrayCount+1]; // Allocate the diff map in the heap so we don't blow the stack for large arrays.
NSInteger (*lengths)[arrayCount+1] = NULL;
size_t lengthsSize = ((selfCount+1) * sizeof(*lengths));
// Would rather use initWithCapacity: to skip the zeroing, but TECHNICALLY
// `mutableBytes` is only guaranteed to be non-NULL if the data object has a non-zero length.
NS_VALID_UNTIL_END_OF_SCOPE NSMutableData *lengthsData = [[NSMutableData alloc] initWithLength:lengthsSize];
lengths = lengthsData.mutableBytes;
if (lengths == NULL) {
ASDisplayNodeFailAssert(@"Failed to allocate memory for diffing with size %tu", lengthsSize);
return nil;
}
for (NSInteger i = 0; i <= selfCount; i++) { for (NSInteger i = 0; i <= selfCount; i++) {
id selfObj = i > 0 ? self[i-1] : nil;
for (NSInteger j = 0; j <= arrayCount; j++) { for (NSInteger j = 0; j <= arrayCount; j++) {
if (i == 0 || j == 0) { if (i == 0 || j == 0) {
lengths[i][j] = 0; lengths[i][j] = 0;
} else if (comparison(self[i-1], array[j-1])) { } else if (comparison(selfObj, array[j-1])) {
lengths[i][j] = 1 + lengths[i-1][j-1]; lengths[i][j] = 1 + lengths[i-1][j-1];
} else { } else {
lengths[i][j] = MAX(lengths[i-1][j], lengths[i][j-1]); lengths[i][j] = MAX(lengths[i-1][j], lengths[i][j-1]);

View File

@ -24,4 +24,4 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -18,4 +18,4 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -101,18 +101,11 @@
#endif #endif
- (void)layoutSublayers - (void)layoutSublayers
{ {
ASDisplayNodeAssertMainThread();
[super layoutSublayers]; [super layoutSublayers];
ASDisplayNode *node = self.asyncdisplaykit_node; [self.asyncdisplaykit_node __layout];
if (ASDisplayNodeThreadIsMain()) {
[node __layout];
} else {
ASDisplayNodeFailAssert(@"not reached assertion");
dispatch_async(dispatch_get_main_queue(), ^ {
[node __layout];
});
}
} }
- (void)setNeedsDisplay - (void)setNeedsDisplay

View File

@ -9,6 +9,7 @@
// //
#import "_ASDisplayView.h" #import "_ASDisplayView.h"
#import "_ASDisplayViewAccessiblity.h"
#import "_ASCoreAnimationExtras.h" #import "_ASCoreAnimationExtras.h"
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
@ -26,9 +27,12 @@
@implementation _ASDisplayView @implementation _ASDisplayView
{ {
__unsafe_unretained ASDisplayNode *_node; // Though UIView has a .node property added via category, since we can add an ivar to a subclass, use that for performance. __unsafe_unretained ASDisplayNode *_node; // Though UIView has a .node property added via category, since we can add an ivar to a subclass, use that for performance.
BOOL _inHitTest; BOOL _inHitTest;
BOOL _inPointInside; BOOL _inPointInside;
NSArray *_accessibleElements; NSArray *_accessibleElements;
CGRect _lastAccessibleElementsFrame;
} }
@synthesize asyncdisplaykit_node = _node; @synthesize asyncdisplaykit_node = _node;
@ -39,10 +43,6 @@
} }
#pragma mark - NSObject Overrides #pragma mark - NSObject Overrides
- (instancetype)init
{
return [self initWithFrame:CGRectZero];
}
- (NSString *)description - (NSString *)description
{ {
@ -53,14 +53,6 @@
#pragma mark - UIView Overrides #pragma mark - UIView Overrides
- (instancetype)initWithFrame:(CGRect)frame
{
if (!(self = [super initWithFrame:frame]))
return nil;
return self;
}
- (void)willMoveToWindow:(UIWindow *)newWindow - (void)willMoveToWindow:(UIWindow *)newWindow
{ {
BOOL visible = (newWindow != nil); BOOL visible = (newWindow != nil);
@ -126,7 +118,6 @@
[newSuperview.asyncdisplaykit_node addSubnode:_node]; [newSuperview.asyncdisplaykit_node addSubnode:_node];
} }
} }
} }
- (void)didMoveToSuperview - (void)didMoveToSuperview
@ -169,27 +160,29 @@
} }
} }
- (void)setNeedsDisplay - (void)addSubview:(UIView *)view
{ {
// Standard implementation does not actually get to the layer, at least for views that don't implement drawRect:. [super addSubview:view];
if (ASDisplayNodeThreadIsMain()) {
[self.layer setNeedsDisplay]; #ifndef ASDK_ACCESSIBILITY_DISABLE
} else { self.accessibleElements = nil;
dispatch_async(dispatch_get_main_queue(), ^ { #endif
[self.layer setNeedsDisplay];
});
}
} }
- (void)setNeedsLayout - (void)willRemoveSubview:(UIView *)subview
{ {
if (ASDisplayNodeThreadIsMain()) { [super willRemoveSubview:subview];
[super setNeedsLayout];
} else { #ifndef ASDK_ACCESSIBILITY_DISABLE
dispatch_async(dispatch_get_main_queue(), ^ { self.accessibleElements = nil;
[super setNeedsLayout]; #endif
}); }
}
- (void)setNeedsDisplay
{
ASDisplayNodeAssertMainThread();
// Standard implementation does not actually get to the layer, at least for views that don't implement drawRect:.
[self.layer setNeedsDisplay];
} }
- (UIViewContentMode)contentMode - (UIViewContentMode)contentMode

View File

@ -9,3 +9,7 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@interface _ASDisplayView (UIAccessibilityContainer)
@property (copy, nonatomic) NSArray *accessibleElements;
@end

View File

@ -8,17 +8,55 @@
// of patent rights can be found in the PATENTS file in the same directory. // of patent rights can be found in the PATENTS file in the same directory.
// //
#ifndef ASDK_ACCESSIBILITY_DISABLE
#import "_ASDisplayView.h" #import "_ASDisplayView.h"
#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#pragma mark - UIAccessibilityElement #pragma mark - UIAccessibilityElement
@implementation UIAccessibilityElement (_ASDisplayView) typedef NSComparisonResult (^SortAccessibilityElementsComparator)(UIAccessibilityElement *, UIAccessibilityElement *);
+ (UIAccessibilityElement *)accessibilityElementWithContainer:(id)container node:(ASDisplayNode *)node /// Sort accessiblity elements first by y and than by x origin.
static void SortAccessibilityElements(NSMutableArray *elements)
{ {
UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container]; ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
static SortAccessibilityElementsComparator comparator = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
comparator = ^NSComparisonResult(UIAccessibilityElement *a, UIAccessibilityElement *b) {
CGPoint originA = a.accessibilityFrame.origin;
CGPoint originB = b.accessibilityFrame.origin;
if (originA.y == originB.y) {
if (originA.x == originB.x) {
return NSOrderedSame;
}
return (originA.x < originB.x) ? NSOrderedAscending : NSOrderedDescending;
}
return (originA.y < originB.y) ? NSOrderedAscending : NSOrderedDescending;
};
});
[elements sortUsingComparator:comparator];
}
@interface ASAccessibilityElement : UIAccessibilityElement
@property (nonatomic, strong) ASDisplayNode *node;
@property (nonatomic, strong) ASDisplayNode *containerNode;
+ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode;
@end
@implementation ASAccessibilityElement
+ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode
{
ASAccessibilityElement *accessibilityElement = [[ASAccessibilityElement alloc] initWithAccessibilityContainer:container];
accessibilityElement.node = node;
accessibilityElement.containerNode = containerNode;
accessibilityElement.accessibilityIdentifier = node.accessibilityIdentifier; accessibilityElement.accessibilityIdentifier = node.accessibilityIdentifier;
accessibilityElement.accessibilityLabel = node.accessibilityLabel; accessibilityElement.accessibilityLabel = node.accessibilityLabel;
accessibilityElement.accessibilityHint = node.accessibilityHint; accessibilityElement.accessibilityHint = node.accessibilityHint;
@ -27,101 +65,116 @@
return accessibilityElement; return accessibilityElement;
} }
@end - (CGRect)accessibilityFrame
{
CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node];
accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.accessibilityContainer);
return accessibilityFrame;
}
@end
#pragma mark - _ASDisplayView / UIAccessibilityContainer #pragma mark - _ASDisplayView / UIAccessibilityContainer
static NSArray *ASCollectUIAccessibilityElementsForNode(ASDisplayNode *viewNode, ASDisplayNode *subnode, id container) { /// Collect all subnodes for the given node by walking down the subnode tree and calculates the screen coordinates based on the containerNode and container
NSMutableArray *accessibleElements = [NSMutableArray array]; static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplayNode *containerNode, id container, NSMutableArray *elements)
ASDisplayNodePerformBlockOnEveryNodeBFS(subnode, ^(ASDisplayNode * _Nonnull currentNode) { {
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
ASDisplayNodePerformBlockOnEveryNodeBFS(node, ^(ASDisplayNode * _Nonnull currentNode) {
// For every subnode that is layer backed or it's supernode has shouldRasterizeDescendants enabled // For every subnode that is layer backed or it's supernode has shouldRasterizeDescendants enabled
// we have to create a UIAccessibilityElement as no view for this node exists // we have to create a UIAccessibilityElement as no view for this node exists
if (currentNode != viewNode && currentNode.isAccessibilityElement) { if (currentNode != containerNode && currentNode.isAccessibilityElement) {
UIAccessibilityElement *accessibilityElement = [UIAccessibilityElement accessibilityElementWithContainer:container node:currentNode]; UIAccessibilityElement *accessibilityElement = [ASAccessibilityElement accessibilityElementWithContainer:container node:currentNode containerNode:containerNode];
// As the node hierarchy is flattened it's necessary to convert the frame for each subnode in the tree to the [elements addObject:accessibilityElement];
// coordinate system of the supernode
CGRect frame = [viewNode convertRect:currentNode.bounds fromNode:currentNode];
accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, container);
[accessibleElements addObject:accessibilityElement];
} }
}); });
}
/// Collect all accessibliity elements for a given view and view node
static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableArray *elements)
{
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
return [accessibleElements copy]; ASDisplayNode *node = view.asyncdisplaykit_node;
// Handle rasterize case
if (node.shouldRasterizeDescendants) {
CollectUIAccessibilityElementsForNode(node, node, view, elements);
return;
}
for (ASDisplayNode *subnode in node.subnodes) {
if (subnode.isAccessibilityElement) {
// An accessiblityElement can either be a UIView or a UIAccessibilityElement
if (subnode.isLayerBacked) {
// No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:subnode containerNode:node];
[elements addObject:accessiblityElement];
} else {
// Accessiblity element is not layer backed just add the view as accessibility element
[elements addObject:subnode.view];
}
} else if (subnode.isLayerBacked) {
// Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement
CollectUIAccessibilityElementsForNode(subnode, node, view, elements);
} else if ([subnode accessibilityElementCount] > 0) {
// UIView is itself a UIAccessibilityContainer just add it
[elements addObject:subnode.view];
}
}
} }
@interface _ASDisplayView () { @interface _ASDisplayView () {
NSArray *_accessibleElements; NSArray *_accessibleElements;
} }
@end @end
@implementation _ASDisplayView (UIAccessibilityContainer) @implementation _ASDisplayView (UIAccessibilityContainer)
#pragma mark - UIAccessibility #pragma mark - UIAccessibility
- (void)setAccessibleElements:(NSArray *)accessibleElements
{
_accessibleElements = nil;
}
- (NSArray *)accessibleElements - (NSArray *)accessibleElements
{ {
ASDisplayNode *viewNode = self.asyncdisplaykit_node; ASDisplayNode *viewNode = self.asyncdisplaykit_node;
if (viewNode == nil) { if (viewNode == nil) {
return nil; return @[];
} }
// Handle rasterize case if (_accessibleElements != nil) {
if (viewNode.shouldRasterizeDescendants) {
_accessibleElements = ASCollectUIAccessibilityElementsForNode(viewNode, viewNode, self);
return _accessibleElements; return _accessibleElements;
} }
// Handle not rasterize case
NSMutableArray *accessibleElements = [NSMutableArray array]; NSMutableArray *accessibleElements = [NSMutableArray array];
CollectAccessibilityElementsForView(self, accessibleElements);
for (ASDisplayNode *subnode in viewNode.subnodes) { SortAccessibilityElements(accessibleElements);
if (subnode.isAccessibilityElement) { _accessibleElements = accessibleElements;
// An accessiblityElement can either be a UIView or a UIAccessibilityElement
id accessiblityElement = nil;
if (subnode.isLayerBacked) {
// No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node
accessiblityElement = [UIAccessibilityElement accessibilityElementWithContainer:self node:subnode];
} else {
accessiblityElement = subnode.view;
}
[accessiblityElement setAccessibilityFrame:UIAccessibilityConvertFrameToScreenCoordinates(subnode.frame, self)];
[accessibleElements addObject:accessiblityElement];
} else if (subnode.isLayerBacked) {
// Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement
[accessibleElements addObjectsFromArray:ASCollectUIAccessibilityElementsForNode(viewNode, subnode, self)];
} else if ([subnode accessibilityElementCount] > 0) {
// Add UIAccessibilityContainer
[accessibleElements addObject:subnode.view];
}
}
_accessibleElements = [accessibleElements copy];
return _accessibleElements; return _accessibleElements;
} }
- (NSInteger)accessibilityElementCount - (NSInteger)accessibilityElementCount
{ {
return [self accessibleElements].count; return self.accessibleElements.count;
} }
- (id)accessibilityElementAtIndex:(NSInteger)index - (id)accessibilityElementAtIndex:(NSInteger)index
{ {
ASDisplayNodeAssertNotNil(_accessibleElements, @"At this point _accessibleElements should be created."); return self.accessibleElements[index];
if (_accessibleElements == nil) {
return nil;
}
return _accessibleElements[index];
} }
- (NSInteger)indexOfAccessibilityElement:(id)element - (NSInteger)indexOfAccessibilityElement:(id)element
{ {
if (_accessibleElements == nil) { return [self.accessibleElements indexOfObjectIdenticalTo:element];
return NSNotFound;
}
return [_accessibleElements indexOfObject:element];
} }
@end @end
#endif

View File

@ -10,6 +10,8 @@
#import "ASAsciiArtBoxCreator.h" #import "ASAsciiArtBoxCreator.h"
#import <tgmath.h>
static const NSUInteger kDebugBoxPadding = 2; static const NSUInteger kDebugBoxPadding = 2;
typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation)
@ -69,7 +71,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation)
for (NSString *child in children) { for (NSString *child in children) {
NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy]; NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy];
NSUInteger topPadding = ceilf((CGFloat)(lineCountPerChild - [lines count])/2.0); NSUInteger topPadding = ceil((CGFloat)(lineCountPerChild - [lines count])/2.0);
NSUInteger bottomPadding = (lineCountPerChild - [lines count])/2.0; NSUInteger bottomPadding = (lineCountPerChild - [lines count])/2.0;
NSUInteger lineLength = [lines[0] length]; NSUInteger lineLength = [lines[0] length];
@ -98,7 +100,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation)
NSUInteger totalLineLength = [concatenatedLines[0] length]; NSUInteger totalLineLength = [concatenatedLines[0] length];
if (totalLineLength < [parent length]) { if (totalLineLength < [parent length]) {
NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - totalLineLength; NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - totalLineLength;
NSUInteger leftPadding = ceilf((CGFloat)difference/2.0); NSUInteger leftPadding = ceil((CGFloat)difference/2.0);
NSUInteger rightPadding = difference/2; NSUInteger rightPadding = difference/2;
NSString *leftString = [@"|" debugbox_stringByAddingPadding:@" " count:leftPadding location:PIDebugBoxPaddingLocationEnd]; NSString *leftString = [@"|" debugbox_stringByAddingPadding:@" " count:leftPadding location:PIDebugBoxPaddingLocationEnd];
@ -137,7 +139,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation)
if (maxChildLength < [parent length]) { if (maxChildLength < [parent length]) {
NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - maxChildLength; NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - maxChildLength;
leftPadding = ceilf((CGFloat)difference/2.0); leftPadding = ceil((CGFloat)difference/2.0);
rightPadding = difference/2; rightPadding = difference/2;
} }
@ -147,7 +149,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation)
for (NSString *child in children) { for (NSString *child in children) {
NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy]; NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy];
NSUInteger leftLinePadding = ceilf((CGFloat)(maxChildLength - [lines[0] length])/2.0); NSUInteger leftLinePadding = ceil((CGFloat)(maxChildLength - [lines[0] length])/2.0);
NSUInteger rightLinePadding = (maxChildLength - [lines[0] length])/2.0; NSUInteger rightLinePadding = (maxChildLength - [lines[0] length])/2.0;
for (NSString *line in lines) { for (NSString *line in lines) {
@ -171,7 +173,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation)
NSUInteger totalLineLength = [boxStrings[0] length]; NSUInteger totalLineLength = [boxStrings[0] length];
[boxStrings addObject:[NSString debugbox_stringWithString:@"-" repeatedCount:totalLineLength]]; [boxStrings addObject:[NSString debugbox_stringWithString:@"-" repeatedCount:totalLineLength]];
NSUInteger leftPadding = ceilf(((CGFloat)(totalLineLength - [parent length]))/2.0); NSUInteger leftPadding = ceil(((CGFloat)(totalLineLength - [parent length]))/2.0);
NSUInteger rightPadding = (totalLineLength - [parent length])/2; NSUInteger rightPadding = (totalLineLength - [parent length])/2;
NSString *topLine = [parent debugbox_stringByAddingPadding:@"-" count:leftPadding location:PIDebugBoxPaddingLocationFront]; NSString *topLine = [parent debugbox_stringByAddingPadding:@"-" count:leftPadding location:PIDebugBoxPaddingLocationFront];

View File

@ -17,7 +17,7 @@ typedef NS_ENUM(NSInteger, ASRelativeDimensionType) {
/** Just a number. It will always resolve to exactly this amount. This is the default type. */ /** Just a number. It will always resolve to exactly this amount. This is the default type. */
ASRelativeDimensionTypePoints, ASRelativeDimensionTypePoints,
/** Multiplied to a provided parent amount to resolve a final amount. */ /** Multiplied to a provided parent amount to resolve a final amount. */
ASRelativeDimensionTypePercent, ASRelativeDimensionTypeFraction,
}; };
typedef struct { typedef struct {
@ -44,7 +44,7 @@ extern ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type,
extern ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points); extern ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points);
extern ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent); extern ASRelativeDimension ASRelativeDimensionMakeWithFraction(CGFloat fraction);
extern ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension); extern ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension);

View File

@ -19,9 +19,9 @@ ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloa
{ {
if (type == ASRelativeDimensionTypePoints) { if (type == ASRelativeDimensionTypePoints) {
ASDisplayNodeCAssertPositiveReal(@"Points", value); ASDisplayNodeCAssertPositiveReal(@"Points", value);
} else if (type == ASRelativeDimensionTypePercent) { } else if (type == ASRelativeDimensionTypeFraction) {
// TODO: Enable this assertion for 2.0. Check that there is no use case for using a larger value, e.g. to layout for a clipsToBounds = NO element. // TODO: Enable this assertion for 2.0. Check that there is no use case for using a larger value, e.g. to layout for a clipsToBounds = NO element.
// ASDisplayNodeCAssert( 0 <= value && value <= 1.0, @"ASRelativeDimension percent value (%f) must be between 0 and 1.", value); // ASDisplayNodeCAssert( 0 <= value && value <= 1.0, @"ASRelativeDimension fraction value (%f) must be between 0 and 1.", value);
} }
ASRelativeDimension dimension; dimension.type = type; dimension.value = value; return dimension; ASRelativeDimension dimension; dimension.type = type; dimension.value = value; return dimension;
} }
@ -32,10 +32,10 @@ ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points)
return ASRelativeDimensionMake(ASRelativeDimensionTypePoints, points); return ASRelativeDimensionMake(ASRelativeDimensionTypePoints, points);
} }
ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent) ASRelativeDimension ASRelativeDimensionMakeWithFraction(CGFloat fraction)
{ {
// ASDisplayNodeCAssert( 0 <= percent && percent <= 1.0, @"ASRelativeDimension percent value (%f) must be between 0 and 1.", percent); // ASDisplayNodeCAssert( 0 <= fraction && fraction <= 1.0, @"ASRelativeDimension fraction value (%f) must be between 0 and 1.", fraction);
return ASRelativeDimensionMake(ASRelativeDimensionTypePercent, percent); return ASRelativeDimensionMake(ASRelativeDimensionTypeFraction, fraction);
} }
ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension) ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension)
@ -53,7 +53,7 @@ NSString *NSStringFromASRelativeDimension(ASRelativeDimension dimension)
switch (dimension.type) { switch (dimension.type) {
case ASRelativeDimensionTypePoints: case ASRelativeDimensionTypePoints:
return [NSString stringWithFormat:@"%.0fpt", dimension.value]; return [NSString stringWithFormat:@"%.0fpt", dimension.value];
case ASRelativeDimensionTypePercent: case ASRelativeDimensionTypeFraction:
return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0]; return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0];
} }
} }
@ -63,7 +63,7 @@ CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat parent
switch (dimension.type) { switch (dimension.type) {
case ASRelativeDimensionTypePoints: case ASRelativeDimensionTypePoints:
return dimension.value; return dimension.value;
case ASRelativeDimensionTypePercent: case ASRelativeDimensionTypeFraction:
return dimension.value * parent; return dimension.value * parent;
} }
} }

View File

@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
/** /**
A layout spec that wraps another layoutable child, applying insets around it. A layout spec that wraps another layoutable child, applying insets around it.
If the child has a size specified as a percentage, the percentage is resolved against this spec's parent If the child has a size specified as a fraction, the fraction is resolved against this spec's parent
size **after** applying insets. size **after** applying insets.
@example ASOuterLayoutSpec contains an ASInsetLayoutSpec with an ASInnerLayoutSpec. Suppose that: @example ASOuterLayoutSpec contains an ASInsetLayoutSpec with an ASInnerLayoutSpec. Suppose that:

View File

@ -40,11 +40,12 @@ NS_ASSUME_NONNULL_BEGIN
* only require a single child. * only require a single child.
* *
* For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example) * For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example)
* a subclass should use this method to set the "primary" child. This is actually the same as calling * a subclass should use this method to set the "primary" child. It can then use setChild:forIdentifier:
* setChild:forIdentifier:0. All other children should be set by defining convenience methods * to set any other required children. Ideally a subclass would hide this from the user, and use the
* that call setChild:forIdentifier behind the scenes. * setChild:forIdentifier: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild
* property that behind the scenes is calling setChild:forIdentifier:.
*/ */
- (void)setChild:(id<ASLayoutable>)child; @property (nullable, strong, nonatomic) id<ASLayoutable> child;
/** /**
* Adds a child with the given identifier to this layout spec. * Adds a child with the given identifier to this layout spec.
@ -76,21 +77,7 @@ NS_ASSUME_NONNULL_BEGIN
* For good measure, in these layout specs it probably makes sense to define * For good measure, in these layout specs it probably makes sense to define
* setChild: and setChild:forIdentifier: methods to do something appropriate or to assert. * setChild: and setChild:forIdentifier: methods to do something appropriate or to assert.
*/ */
- (void)setChildren:(NSArray<id<ASLayoutable>> *)children; @property (nullable, strong, nonatomic) NSArray<id<ASLayoutable>> *children;
/**
* Get child methods
*
* There is a corresponding "getChild" method for the above "setChild" methods. If a subclass
* has extra layoutable children, it is recommended to make a corresponding get method for that
* child. For example, the ASBackgroundLayoutSpec responds to backgroundChild.
*
* If a get method is called on a spec that doesn't make sense, then the standard is to assert.
* For example, calling children on an ASInsetLayoutSpec will assert.
*/
/** Returns the child added to this layout spec using the default identifier. */
- (nullable id<ASLayoutable>)child;
/** /**
* Returns the child added to this layout spec using the given index. * Returns the child added to this layout spec using the given index.
@ -99,11 +86,6 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (nullable id<ASLayoutable>)childForIndex:(NSUInteger)index; - (nullable id<ASLayoutable>)childForIndex:(NSUInteger)index;
/**
* Returns all children added to this layout spec.
*/
- (nullable NSArray<id<ASLayoutable>> *)children;
@end @end
@interface ASLayoutSpec (Debugging) <ASLayoutableAsciiArtProtocol> @interface ASLayoutSpec (Debugging) <ASLayoutableAsciiArtProtocol>

View File

@ -117,7 +117,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - ASStaticLayoutable #pragma mark - ASStaticLayoutable
/** /**
If specified, the child's size is restricted according to this size. Percentages are resolved relative to the static layout spec. If specified, the child's size is restricted according to this size. Fractions are resolved relative to the static layout spec.
*/ */
@property (nonatomic, assign) ASRelativeSizeRange sizeRange; @property (nonatomic, assign) ASRelativeSizeRange sizeRange;

View File

@ -11,6 +11,7 @@
#import "ASRatioLayoutSpec.h" #import "ASRatioLayoutSpec.h"
#import <algorithm> #import <algorithm>
#import <tgmath.h>
#import <vector> #import <vector>
#import "ASAssert.h" #import "ASAssert.h"
@ -64,7 +65,7 @@
// Choose the size closest to the desired ratio. // Choose the size closest to the desired ratio.
const auto &bestSize = std::max_element(sizeOptions.begin(), sizeOptions.end(), [&](const CGSize &a, const CGSize &b){ const auto &bestSize = std::max_element(sizeOptions.begin(), sizeOptions.end(), [&](const CGSize &a, const CGSize &b){
return fabs((a.height / a.width) - _ratio) > fabs((b.height / b.width) - _ratio); return std::fabs((a.height / a.width) - _ratio) > std::fabs((b.height / b.width) - _ratio);
}); });
// If there is no max size in *either* dimension, we can't apply the ratio, so just pass our size range through. // If there is no max size in *either* dimension, we can't apply the ratio, so just pass our size range through.

View File

@ -39,11 +39,11 @@ NS_ASSUME_NONNULL_BEGIN
extern ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height); extern ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height);
/** Convenience constructor to provide size in Points. */ /** Convenience constructor to provide size in points. */
extern ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size); extern ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size);
/** Convenience constructor to provide size in Percentage. */ /** Convenience constructor to provide size as a fraction. */
extern ASRelativeSize ASRelativeSizeMakeWithPercent(CGFloat percent); extern ASRelativeSize ASRelativeSizeMakeWithFraction(CGFloat fraction);
/** Resolve this relative size relative to a parent size. */ /** Resolve this relative size relative to a parent size. */
extern CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize); extern CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize);
@ -61,7 +61,7 @@ extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelati
extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact); extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact);
extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactPercent(CGFloat percent); extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction);
extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth,
ASRelativeDimension exactHeight); ASRelativeDimension exactHeight);

View File

@ -25,10 +25,10 @@ ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size)
ASRelativeDimensionMakeWithPoints(size.height)); ASRelativeDimensionMakeWithPoints(size.height));
} }
ASRelativeSize ASRelativeSizeMakeWithPercent(CGFloat percent) ASRelativeSize ASRelativeSizeMakeWithFraction(CGFloat fraction)
{ {
return ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(percent), return ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(fraction),
ASRelativeDimensionMakeWithPercent(percent)); ASRelativeDimensionMakeWithFraction(fraction));
} }
CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize) CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize)
@ -67,9 +67,9 @@ ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact)
return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact)); return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact));
} }
ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactPercent(CGFloat percent) ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction)
{ {
return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithPercent(percent)); return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithFraction(fraction));
} }
ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth,

View File

@ -85,4 +85,4 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ASStaticLayoutable @protocol ASStaticLayoutable
/** /**
If specified, the child's size is restricted according to this size. Percentages are resolved relative to the static layout spec. If specified, the child's size is restricted according to this size. Fractions are resolved relative to the static layout spec.
*/ */
@property (nonatomic, assign) ASRelativeSizeRange sizeRange; @property (nonatomic, assign) ASRelativeSizeRange sizeRange;

View File

@ -18,4 +18,4 @@
- (BOOL)isCancelled; - (BOOL)isCancelled;
- (void)cancel; - (void)cancel;
@end @end

View File

@ -95,7 +95,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *nodes, NS
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread. * data stores before entering into editing the backing store on a background thread.
*/ */
- (void)prepareForReloadData; - (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount;
/** /**
* Notifies the subclass that the data controller is about to reload its data entirely * Notifies the subclass that the data controller is about to reload its data entirely
@ -104,7 +104,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *nodes, NS
* concrete implementation. This is a great place to perform new node creation like supplementary views * concrete implementation. This is a great place to perform new node creation like supplementary views
* or header/footer nodes. * or header/footer nodes.
*/ */
- (void)willReloadData; - (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount;
/** /**
* Notifies the subclass to perform setup before sections are inserted in the data controller * Notifies the subclass to perform setup before sections are inserted in the data controller

View File

@ -344,7 +344,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
// The node is loaded but we're not on main. // The node is loaded but we're not on main.
// We will call [self __setNeedsLayout] when we apply // We will call [self __setNeedsLayout] when we apply
// the pending state. We need to call it on main if the node is loaded // the pending state. We need to call it on main if the node is loaded
// to support implicit hierarchy management. // to support automatic subnode management.
[ASDisplayNodeGetPendingState(self) setNeedsLayout]; [ASDisplayNodeGetPendingState(self) setNeedsLayout];
} else { } else {
// The node is not loaded and we're not on main. // The node is not loaded and we're not on main.

View File

@ -21,8 +21,6 @@
#import "ASLayoutTransition.h" #import "ASLayoutTransition.h"
#import "ASEnvironment.h" #import "ASEnvironment.h"
#include <vector>
@protocol _ASDisplayLayerDelegate; @protocol _ASDisplayLayerDelegate;
@class _ASDisplayLayer; @class _ASDisplayLayer;
@class _ASPendingState; @class _ASPendingState;
@ -85,10 +83,10 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
// Prevent calling setNeedsDisplay on a layer that backs a UIImageView. Usually calling setNeedsDisplay on a CALayer // Prevent calling setNeedsDisplay on a layer that backs a UIImageView. Usually calling setNeedsDisplay on a CALayer
// triggers a recreation of the contents of layer unfortunately calling it on a CALayer that backs a UIImageView // triggers a recreation of the contents of layer unfortunately calling it on a CALayer that backs a UIImageView
// it goes trough the normal flow to assign the contents to a layer via the CALayerDelegate methods. Unfortunately // it goes through the normal flow to assign the contents to a layer via the CALayerDelegate methods. Unfortunately
// UIImageView does not do recreate the layer contents the usual way, it actually does not implement some of the // UIImageView does not do recreate the layer contents the usual way, it actually does not implement some of the
// methods at all instead it throws away the contents of the layer and nothing will show up. // methods at all instead it throws away the contents of the layer and nothing will show up.
unsigned canCallNeedsDisplayOfLayer:1; unsigned canCallSetNeedsDisplayOfLayer:1;
// whether custom drawing is enabled // whether custom drawing is enabled
unsigned implementsInstanceDrawRect:1; unsigned implementsInstanceDrawRect:1;
@ -124,7 +122,10 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
// Main thread only // Main thread only
_ASTransitionContext *_pendingLayoutTransitionContext; _ASTransitionContext *_pendingLayoutTransitionContext;
BOOL _usesImplicitHierarchyManagement; BOOL _automaticallyManagesSubnodes;
NSTimeInterval _defaultLayoutTransitionDuration;
NSTimeInterval _defaultLayoutTransitionDelay;
UIViewAnimationOptions _defaultLayoutTransitionOptions;
int32_t _pendingTransitionID; int32_t _pendingTransitionID;
ASLayoutTransition *_pendingLayoutTransition; ASLayoutTransition *_pendingLayoutTransition;

View File

@ -20,7 +20,7 @@ ASDISPLAYNODE_EXTERN_C_BEGIN
@param sourceImageSize The size of the encoded image. @param sourceImageSize The size of the encoded image.
@param boundsSize The bounds in which the image will be displayed. @param boundsSize The bounds in which the image will be displayed.
@param contentMode The mode that defines how image will be scaled and cropped to fit. Supported values are UIViewContentModeScaleToAspectFill and UIViewContentModeScaleToAspectFit. @param contentMode The mode that defines how image will be scaled and cropped to fit. Supported values are UIViewContentModeScaleToAspectFill and UIViewContentModeScaleToAspectFit.
@param cropRect A rectangle that is to be featured by the cropped image. The rectangle is specified as a "unit rectangle," using percentages of the source image's width and height, e.g. CGRectMake(0.5, 0, 0.5, 1.0) will feature the full right half a photo. If the cropRect is empty, the contentMode will be used to determine the drawRect's size, and only the cropRect's origin will be used for positioning. @param cropRect A rectangle that is to be featured by the cropped image. The rectangle is specified as a "unit rectangle," using fractions of the source image's width and height, e.g. CGRectMake(0.5, 0, 0.5, 1.0) will feature the full right half a photo. If the cropRect is empty, the contentMode will be used to determine the drawRect's size, and only the cropRect's origin will be used for positioning.
@param forceUpscaling A boolean that indicates you would *not* like the backing size to be downscaled if the image is smaller than the destination size. Setting this to YES will result in higher memory usage when images are smaller than their destination. @param forceUpscaling A boolean that indicates you would *not* like the backing size to be downscaled if the image is smaller than the destination size. Setting this to YES will result in higher memory usage when images are smaller than their destination.
@discussion If the image is smaller than the size and UIViewContentModeScaleToAspectFill is specified, we suggest the input size so it will be efficiently upscaled on the GPU by the displaying layer at composite time. @discussion If the image is smaller than the size and UIViewContentModeScaleToAspectFill is specified, we suggest the input size so it will be efficiently upscaled on the GPU by the displaying layer at composite time.
*/ */

View File

@ -9,6 +9,7 @@
// //
#import "ASImageNode+CGExtras.h" #import "ASImageNode+CGExtras.h"
#import <tgmath.h>
// TODO rewrite these to be closer to the intended use -- take UIViewContentMode as param, CGRect destinationBounds, CGSize sourceSize. // TODO rewrite these to be closer to the intended use -- take UIViewContentMode as param, CGRect destinationBounds, CGSize sourceSize.
static CGSize _ASSizeFillWithAspectRatio(CGFloat aspectRatio, CGSize constraints); static CGSize _ASSizeFillWithAspectRatio(CGFloat aspectRatio, CGSize constraints);
@ -20,7 +21,7 @@ static CGSize _ASSizeFillWithAspectRatio(CGFloat sizeToScaleAspectRatio, CGSize
if (sizeToScaleAspectRatio > destinationAspectRatio) { if (sizeToScaleAspectRatio > destinationAspectRatio) {
return CGSizeMake(destinationSize.height * sizeToScaleAspectRatio, destinationSize.height); return CGSizeMake(destinationSize.height * sizeToScaleAspectRatio, destinationSize.height);
} else { } else {
return CGSizeMake(destinationSize.width, roundf(destinationSize.width / sizeToScaleAspectRatio)); return CGSizeMake(destinationSize.width, round(destinationSize.width / sizeToScaleAspectRatio));
} }
} }
@ -49,7 +50,7 @@ void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize,
// Often, an image is too low resolution to completely fill the width and height provided. // Often, an image is too low resolution to completely fill the width and height provided.
// Per the API contract as commented in the header, we will adjust input parameters (destinationWidth, destinationHeight) to ensure that the image is not upscaled on the CPU. // Per the API contract as commented in the header, we will adjust input parameters (destinationWidth, destinationHeight) to ensure that the image is not upscaled on the CPU.
CGFloat boundsAspectRatio = (float)destinationWidth / (float)destinationHeight; CGFloat boundsAspectRatio = (CGFloat)destinationWidth / (CGFloat)destinationHeight;
CGSize scaledSizeForImage = sourceImageSize; CGSize scaledSizeForImage = sourceImageSize;
BOOL cropToRectDimensions = !CGRectIsEmpty(cropRect); BOOL cropToRectDimensions = !CGRectIsEmpty(cropRect);
@ -66,8 +67,8 @@ void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize,
// If fitting the desired aspect ratio to the image size actually results in a larger buffer, use the input values. // If fitting the desired aspect ratio to the image size actually results in a larger buffer, use the input values.
// However, if there is a pixel savings (e.g. we would have to upscale the image), overwrite the function arguments. // However, if there is a pixel savings (e.g. we would have to upscale the image), overwrite the function arguments.
if (forceUpscaling == NO && (scaledSizeForImage.width * scaledSizeForImage.height) < (destinationWidth * destinationHeight)) { if (forceUpscaling == NO && (scaledSizeForImage.width * scaledSizeForImage.height) < (destinationWidth * destinationHeight)) {
destinationWidth = (size_t)roundf(scaledSizeForImage.width); destinationWidth = (size_t)round(scaledSizeForImage.width);
destinationHeight = (size_t)roundf(scaledSizeForImage.height); destinationHeight = (size_t)round(scaledSizeForImage.height);
if (destinationWidth == 0 || destinationHeight == 0) { if (destinationWidth == 0 || destinationHeight == 0) {
*outBackingSize = CGSizeZero; *outBackingSize = CGSizeZero;
*outDrawRect = CGRectZero; *outDrawRect = CGRectZero;

View File

@ -16,6 +16,7 @@
#import "ASLayout.h" #import "ASLayout.h"
#import <queue> #import <queue>
#import <memory>
#import "NSArray+Diffing.h" #import "NSArray+Diffing.h"
#import "ASEqualityHelpers.h" #import "ASEqualityHelpers.h"
@ -47,7 +48,8 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
} }
@implementation ASLayoutTransition { @implementation ASLayoutTransition {
ASDN::RecursiveMutex __instanceLock__; std::shared_ptr<ASDN::RecursiveMutex> __instanceLock__;
BOOL _calculatedSubnodeOperations; BOOL _calculatedSubnodeOperations;
NSArray<ASDisplayNode *> *_insertedSubnodes; NSArray<ASDisplayNode *> *_insertedSubnodes;
NSArray<ASDisplayNode *> *_removedSubnodes; NSArray<ASDisplayNode *> *_removedSubnodes;
@ -61,6 +63,8 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
{ {
self = [super init]; self = [super init];
if (self) { if (self) {
__instanceLock__ = std::make_shared<ASDN::RecursiveMutex>();
_node = node; _node = node;
_pendingLayout = pendingLayout; _pendingLayout = pendingLayout;
_previousLayout = previousLayout; _previousLayout = previousLayout;
@ -70,7 +74,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
- (BOOL)isSynchronous - (BOOL)isSynchronous
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexSharedLocker l(__instanceLock__);
return !ASLayoutCanTransitionAsynchronous(_pendingLayout); return !ASLayoutCanTransitionAsynchronous(_pendingLayout);
} }
@ -82,7 +86,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
- (void)applySubnodeInsertions - (void)applySubnodeInsertions
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexSharedLocker l(__instanceLock__);
[self calculateSubnodeOperationsIfNeeded]; [self calculateSubnodeOperationsIfNeeded];
NSUInteger i = 0; NSUInteger i = 0;
@ -95,7 +99,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
- (void)applySubnodeRemovals - (void)applySubnodeRemovals
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexSharedLocker l(__instanceLock__);
[self calculateSubnodeOperationsIfNeeded]; [self calculateSubnodeOperationsIfNeeded];
for (ASDisplayNode *subnode in _removedSubnodes) { for (ASDisplayNode *subnode in _removedSubnodes) {
[subnode removeFromSupernode]; [subnode removeFromSupernode];
@ -104,7 +108,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
- (void)calculateSubnodeOperationsIfNeeded - (void)calculateSubnodeOperationsIfNeeded
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexSharedLocker l(__instanceLock__);
if (_calculatedSubnodeOperations) { if (_calculatedSubnodeOperations) {
return; return;
} }
@ -134,27 +138,27 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
- (NSArray<ASDisplayNode *> *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context - (NSArray<ASDisplayNode *> *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexSharedLocker l(__instanceLock__);
return _node.subnodes; return _node.subnodes;
} }
- (NSArray<ASDisplayNode *> *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context - (NSArray<ASDisplayNode *> *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexSharedLocker l(__instanceLock__);
[self calculateSubnodeOperationsIfNeeded]; [self calculateSubnodeOperationsIfNeeded];
return _insertedSubnodes; return _insertedSubnodes;
} }
- (NSArray<ASDisplayNode *> *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context - (NSArray<ASDisplayNode *> *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexSharedLocker l(__instanceLock__);
[self calculateSubnodeOperationsIfNeeded]; [self calculateSubnodeOperationsIfNeeded];
return _removedSubnodes; return _removedSubnodes;
} }
- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key - (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexSharedLocker l(__instanceLock__);
if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { if ([key isEqualToString:ASTransitionContextFromLayoutKey]) {
return _previousLayout; return _previousLayout;
} else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) {
@ -166,7 +170,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key - (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexSharedLocker l(__instanceLock__);
if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { if ([key isEqualToString:ASTransitionContextFromLayoutKey]) {
return _previousLayout.constrainedSizeRange; return _previousLayout.constrainedSizeRange;
} else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) {

View File

@ -10,6 +10,8 @@
#import "ASStackPositionedLayout.h" #import "ASStackPositionedLayout.h"
#import <tgmath.h>
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASLayoutSpecUtilities.h" #import "ASLayoutSpecUtilities.h"
@ -105,16 +107,16 @@ ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnposition
case ASStackLayoutJustifyContentStart: case ASStackLayoutJustifyContentStart:
return stackedLayout(style, 0, unpositionedLayout, constrainedSize); return stackedLayout(style, 0, unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentCenter: case ASStackLayoutJustifyContentCenter:
return stackedLayout(style, floorf(violation / 2), unpositionedLayout, constrainedSize); return stackedLayout(style, std::floor(violation / 2), unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentEnd: case ASStackLayoutJustifyContentEnd:
return stackedLayout(style, violation, unpositionedLayout, constrainedSize); return stackedLayout(style, violation, unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentSpaceBetween: { case ASStackLayoutJustifyContentSpaceBetween: {
const auto numOfSpacings = numOfItems - 1; const auto numOfSpacings = numOfItems - 1;
return stackedLayout(style, 0, floorf(violation / numOfSpacings), fmodf(violation, numOfSpacings), unpositionedLayout, constrainedSize); return stackedLayout(style, 0, std::floor(violation / numOfSpacings), std::fmod(violation, numOfSpacings), unpositionedLayout, constrainedSize);
} }
case ASStackLayoutJustifyContentSpaceAround: { case ASStackLayoutJustifyContentSpaceAround: {
// Spacing between items are twice the spacing on the edges // Spacing between items are twice the spacing on the edges
CGFloat spacingUnit = floorf(violation / (numOfItems * 2)); CGFloat spacingUnit = std::floor(violation / (numOfItems * 2));
return stackedLayout(style, spacingUnit, spacingUnit * 2, 0, unpositionedLayout, constrainedSize); return stackedLayout(style, spacingUnit, spacingUnit * 2, 0, unpositionedLayout, constrainedSize);
} }
} }

View File

@ -10,6 +10,7 @@
#import "ASStackUnpositionedLayout.h" #import "ASStackUnpositionedLayout.h"
#import <tgmath.h>
#import <numeric> #import <numeric>
#import "ASLayoutSpecUtilities.h" #import "ASLayoutSpecUtilities.h"
@ -87,7 +88,7 @@ static void stretchChildrenAlongCrossDimension(std::vector<ASStackUnpositionedIt
// restretch all stretchable children along the cross axis using the new min. set their max size to childCrossMax, // restretch all stretchable children along the cross axis using the new min. set their max size to childCrossMax,
// not crossMax, so that if any of them would choose a larger size just because the min size increased (weird!) // not crossMax, so that if any of them would choose a larger size just because the min size increased (weird!)
// they are forced to choose the same width as all the other children. // they are forced to choose the same width as all the other children.
if (alignItems == ASStackLayoutAlignItemsStretch && fabs(cross - childCrossMax) > 0.01) { if (alignItems == ASStackLayoutAlignItemsStretch && std::fabs(cross - childCrossMax) > 0.01) {
l.layout = crossChildLayout(child, style, stack, stack, childCrossMax, childCrossMax); l.layout = crossChildLayout(child, style, stack, stack, childCrossMax, childCrossMax);
} }
} }
@ -182,7 +183,7 @@ static const CGFloat kViolationEpsilon = 0.01;
*/ */
static std::function<BOOL(const ASStackUnpositionedItem &)> isFlexibleInViolationDirection(const CGFloat violation) static std::function<BOOL(const ASStackUnpositionedItem &)> isFlexibleInViolationDirection(const CGFloat violation)
{ {
if (fabs(violation) < kViolationEpsilon) { if (std::fabs(violation) < kViolationEpsilon) {
return [](const ASStackUnpositionedItem &l) { return NO; }; return [](const ASStackUnpositionedItem &l) { return NO; };
} else if (violation > 0) { } else if (violation > 0) {
return [](const ASStackUnpositionedItem &l) { return l.child.flexGrow; }; return [](const ASStackUnpositionedItem &l) { return l.child.flexGrow; };
@ -263,7 +264,7 @@ static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem>
} }
// Each flexible child along the direction of the violation is expanded or contracted equally // Each flexible child along the direction of the violation is expanded or contracted equally
const CGFloat violationPerFlexChild = floorf(violation / flexibleChildren); const CGFloat violationPerFlexChild = std::floor(violation / flexibleChildren);
// If the floor operation above left a remainder we may have a remainder after deducting the adjustments from all the // If the floor operation above left a remainder we may have a remainder after deducting the adjustments from all the
// contributions of the flexible children. // contributions of the flexible children.
const CGFloat violationRemainder = violation - (violationPerFlexChild * flexibleChildren); const CGFloat violationRemainder = violation - (violationPerFlexChild * flexibleChildren);

View File

@ -32,8 +32,8 @@ extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UI
UIEdgeInsets insets = [image capInsets]; UIEdgeInsets insets = [image capInsets];
// These are lifted from what UIImageView does by experimentation. Without these exact values, the stretching is slightly off. // These are lifted from what UIImageView does by experimentation. Without these exact values, the stretching is slightly off.
const float halfPixelFudge = 0.49f; const CGFloat halfPixelFudge = 0.49f;
const float otherPixelFudge = 0.02f; const CGFloat otherPixelFudge = 0.02f;
// Convert to unit coordinates for the contentsCenter property. // Convert to unit coordinates for the contentsCenter property.
CGRect contentsCenter = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); CGRect contentsCenter = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
if (insets.left > 0 || insets.right > 0) { if (insets.left > 0 || insets.right > 0) {

View File

@ -15,6 +15,7 @@
#import "NSIndexSet+ASHelpers.h" #import "NSIndexSet+ASHelpers.h"
#import "ASAssert.h" #import "ASAssert.h"
#import "ASDisplayNode+Beta.h" #import "ASDisplayNode+Beta.h"
#import <unordered_map> #import <unordered_map>
#define ASFailUpdateValidation(...)\ #define ASFailUpdateValidation(...)\

View File

@ -168,4 +168,4 @@ namespace ASTupleOperations
} }
}; };
} }

View File

@ -10,13 +10,14 @@
#import "ASTextKitContext.h" #import "ASTextKitContext.h"
#import "ASLayoutManager.h" #import "ASLayoutManager.h"
#import "ASThread.h"
#import <mutex> #include <memory>
@implementation ASTextKitContext @implementation ASTextKitContext
{ {
// All TextKit operations (even non-mutative ones) must be executed serially. // All TextKit operations (even non-mutative ones) must be executed serially.
std::mutex _textKitMutex; std::shared_ptr<ASDN::Mutex> __instanceLock__;
NSLayoutManager *_layoutManager; NSLayoutManager *_layoutManager;
NSTextStorage *_textStorage; NSTextStorage *_textStorage;
@ -35,8 +36,11 @@
{ {
if (self = [super init]) { if (self = [super init]) {
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock.
static std::mutex __static_mutex; static ASDN::Mutex __staticMutex;
std::lock_guard<std::mutex> l(__static_mutex); ASDN::MutexLocker l(__staticMutex);
__instanceLock__ = std::make_shared<ASDN::Mutex>();
// Create the TextKit component stack with our default configuration. // Create the TextKit component stack with our default configuration.
if (textStorageCreationBlock) { if (textStorageCreationBlock) {
_textStorage = textStorageCreationBlock(attributedString); _textStorage = textStorageCreationBlock(attributedString);
@ -60,13 +64,13 @@
- (CGSize)constrainedSize - (CGSize)constrainedSize
{ {
std::lock_guard<std::mutex> l(_textKitMutex); ASDN::MutexSharedLocker l(__instanceLock__);
return _textContainer.size; return _textContainer.size;
} }
- (void)setConstrainedSize:(CGSize)constrainedSize - (void)setConstrainedSize:(CGSize)constrainedSize
{ {
std::lock_guard<std::mutex> l(_textKitMutex); ASDN::MutexSharedLocker l(__instanceLock__);
_textContainer.size = constrainedSize; _textContainer.size = constrainedSize;
} }
@ -74,7 +78,7 @@
NSTextStorage *, NSTextStorage *,
NSTextContainer *))block NSTextContainer *))block
{ {
std::lock_guard<std::mutex> l(_textKitMutex); ASDN::MutexSharedLocker l(__instanceLock__);
if (block) { if (block) {
block(_layoutManager, _textStorage, _textContainer); block(_layoutManager, _textStorage, _textContainer);
} }

View File

@ -8,12 +8,15 @@
// of patent rights can be found in the PATENTS file in the same directory. // of patent rights can be found in the PATENTS file in the same directory.
// //
#import "ASTextKitContext.h"
#import "ASTextKitFontSizeAdjuster.h"
#import "ASLayoutManager.h"
#import "ASTextKitFontSizeAdjuster.h"
#import <tgmath.h>
#import <mutex> #import <mutex>
#import "ASTextKitContext.h"
#import "ASLayoutManager.h"
//#define LOG(...) NSLog(__VA_ARGS__) //#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...) #define LOG(...)
@ -48,7 +51,7 @@
[attrString enumerateAttributesInRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { [attrString enumerateAttributesInRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
if (attrs[NSFontAttributeName] != nil) { if (attrs[NSFontAttributeName] != nil) {
UIFont *font = attrs[NSFontAttributeName]; UIFont *font = attrs[NSFontAttributeName];
font = [font fontWithSize:roundf(font.pointSize * scaleFactor)]; font = [font fontWithSize:std::round(font.pointSize * scaleFactor)];
[attrString removeAttribute:NSFontAttributeName range:range]; [attrString removeAttribute:NSFontAttributeName range:range];
[attrString addAttribute:NSFontAttributeName value:font range:range]; [attrString addAttribute:NSFontAttributeName value:font range:range];
} }
@ -166,7 +169,7 @@
// adjust here so we start at the proper place in our scale array if we have too many lines // adjust here so we start at the proper place in our scale array if we have too many lines
scaleIndex++; scaleIndex++;
if (ceilf(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) { if (std::ceil(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) {
// we fit! we are done // we fit! we are done
break; break;
} }

View File

@ -11,6 +11,7 @@
#import "ASTextKitRenderer+Positioning.h" #import "ASTextKitRenderer+Positioning.h"
#import <CoreText/CoreText.h> #import <CoreText/CoreText.h>
#import <tgmath.h>
#import "ASAssert.h" #import "ASAssert.h"
@ -163,7 +164,7 @@ static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3;
[self enumerateTextIndexesAtPosition:position usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { [self enumerateTextIndexesAtPosition:position usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) {
CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect)); CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect));
CGFloat currentDistance = sqrtf(powf(position.x - glyphLocation.x, 2.f) + powf(position.y - glyphLocation.y, 2.f)); CGFloat currentDistance = std::sqrt(std::pow(position.x - glyphLocation.x, 2.f) + std::pow(position.y - glyphLocation.y, 2.f));
if (currentDistance < minimumGlyphDistance) { if (currentDistance < minimumGlyphDistance) {
minimumGlyphDistance = currentDistance; minimumGlyphDistance = currentDistance;
minimumGlyphCharacterIndex = characterIndex; minimumGlyphCharacterIndex = characterIndex;

View File

@ -10,6 +10,8 @@
#import "ASTextKitShadower.h" #import "ASTextKitShadower.h"
#import <tgmath.h>
static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets) static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets)
{ {
return UIEdgeInsetsInsetRect({.size = size}, insets).size; return UIEdgeInsetsInsetRect({.size = size}, insets).size;
@ -87,11 +89,11 @@ static inline UIEdgeInsets _invertInsets(UIEdgeInsets insets)
// min values are expected to be negative for most typical shadowOffset and // min values are expected to be negative for most typical shadowOffset and
// blurRadius settings: // blurRadius settings:
shadowPadding.top = fminf(0.0f, _shadowOffset.height - _shadowRadius); shadowPadding.top = std::fmin(0.0f, _shadowOffset.height - _shadowRadius);
shadowPadding.left = fminf(0.0f, _shadowOffset.width - _shadowRadius); shadowPadding.left = std::fmin(0.0f, _shadowOffset.width - _shadowRadius);
shadowPadding.bottom = fminf(0.0f, -_shadowOffset.height - _shadowRadius); shadowPadding.bottom = std::fmin(0.0f, -_shadowOffset.height - _shadowRadius);
shadowPadding.right = fminf(0.0f, -_shadowOffset.width - _shadowRadius); shadowPadding.right = std::fmin(0.0f, -_shadowOffset.width - _shadowRadius);
_calculatedShadowPadding = shadowPadding; _calculatedShadowPadding = shadowPadding;
} }

View File

@ -32,4 +32,4 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -128,4 +128,4 @@
return result; return result;
} }
@end @end

View File

@ -11,7 +11,7 @@
// //
#import "_ASTransitionContext.h" #import "_ASTransitionContext.h"
#import "ASDisplayNode.h"
#import "ASLayout.h" #import "ASLayout.h"

View File

@ -45,7 +45,7 @@
}]; }];
#pragma clang diagnostic pop #pragma clang diagnostic pop
[self waitForExpectationsWithTimeout:3 handler:nil]; [self waitForExpectationsWithTimeout:30 handler:nil];
} }
@end @end

View File

@ -294,34 +294,6 @@
collectionView.asyncDelegate = nil; collectionView.asyncDelegate = nil;
} }
#pragma mark - #collectionView:numberOfSectionsForSupplementaryNodeOfKind:
- (void)testThatItRespondsWithTheDefaultNumberOfSections
{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
XCTAssert(sections == 1, @"should return 1 by default");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItProvidesTheNumberOfSectionsInTheDataSource
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
XCTAssert(sections == 2, @"should return 2");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
#pragma mark - #collectionView:supplementaryNodesOfKind:inSection: #pragma mark - #collectionView:supplementaryNodesOfKind:inSection:
- (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheDelegate - (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheDelegate

View File

@ -47,28 +47,13 @@
@implementation ASDisplayNodeImplicitHierarchyTests @implementation ASDisplayNodeImplicitHierarchyTests
- (void)setUp {
[super setUp];
[ASDisplayNode setUsesImplicitHierarchyManagement:YES];
}
- (void)tearDown {
[ASDisplayNode setUsesImplicitHierarchyManagement:NO];
[super tearDown];
}
- (void)testFeatureFlag - (void)testFeatureFlag
{ {
XCTAssert([ASDisplayNode usesImplicitHierarchyManagement]);
ASDisplayNode *node = [[ASDisplayNode alloc] init]; ASDisplayNode *node = [[ASDisplayNode alloc] init];
XCTAssert(node.usesImplicitHierarchyManagement); XCTAssertFalse(node.automaticallyManagesSubnodes);
[ASDisplayNode setUsesImplicitHierarchyManagement:NO]; node.automaticallyManagesSubnodes = YES;
XCTAssertFalse([ASDisplayNode usesImplicitHierarchyManagement]); XCTAssertTrue(node.automaticallyManagesSubnodes);
XCTAssertFalse(node.usesImplicitHierarchyManagement);
node.usesImplicitHierarchyManagement = YES;
XCTAssert(node.usesImplicitHierarchyManagement);
} }
- (void)testInitialNodeInsertionWithOrdering - (void)testInitialNodeInsertionWithOrdering
@ -80,6 +65,7 @@
ASDisplayNode *node5 = [[ASDisplayNode alloc] init]; ASDisplayNode *node5 = [[ASDisplayNode alloc] init];
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
ASStaticLayoutSpec *staticLayout = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node4]]; ASStaticLayoutSpec *staticLayout = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node4]];
@ -106,6 +92,7 @@
ASDisplayNode *node3 = [[ASDisplayNode alloc] init]; ASDisplayNode *node3 = [[ASDisplayNode alloc] init];
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize){ node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize){
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
if ([strongNode.layoutState isEqualToNumber:@1]) { if ([strongNode.layoutState isEqualToNumber:@1]) {
@ -136,6 +123,7 @@
ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
if ([strongNode.layoutState isEqualToNumber:@1]) { if ([strongNode.layoutState isEqualToNumber:@1]) {
@ -179,6 +167,7 @@
ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
@ -190,6 +179,8 @@
}; };
// Intentionally trigger view creation // Intentionally trigger view creation
[node view];
[node1 view];
[node2 view]; [node2 view];
XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"]; XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"];
@ -199,7 +190,7 @@
node.layoutState = @2; node.layoutState = @2;
[node invalidateCalculatedLayout]; [node invalidateCalculatedLayout];
[node transitionLayoutWithAnimation:YES shouldMeasureAsync:YES measurementCompletion:^{ [node transitionLayoutAnimated:YES measurementCompletion:^{
// Push this to the next runloop to let async insertion / removing of nodes finished before checking // Push this to the next runloop to let async insertion / removing of nodes finished before checking
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
XCTAssertEqual(node.subnodes[0], node2); XCTAssertEqual(node.subnodes[0], node2);

View File

@ -0,0 +1,36 @@
//
// ASDisplayNodeSnapshotTests.m
// AsyncDisplayKit
//
// Created by Adlai Holler on 8/16/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import "ASSnapshotTestCase.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface ASDisplayNodeSnapshotTests : ASSnapshotTestCase
@end
@implementation ASDisplayNodeSnapshotTests
- (void)testBasicHierarchySnapshotTesting
{
ASDisplayNode *node = [[ASDisplayNode alloc] init];
node.backgroundColor = [UIColor blueColor];
ASTextNode *subnode = [[ASTextNode alloc] init];
subnode.backgroundColor = [UIColor whiteColor];
subnode.attributedText = [[NSAttributedString alloc] initWithString:@"Hello"];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(5, 5, 5, 5) child:subnode];
};
[node measure:CGSizeMake(100, 100)];
ASSnapshotVerifyNode(node, nil);
}
@end

View File

@ -29,7 +29,7 @@
// trivial test case to ensure ASSnapshotTestCase works // trivial test case to ensure ASSnapshotTestCase works
ASImageNode *imageNode = [[ASImageNode alloc] init]; ASImageNode *imageNode = [[ASImageNode alloc] init];
imageNode.image = [self testImage]; imageNode.image = [self testImage];
imageNode.frame = CGRectMake(0, 0, 100, 100); [imageNode measure:CGSizeMake(100, 100)];
ASSnapshotVerifyNode(imageNode, nil); ASSnapshotVerifyNode(imageNode, nil);
} }

View File

@ -15,7 +15,7 @@
#import "ASLayout.h" #import "ASLayout.h"
@interface ASTestNode : ASDisplayNode @interface ASTestNode : ASDisplayNode
- (void)setLayoutSpecUnderTest:(ASLayoutSpec *)layoutSpecUnderTest sizeRange:(ASSizeRange)sizeRange; @property (strong, nonatomic, nullable) ASLayoutSpec *layoutSpecUnderTest;
@end @end
@implementation ASLayoutSpecSnapshotTestCase @implementation ASLayoutSpecSnapshotTestCase
@ -37,18 +37,15 @@
[node addSubnode:subnode]; [node addSubnode:subnode];
} }
[node setLayoutSpecUnderTest:layoutSpec sizeRange:sizeRange]; node.layoutSpecUnderTest = layoutSpec;
[node measureWithSizeRange:sizeRange];
ASSnapshotVerifyNode(node, identifier); ASSnapshotVerifyNode(node, identifier);
} }
@end @end
@implementation ASTestNode @implementation ASTestNode
{
ASLayout *_layoutUnderTest;
}
- (instancetype)init - (instancetype)init
{ {
if (self = [super init]) { if (self = [super init]) {
@ -57,22 +54,9 @@
return self; return self;
} }
- (void)setLayoutSpecUnderTest:(ASLayoutSpec *)layoutSpecUnderTest sizeRange:(ASSizeRange)sizeRange - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{ {
ASLayout *layout = [layoutSpecUnderTest measureWithSizeRange:sizeRange]; return _layoutSpecUnderTest;
layout.position = CGPointZero;
layout = [ASLayout layoutWithLayoutableObject:self
constrainedSizeRange:sizeRange
size:layout.size
sublayouts:@[layout]];
_layoutUnderTest = [layout filteredNodeLayoutTree];
self.frame = CGRectMake(0, 0, _layoutUnderTest.size.width, _layoutUnderTest.size.height);
[self measure:_layoutUnderTest.size];
}
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
{
return _layoutUnderTest;
} }
@end @end

View File

@ -10,7 +10,7 @@
#import <FBSnapshotTestCase/FBSnapshotTestCase.h> #import <FBSnapshotTestCase/FBSnapshotTestCase.h>
#import <AsyncDisplayKit/ASDisplayNode.h> @class ASDisplayNode;
#define ASSnapshotVerifyNode(node__, identifier__) \ #define ASSnapshotVerifyNode(node__, identifier__) \
{ \ { \

View File

@ -0,0 +1,28 @@
//
// ASSnapshotTestCase.m
// AsyncDisplayKit
//
// 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 "ASSnapshotTestCase.h"
#import "ASDisplayNode+Beta.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+Subclasses.h"
@implementation ASSnapshotTestCase
+ (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node
{
ASDisplayNodeAssertNotNil(node.calculatedLayout, @"Node %@ must be measured before it is rendered.", node);
node.bounds = (CGRect) { .size = node.calculatedSize };
ASDisplayNodePerformBlockOnEveryNode(nil, node, ^(ASDisplayNode * _Nonnull node) {
[node.layer setNeedsDisplay];
});
[node recursivelyEnsureDisplaySynchronously:YES];
}
@end

View File

@ -1,57 +0,0 @@
//
// ASSnapshotTestCase.mm
// AsyncDisplayKit
//
// 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 "ASSnapshotTestCase.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNodeInternal.h"
@implementation ASSnapshotTestCase
+ (void)_layoutAndDisplayNode:(ASDisplayNode *)node
{
if (![node __shouldLoadViewOrLayer]) {
return;
}
CALayer *layer = node.layer;
[layer setNeedsLayout];
[layer layoutIfNeeded];
[layer setNeedsDisplay];
[layer displayIfNeeded];
}
+ (void)_recursivelyLayoutAndDisplayNode:(ASDisplayNode *)node
{
for (ASDisplayNode *subnode in node.subnodes) {
[self _recursivelyLayoutAndDisplayNode:subnode];
}
[self _layoutAndDisplayNode:node];
}
+ (void)_recursivelySetDisplaysAsynchronously:(BOOL)flag forNode:(ASDisplayNode *)node
{
node.displaysAsynchronously = flag;
for (ASDisplayNode *subnode in node.subnodes) {
[self _recursivelySetDisplaysAsynchronously:flag forNode:subnode];
}
}
+ (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node
{
// TODO: Reconfigure this to be able to use [node recursivelyEnsureDisplay];
[self _recursivelySetDisplaysAsynchronously:NO forNode:node];
[self _recursivelyLayoutAndDisplayNode:node];
}
@end

View File

@ -330,7 +330,7 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
subnode2.staticSize = {50, 50}; subnode2.staticSize = {50, 50};
ASRatioLayoutSpec *child1 = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.5 child:subnode1]; ASRatioLayoutSpec *child1 = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.5 child:subnode1];
child1.flexBasis = ASRelativeDimensionMakeWithPercent(1); child1.flexBasis = ASRelativeDimensionMakeWithFraction(1);
child1.flexGrow = YES; child1.flexGrow = YES;
child1.flexShrink = YES; child1.flexShrink = YES;
@ -508,7 +508,7 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testStackLayoutSpecWithStyle:style sizeRange:kOverflowSize subnodes:subnodes identifier:@"overflow"]; [self testStackLayoutSpecWithStyle:style sizeRange:kOverflowSize subnodes:subnodes identifier:@"overflow"];
} }
- (void)testPercentageFlexBasisResolvesAgainstParentSize - (void)testFractionalFlexBasisResolvesAgainstParentSize
{ {
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
@ -520,7 +520,7 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
// This should override the intrinsic size of 50pts and instead compute to 50% = 100pts. // This should override the intrinsic size of 50pts and instead compute to 50% = 100pts.
// The result should be that the red box is twice as wide as the blue and gree boxes after flexing. // The result should be that the red box is twice as wide as the blue and gree boxes after flexing.
((ASStaticSizeDisplayNode *)subnodes[0]).flexBasis = ASRelativeDimensionMakeWithPercent(0.5); ((ASStaticSizeDisplayNode *)subnodes[0]).flexBasis = ASRelativeDimensionMakeWithFraction(0.5);
static ASSizeRange kSize = {{200, 0}, {200, INFINITY}}; static ASSizeRange kSize = {{200, 0}, {200, INFINITY}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];

Some files were not shown because too many files have changed in this diff Show More