diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index c833fccec0..7349557d8c 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -66,7 +66,7 @@ 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A36195D057000B7D73C /* ASTextNodeTests.m */; }; 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"; }; }; - 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, ); }; }; 18C2ED801B9B7DE800F627B3 /* 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, ); }; }; 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 */; }; + 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */; }; 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; 83A7D95A1D44542100BF333E /* 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 */; }; 9C70F20C1CDBE9B6007D6C76 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.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 */; }; 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; 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 */; }; 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, ); }; }; + CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */; }; CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; CC3B20851C3F76D600798563 /* 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 */; }; CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.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 */; }; 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, ); }; }; + CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.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 */; }; DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.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, ); }; }; DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; @@ -708,7 +710,6 @@ F7CE6C611D2CDB3E00BE4C15 /* ASLayoutable.h in CopyFiles */, F7CE6C621D2CDB3E00BE4C15 /* ASLayoutablePrivate.h in CopyFiles */, F7CE6C631D2CDB3E00BE4C15 /* ASLayoutSpec.h in CopyFiles */, - DE72E2AC1D4D90E000DDB258 /* (null) in CopyFiles */, F7CE6C641D2CDB3E00BE4C15 /* ASOverlayLayoutSpec.h in CopyFiles */, F7CE6C651D2CDB3E00BE4C15 /* ASRatioLayoutSpec.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 = ""; }; 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDealloc2MainObject.h; path = ../Details/ASDealloc2MainObject.h; sourceTree = ""; }; 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = ""; }; - 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASSnapshotTestCase.mm; sourceTree = ""; }; + 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASSnapshotTestCase.m; sourceTree = ""; }; 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = ""; }; 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = ""; }; 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = ""; }; @@ -977,6 +978,7 @@ 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ASConvenience.h"; sourceTree = ""; }; 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = ""; }; + 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeSnapshotTests.m; sourceTree = ""; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; 83A7D9581D44542100BF333E /* ASWeakMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakMap.h; sourceTree = ""; }; @@ -1075,6 +1077,7 @@ 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 = ""; }; 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 = ""; }; + CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASUICollectionViewTests.m; sourceTree = ""; }; CC3B20811C3F76D600798563 /* ASPendingStateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPendingStateController.h; sourceTree = ""; }; CC3B20821C3F76D600798563 /* ASPendingStateController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPendingStateController.mm; sourceTree = ""; }; CC3B20871C3F7A5400798563 /* ASWeakSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakSet.h; sourceTree = ""; }; @@ -1087,6 +1090,7 @@ CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = ""; }; CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = ""; }; CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = ""; }; + CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeSnapshotTests.m; sourceTree = ""; }; 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 = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; @@ -1320,6 +1324,8 @@ 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = { isa = PBXGroup; children = ( + CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */, + CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */, 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */, DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, @@ -1327,7 +1333,7 @@ CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */, 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, - 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */, + 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */, 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */, ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */, 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, @@ -1336,6 +1342,7 @@ ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */, AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */, + 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */, ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */, ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */, 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */, @@ -1443,6 +1450,10 @@ 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */, 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */, 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */, + CC3B20871C3F7A5400798563 /* ASWeakSet.h */, + CC3B20881C3F7A5400798563 /* ASWeakSet.m */, + DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, + DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, ); path = Details; sourceTree = ""; @@ -1511,10 +1522,6 @@ ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */, 83A7D9581D44542100BF333E /* ASWeakMap.h */, 83A7D9591D44542100BF333E /* ASWeakMap.m */, - CC3B20871C3F7A5400798563 /* ASWeakSet.h */, - CC3B20881C3F7A5400798563 /* ASWeakSet.m */, - DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, - DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */, 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */, ); @@ -2180,6 +2187,7 @@ 058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */, 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */, 058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */, + CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */, 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */, CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */, 058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */, @@ -2193,11 +2201,13 @@ 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */, ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, + CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */, ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, - 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */, + 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, + 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */, AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */, 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, @@ -2491,6 +2501,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.1; OTHER_CFLAGS = "-Wall"; @@ -2512,6 +2523,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.1; OTHER_CFLAGS = "-Wall"; @@ -2697,6 +2709,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.1; OTHER_CFLAGS = "-Wall"; diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index f1f40a33cc..6341f13732 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -14,7 +14,6 @@ #import "ASDisplayNode+Subclasses.h" #import "ASBackgroundLayoutSpec.h" #import "ASInsetLayoutSpec.h" -#import "ASDisplayNode+Beta.h" #import "ASStaticLayoutSpec.h" @interface ASButtonNode () @@ -56,7 +55,7 @@ - (instancetype)init { if (self = [super init]) { - self.usesImplicitHierarchyManagement = YES; + self.automaticallyManagesSubnodes = YES; _contentSpacing = 8.0; _laysOutHorizontally = YES; diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index 151578d735..c5cbda3794 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -115,13 +115,35 @@ [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; } -- (void)transitionLayoutWithAnimation:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion +- (void)transitionLayoutAnimated:(BOOL)animated + measurementCompletion:(void (^)())completion { CGSize oldSize = self.calculatedSize; - [super transitionLayoutWithAnimation:animated - shouldMeasureAsync:shouldMeasureAsync + [super transitionLayoutAnimated:animated + 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:^{ [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; if (completion) { @@ -131,22 +153,13 @@ ]; } +//Deprecated - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(void(^)())completion { - CGSize oldSize = self.calculatedSize; - [super transitionLayoutWithSizeRange:constrainedSize - animated:animated - shouldMeasureAsync:shouldMeasureAsync - measurementCompletion:^{ - [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; - if (completion) { - completion(); - } - } - ]; + [self transitionLayoutWithSizeRange:constrainedSize animated:animated measurementCompletion:completion]; } - (void)didRelayoutFromOldSize:(CGSize)oldSize toNewSize:(CGSize)newSize diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index c8d7aa2573..510ad4e50b 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -17,6 +17,7 @@ #import "ASEnvironmentInternal.h" #import "ASInternalHelpers.h" #import "ASCellNode+Internal.h" +#import "AsyncDisplayKit+Debug.h" #pragma mark - _ASCollectionPendingState @@ -171,6 +172,12 @@ [self.view clearFetchedData]; } +- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState +{ + [super interfaceStateDidChange:newState fromState:oldState]; + [ASRangeController layoutDebugOverlayIfNeeded]; +} + #if ASRangeControllerLoggingEnabled - (void)visibleStateDidChange:(BOOL)isVisible { diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 62f508f173..afe74f5ea8 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -400,17 +400,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (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. * 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 +/** + * 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 * at the given index path to the view hierarchy. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 5e71ffd40c..7ddcf1d5af 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -18,7 +18,6 @@ #import "ASCollectionViewFlowLayoutInspector.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNode+Beta.h" #import "ASInternalHelpers.h" #import "UICollectionViewLayout+ASConvenience.h" #import "ASRangeController.h" @@ -151,11 +150,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } _asyncDelegateFlags; struct { - unsigned int asyncDataSourceConstrainedSizeForNode:1; unsigned int asyncDataSourceNodeForItemAtIndexPath:1; unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1; unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1; - unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1; } _asyncDataSourceFlags; struct { @@ -354,11 +351,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDataSource = asyncDataSource; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - _asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; - _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath); @@ -1038,11 +1033,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; 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 - (ASRangeController *)rangeController @@ -1080,6 +1070,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return [_dataController nodeAtIndexPath:indexPath]; } +- (NSString *)nameForRangeControllerDataSource +{ + return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]); +} + #pragma mark - ASRangeControllerDelegate - (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h index 1b87cad643..f013ff2134 100644 --- a/AsyncDisplayKit/ASContextTransitioning.h +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -10,7 +10,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import +#import + +@class ASDisplayNode; +@class ASLayout; NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index b44715c97e..9302b9cf2b 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASContextTransitioning.h" +#import "ASDisplayNode.h" #import "ASLayoutRangeType.h" NS_ASSUME_NONNULL_BEGIN @@ -20,9 +20,6 @@ ASDISPLAYNODE_EXTERN_C_END @interface ASDisplayNode (Beta) -+ (BOOL)usesImplicitHierarchyManagement; -+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled; - /** * ASTableView and ASCollectionView now throw exceptions on invalid updates * like their UIKit counterparts. If YES, these classes will log messages @@ -63,65 +60,12 @@ ASDISPLAYNODE_EXTERN_C_END /** @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)context; - -/** - * @discussion A place to clean up your nodes after the transition - */ -- (void)didCompleteLayoutTransition:(id)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. * Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished. */ - (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 * 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, diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 005d9e2203..cd4f550835 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -16,6 +16,7 @@ #import #import #import +#import #define ASDisplayNodeLoggingEnabled 0 @@ -615,7 +616,7 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END + /** * ## UIView bridge * @@ -744,11 +745,92 @@ NS_ASSUME_NONNULL_END // Accessibility identification support @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)context; + +/** + * @discussion A place to clean up your nodes after the transition + */ +- (void)didCompleteLayoutTransition:(nonnull id)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 /* - ASDisplayNode participates in ASAsyncTransactions, so you can determine when your subnodes are done rendering. - See: -(void)asyncdisplaykit_asyncTransactionContainerStateDidChange in ASDisplayNodeSubclass.h + * ASDisplayNode support for automatic subnode management. + */ +@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) @end @@ -763,7 +845,9 @@ NS_ASSUME_NONNULL_END - (void)addSubnode:(nonnull ASDisplayNode *)node; @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) /** * Convenience method, equivalent to [layer addSublayer:node.layer]. @@ -776,8 +860,69 @@ NS_ASSUME_NONNULL_END @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)recursivelyReclaimMemory ASDISPLAYNODE_DEPRECATED; @property (nonatomic, assign) BOOL placeholderFadesOut ASDISPLAYNODE_DEPRECATED; @end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index c57589025d..566b4ed9c6 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -77,18 +77,6 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS @synthesize isFinalLayoutable = _isFinalLayoutable; @synthesize threadSafeBounds = _threadSafeBounds; -static BOOL usesImplicitHierarchyManagement = NO; - -+ (BOOL)usesImplicitHierarchyManagement -{ - return usesImplicitHierarchyManagement; -} - -+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled -{ - usesImplicitHierarchyManagement = enabled; -} - static BOOL suppressesInvalidCollectionUpdateExceptions = YES; + (BOOL)suppressesInvalidCollectionUpdateExceptions @@ -291,8 +279,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _environmentState = ASEnvironmentStateMakeDefault(); + _defaultLayoutTransitionDuration = 0.2; + _defaultLayoutTransitionDelay = 0.0; + _defaultLayoutTransitionOptions = UIViewAnimationOptionBeginFromCurrentState; + _flags.canClearContentsOfLayer = YES; - _flags.canCallNeedsDisplayOfLayer = NO; + _flags.canCallSetNeedsDisplayOfLayer = YES; } - (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 - if (_flags.synchronous) { - if ([view isKindOfClass:[UIImageView class]]) { - _flags.canClearContentsOfLayer = NO; - } else { - _flags.canCallNeedsDisplayOfLayer = YES; - } + if (_flags.synchronous && [_viewClass isSubclassOfClass:[UIImageView class]]) { + _flags.canClearContentsOfLayer = NO; + _flags.canCallSetNeedsDisplayOfLayer = NO; } return view; @@ -646,7 +635,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _calculatedLayout ? : [ASLayout layoutWithLayoutableObject:self constrainedSizeRange:constrainedSize size:CGSizeZero]; } - [self cancelLayoutTransitionsInProgress]; + [self cancelLayoutTransition]; ASLayout *previousLayout = _calculatedLayout; ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize]; @@ -699,11 +688,23 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) 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 -- (void)transitionLayoutWithAnimation:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion +- (void)transitionLayoutAnimated:(BOOL)animated measurementCompletion:(void (^)())completion { if (_calculatedLayout == nil) { // constrainedSizeRange returns a struct and is invalid to call on nil. @@ -714,14 +715,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self invalidateCalculatedLayout]; [self transitionLayoutWithSizeRange:_calculatedLayout.constrainedSizeRange animated:animated - shouldMeasureAsync:shouldMeasureAsync measurementCompletion:completion]; } - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion + measurementCompletion:(void (^)())completion { ASDisplayNodeAssertMainThread(); if (! [self shouldMeasureWithSizeRange:constrainedSize]) { @@ -741,7 +740,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) node.pendingTransitionID = transitionID; }); - void (^transitionBlock)() = ^{ + ASPerformBlockOnBackgroundThread(^{ if ([self _shouldAbortTransitionWithID:transitionID]) { return; } @@ -751,11 +750,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASLayoutableSetCurrentContext(ASLayoutableContextMake(transitionID, NO)); ASDN::MutexLocker l(__instanceLock__); - BOOL disableImplicitHierarchyManagement = self.usesImplicitHierarchyManagement == NO; - self.usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x + BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO); + self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x newLayout = [self calculateLayoutThatFits:constrainedSize]; - if (disableImplicitHierarchyManagement) { - self.usesImplicitHierarchyManagement = NO; // Temporary flag for 1.9.x + if (automaticallyManagesSubnodesDisabled) { + self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x } ASLayoutableClearCurrentContext(); @@ -789,12 +788,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // Setup pending layout transition for animation - // The pending layout transition needs to stay alive at least until applySubnodeInsertions did finish execute as - // it can happen that with Implicit Hierarchy Management new nodes gonna be added that internally call setNeedsLayout - // what will invalidate and deallocate the transition in the middle of inserting nodes - NS_VALID_UNTIL_END_OF_SCOPE ASLayoutTransition *pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self pendingLayout:newLayout previousLayout:previousLayout]; - _pendingLayoutTransition = pendingLayoutTransition; - + _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self + pendingLayout:newLayout + previousLayout:previousLayout]; // Setup context for pending layout transition. we need to hold a strong reference to the context _pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated layoutDelegate:_pendingLayoutTransition @@ -806,12 +802,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Kick off animating the layout transition [self animateLayoutTransition:_pendingLayoutTransitionContext]; }); - }; - - ASPerformBlockOnBackgroundThread(transitionBlock); + }); } -- (void)cancelLayoutTransitionsInProgress +- (void)cancelLayoutTransition { ASDN::MutexLocker l(__instanceLock__); 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 { ASDN::MutexLocker l(__instanceLock__); @@ -864,16 +846,126 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) 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 - * applies all subnodes without animation and calls completes the transition on the context. + * Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default a fade in and out + * animation is provided. */ - (void)animateLayoutTransition:(id)context { - [self __layoutSublayouts]; - [context completeTransition:YES]; + if ([context isAnimated] == NO) { + [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 *removedSubnodes = [context removedSubnodes]; + NSMutableArray *removedViews = [NSMutableArray array]; + NSMutableArray *insertedSubnodes = [[context insertedSubnodes] mutableCopy]; + NSMutableArray *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]; } -#pragma mark - _ASTransitionContextCompletionDelegate +#pragma mark _ASTransitionContextCompletionDelegate /* * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this @@ -920,8 +1012,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) */ - (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition { - // Layout transition is not supported for non implicit hierarchy managed nodes yet - if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) { + // Layout transition is not supported for nodes that are not have automatic subnode management enabled + if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) { return; } @@ -1087,7 +1179,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDN::MutexLocker l(__instanceLock__); // 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. if (_pendingViewState.hasSetNeedsLayout) { [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 // 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 if (ASHierarchyStateIncludesLayoutPending(stateToEnterOrExit)) { int32_t pendingTransitionId = newSupernode.pendingTransitionID; @@ -1961,11 +2053,11 @@ static NSInteger incrementIfFound(NSInteger i) { _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay; } -// Helper method to determine if it's save to call setNeedsDisplay on a layer without throwing away the content. -// For details look at the comment on the canCallNeedsDisplayOfLayer flag -- (BOOL)__canCallNeedsDisplayOfLayer +// 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 canCallSetNeedsDisplayOfLayer flag +- (BOOL)__canCallSetNeedsDisplayOfLayer { - return _flags.canCallNeedsDisplayOfLayer; + return _flags.canCallSetNeedsDisplayOfLayer; } - (BOOL)placeholderShouldPersist @@ -2016,9 +2108,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) ASDisplayNode *node = [layer asyncdisplaykit_node]; - if ([node __canCallNeedsDisplayOfLayer]) { - // Layers for UIKit components that are wrapped wtihin a node needs to be set to be displayed as the contents of - // the layer get's cleared and would not be recreated otherwise + if (node.isSynchronous && [node __canCallSetNeedsDisplayOfLayer]) { + // 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. + // We do not call this for _ASDisplayLayer as an optimization. [layer setNeedsDisplay]; } @@ -2602,7 +2695,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) ASDisplayNodeAssertTrue(layout.size.width >= 0.0); ASDisplayNodeAssertTrue(layout.size.height >= 0.0); - if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) { + if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) { return; } @@ -3175,6 +3268,36 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; @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 { self.placeholderFadeDuration = placeholderFadesOut ? 0.1 : 0.0; diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 573323fda6..b47dc7ca14 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -11,6 +11,7 @@ #import "ASEditableTextNode.h" #import +#import #import "ASDisplayNode+Subclasses.h" #import "ASEqualityHelpers.h" @@ -238,9 +239,9 @@ { ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; - CGFloat width = ceilf(textSize.width + _textContainerInset.left + _textContainerInset.right); - CGFloat height = ceilf(textSize.height + _textContainerInset.top + _textContainerInset.bottom); - return CGSizeMake(fminf(width, constrainedSize.width), fminf(height, constrainedSize.height)); + CGFloat width = std::ceil(textSize.width + _textContainerInset.left + _textContainerInset.right); + CGFloat height = std::ceil(textSize.height + _textContainerInset.top + _textContainerInset.bottom); + return CGSizeMake(std::fmin(width, constrainedSize.width), std::fmin(height, constrainedSize.height)); } - (void)layout diff --git a/AsyncDisplayKit/ASImageNode+tvOS.m b/AsyncDisplayKit/ASImageNode+tvOS.m index 26b7dd49b4..cc0af5de01 100644 --- a/AsyncDisplayKit/ASImageNode+tvOS.m +++ b/AsyncDisplayKit/ASImageNode+tvOS.m @@ -12,7 +12,10 @@ #if TARGET_OS_TV #import "ASImageNode+tvOS.h" + #import +#import + #import "ASDisplayNodeExtras.h" @implementation ASImageNode (tvOS) @@ -75,8 +78,8 @@ // BUT we apply our transforms to *view since we want to apply // the transforms to the root view (L: 107) CGPoint point = [touch locationInView:self.view]; - float pitch = 0; - float yaw = 0; + CGFloat pitch = 0; + CGFloat yaw = 0; BOOL topHalf = NO; if (point.y > CGRectGetHeight(self.view.frame)) { pitch = 15; @@ -100,7 +103,7 @@ if (yaw > 0) { yaw = -yaw; } else { - yaw = fabsf(yaw); + yaw = fabs(yaw); } } diff --git a/AsyncDisplayKit/ASImageNode.h b/AsyncDisplayKit/ASImageNode.h index 9f33119e6c..8b64e7b52c 100644 --- a/AsyncDisplayKit/ASImageNode.h +++ b/AsyncDisplayKit/ASImageNode.h @@ -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 * 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 * 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 diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index f4302be39a..090ba3981a 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -10,6 +10,8 @@ #import "ASImageNode.h" +#import + #import "_ASDisplayLayer.h" #import "ASAssert.h" #import "ASDisplayNode+Subclasses.h" @@ -52,9 +54,9 @@ struct ASImageNodeDrawParameters { @property CGRect imageDrawRect; @property BOOL isOpaque; @property (nonatomic, strong) UIColor *backgroundColor; -@property ASDisplayNodeContextModifier preContextBlock; -@property ASDisplayNodeContextModifier postContextBlock; -@property asimagenode_modification_block_t imageModificationBlock; +@property (nonatomic, copy) ASDisplayNodeContextModifier preContextBlock; +@property (nonatomic, copy) ASDisplayNodeContextModifier postContextBlock; +@property (nonatomic, copy) asimagenode_modification_block_t imageModificationBlock; @end @@ -317,7 +319,7 @@ struct ASImageNodeDrawParameters { CGSize imageSize = image.size; 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) { CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height); diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index 80d63d4bb0..1d9b96c7be 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -70,8 +70,14 @@ typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) */ @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 annotation, CGPoint *centerOffset); + @end NS_ASSUME_NONNULL_END -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index c0d7fb9a25..7c64abef9a 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -10,6 +10,9 @@ #if TARGET_OS_IOS #import "ASMapNode.h" + +#import + #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNodeExtras.h" @@ -164,6 +167,14 @@ self.options = options; } +- (void)setMapDelegate:(id)mapDelegate { + _mapDelegate = mapDelegate; + + if (_mapView) { + _mapView.delegate = mapDelegate; + } +} + #pragma mark - Snapshotter - (void)takeSnapshot @@ -206,15 +217,27 @@ UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); [image drawAtPoint:CGPointZero]; - // Get a standard annotation view pin. Future implementations should use a custom annotation image property. - MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - UIImage *pinImage = pin.image; - CGSize pinSize = pin.bounds.size; + UIImage *pinImage; + CGPoint pinCenterOffset = CGPointZero; + + // Get a standard annotation view pin if there is no custom annotation block. + if (!strongSelf.imageForStaticMapAnnotationBlock) { + pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; + } for (id 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]; if (CGRectContainsPoint(finalImageRect, point)) { - CGPoint pinCenterOffset = pin.centerOffset; + CGSize pinSize = pinImage.size; point.x -= pinSize.width / 2.0; point.y -= pinSize.height / 2.0; 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 { _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; @@ -320,16 +354,16 @@ CLLocationCoordinate2D bottomRightCoord = CLLocationCoordinate2DMake(90, -180); for (id annotation in annotations) { - topLeftCoord = CLLocationCoordinate2DMake(fmax(topLeftCoord.latitude, annotation.coordinate.latitude), - fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); - bottomRightCoord = CLLocationCoordinate2DMake(fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), - fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); + topLeftCoord = CLLocationCoordinate2DMake(std::fmax(topLeftCoord.latitude, annotation.coordinate.latitude), + std::fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); + bottomRightCoord = CLLocationCoordinate2DMake(std::fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), + std::fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); } MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5, topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5), - MKCoordinateSpanMake(fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, - fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); + MKCoordinateSpanMake(std::fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, + std::fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); return region; } @@ -400,4 +434,4 @@ } } @end -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 21b5d698e2..5ca8f79674 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -16,6 +16,8 @@ @class ASPagerNode; @class ASPagerFlowLayout; +NS_ASSUME_NONNULL_BEGIN + #define ASPagerNodeDataSource ASPagerDataSource @protocol ASPagerDataSource @@ -52,19 +54,21 @@ */ - (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 @protocol ASPagerDelegate +@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 @interface ASPagerNode : ASCollectionNode @@ -82,14 +86,15 @@ /** * Data Source is required, and uses a different protocol from ASCollectionNode. */ -- (void)setDataSource:(id )dataSource; -- (id )dataSource; +- (void)setDataSource:(nullable id )dataSource; +- (nullable id )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... */ -@property (nonatomic, weak) id delegate; +- (void)setDelegate:(nullable id )delegate; +- (nullable id )delegate; /** * The underlying ASCollectionView object. @@ -112,3 +117,5 @@ - (ASCellNode *)nodeForPageAtIndex:(NSInteger)index; @end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index b5050f4d5c..3b3db1360f 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -15,18 +15,23 @@ #import "ASDisplayNode+Subclasses.h" #import "ASPagerFlowLayout.h" -@interface ASPagerNode () +@interface ASPagerNode () { ASPagerFlowLayout *_flowLayout; - ASPagerNodeProxy *_proxy; - __weak id _pagerDataSource; + + __weak id _pagerDataSource; + ASPagerNodeProxy *_proxyDataSource; BOOL _pagerDataSourceImplementsNodeBlockAtIndex; - BOOL _pagerDataSourceImplementsConstrainedSizeForNode; + + __weak id _pagerDelegate; + ASPagerNodeProxy *_proxyDelegate; + BOOL _pagerDelegateImplementsConstrainedSizeForNode; } @end @implementation ASPagerNode + @dynamic view, delegate, dataSource; #pragma mark - Lifecycle @@ -58,6 +63,8 @@ [super didLoad]; ASCollectionView *cv = self.view; + cv.asyncDataSource = (id)_proxyDataSource ?: self; + cv.asyncDelegate = (id)_proxyDelegate ?: self; #if TARGET_OS_IOS cv.pagingEnabled = YES; cv.scrollsToTop = NO; @@ -122,9 +129,10 @@ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - if (_pagerDataSourceImplementsConstrainedSizeForNode) { - return [_pagerDataSource pagerNode:self constrainedSizeForNodeAtIndexPath:indexPath]; + if (_pagerDelegateImplementsConstrainedSizeForNode) { + return [_pagerDelegate pagerNode:self constrainedSizeForNodeAtIndex:indexPath.item]; } + return ASSizeRangeMake(CGSizeZero, self.view.bounds.size); } @@ -135,26 +143,38 @@ return _pagerDataSource; } -- (void)setDataSource:(id )pagerDataSource +- (void)setDataSource:(id )dataSource { - if (pagerDataSource != _pagerDataSource) { - _pagerDataSource = pagerDataSource; + if (dataSource != _pagerDataSource) { + _pagerDataSource = dataSource; _pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; // Data source must implement pagerNode:nodeBlockAtIndex: or 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 )_proxyDataSource; + } +} + +- (void)setDelegate:(id)delegate +{ + if (delegate != _pagerDelegate) { + _pagerDelegate = delegate; - super.dataSource = (id )_proxy; + _pagerDelegateImplementsConstrainedSizeForNode = [_pagerDelegate respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndex:)]; + + _proxyDelegate = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil; + + super.delegate = (id )_proxyDelegate; } } - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy { [self setDataSource:nil]; + [self setDelegate:nil]; } @end diff --git a/AsyncDisplayKit/ASRunLoopQueue.h b/AsyncDisplayKit/ASRunLoopQueue.h index 37b0031eda..32382ef957 100644 --- a/AsyncDisplayKit/ASRunLoopQueue.h +++ b/AsyncDisplayKit/ASRunLoopQueue.h @@ -24,4 +24,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASTableNode.mm b/AsyncDisplayKit/ASTableNode.mm index 2f4de2d0cf..f8af1ae0cb 100644 --- a/AsyncDisplayKit/ASTableNode.mm +++ b/AsyncDisplayKit/ASTableNode.mm @@ -16,6 +16,7 @@ #import "ASDisplayNode+Subclasses.h" #import "ASInternalHelpers.h" #import "ASCellNode+Internal.h" +#import "AsyncDisplayKit+Debug.h" #pragma mark - _ASTablePendingState @@ -125,6 +126,12 @@ [self.view clearFetchedData]; } +- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState +{ + [super interfaceStateDidChange:newState fromState:oldState]; + [ASRangeController layoutDebugOverlayIfNeeded]; +} + #if ASRangeControllerLoggingEnabled - (void)visibleStateDidChange:(BOOL)isVisible { diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 958530cca9..8dc5052495 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -17,7 +17,6 @@ #import "ASChangeSetDataController.h" #import "ASDelegateProxy.h" #import "ASDisplayNodeExtras.h" -#import "ASDisplayNode+Beta.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASInternalHelpers.h" #import "ASLayout.h" @@ -937,6 +936,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return ASInterfaceStateForDisplayNode(self.tableNode, self.window); } +- (NSString *)nameForRangeControllerDataSource +{ + return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]); +} + #pragma mark - ASRangeControllerDelegate - (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController @@ -1159,8 +1163,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell { - CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width; ASCellNode *node = tableViewCell.node; + if (node == nil) { + return; + } + + CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width; ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; // Table view cells should always fill its content view width. diff --git a/AsyncDisplayKit/ASTextNode+Beta.h b/AsyncDisplayKit/ASTextNode+Beta.h index 899b7d2175..1e6dc38765 100644 --- a/AsyncDisplayKit/ASTextNode+Beta.h +++ b/AsyncDisplayKit/ASTextNode+Beta.h @@ -31,6 +31,14 @@ NS_ASSUME_NONNULL_BEGIN */ @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 NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index b564caa488..2b84eb1944 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -12,6 +12,7 @@ #import "ASTextNode+Beta.h" #include +#import #import "_ASDisplayLayer.h" #import "ASDisplayNode+Subclasses.h" @@ -26,6 +27,8 @@ #import "ASInternalHelpers.h" #import "ASLayout.h" +#import "CGRect+ASConvenience.h" + static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15; static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1; static const CGFloat ASTextNodeHighlightLightOpacity = 0.11; @@ -44,8 +47,11 @@ struct ASTextNodeDrawParameter { @implementation ASTextNode { CGSize _shadowOffset; CGColorRef _shadowColor; + UIColor *_cachedShadowUIColor; CGFloat _shadowOpacity; CGFloat _shadowRadius; + + UIEdgeInsets _textContainerInset; NSArray *_exclusionPaths; @@ -211,7 +217,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; ASDN::MutexLocker l(__instanceLock__); 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] constrainedSize:constrainedSize]; } @@ -232,6 +246,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .pointSizeScaleFactors = self.pointSizeScaleFactors, .layoutManagerCreationBlock = self.layoutManagerCreationBlock, .textStorageCreationBlock = self.textStorageCreationBlock, + .shadowOffset = _shadowOffset, + .shadowColor = _cachedShadowUIColor, + .shadowOpacity = _shadowOpacity, + .shadowRadius = _shadowRadius }; } @@ -273,6 +291,24 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; #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 { 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. CGSize rendererConstrainedSize = _renderer.constrainedSize; + //inset bounds + boundsSize.width -= _textContainerInset.left + _textContainerInset.right; + boundsSize.height -= _textContainerInset.top + _textContainerInset.bottom; + if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) { return NO; } else { @@ -315,9 +355,14 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; if (layout != nil) { ASDN::MutexLocker l(__instanceLock__); - if (CGSizeEqualToSize(_constrainedSize, layout.size) == NO) { - _constrainedSize = layout.size; - _renderer.constrainedSize = layout.size; + CGSize layoutSize = layout.size; + //Apply textContainerInset + 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__); + //remove textContainerInset + constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right); + constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom); + _constrainedSize = constrainedSize; // 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; } } + + //add textContainerInset + size.width += (_textContainerInset.left + _textContainerInset.right); + size.height += (_textContainerInset.top + _textContainerInset.bottom); + return size; } @@ -460,6 +514,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; CGContextSaveGState(context); + CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top); + ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds]; UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer]; CGPoint boundsOrigin = drawParameterBounds.origin; @@ -519,7 +575,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [renderer enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { 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 the distance computed from the touch to the glyph location is // 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) { UIEdgeInsets shadowPadding = _renderer.shadower.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]; // 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. // This would completely eliminate the memory and performance cost of the backing store. CGSize size = self.calculatedSize; - if (CGSizeEqualToSize(size, CGSizeZero)) { + if ((size.width * size.height) < CGFLOAT_EPSILON) { return nil; } @@ -1043,7 +1106,11 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI if (shadowColor != NULL) { CGColorRetain(shadowColor); } + if (_shadowColor != NULL) { + CGColorRelease(_shadowColor); + } _shadowColor = shadowColor; + _cachedShadowUIColor = [UIColor colorWithCGColor:shadowColor]; [self _invalidateRenderer]; [self setNeedsDisplay]; } diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 039c21269a..9223da3fd4 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -12,7 +12,7 @@ #import #import -@class AVAsset, AVPlayer, AVPlayerItem, AVVideoComposition, AVAudioMix; +@class AVAsset, AVPlayer, AVPlayerLayer, AVPlayerItem, AVVideoComposition, AVAudioMix; @protocol ASVideoNodeDelegate; typedef enum { @@ -51,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, strong, readwrite) AVAudioMix *audioMix; @property (nullable, nonatomic, strong, readonly) AVPlayer *player; +@property (nullable, nonatomic, strong, readonly) AVPlayerLayer *playerLayer; @property (nullable, nonatomic, strong, readonly) AVPlayerItem *currentItem; diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index e6f44993ea..008c4b94f0 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -37,6 +37,7 @@ static void *ASVideoNodeContext = &ASVideoNodeContext; static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp"; static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty"; static NSString * const kStatus = @"status"; +static NSString * const kRate = @"rate"; @interface ASVideoNode () { @@ -208,6 +209,25 @@ static NSString * const kStatus = @"status"; [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 { [super layout]; @@ -267,6 +287,7 @@ static NSString * const kStatus = @"status"; AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; previewImageGenerator.appliesPreferredTrackTransform = YES; + previewImageGenerator.videoComposition = _videoComposition; [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]] completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { @@ -291,29 +312,47 @@ static NSString * const kStatus = @"status"; { ASDN::MutexLocker l(__instanceLock__); - if (object != _currentPlayerItem) { - return; - } - - if ([keyPath isEqualToString:kStatus]) { - 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 (self.image == nil && self.URL == nil) { - [self generatePlaceholderImage]; + if (object == _currentPlayerItem) { + if ([keyPath isEqualToString:kStatus]) { + if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { + if (self.playerState != ASVideoNodePlayerStatePlaying) { + self.playerState = ASVideoNodePlayerStateReadyToPlay; + } + // 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) { + [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]) { - self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; - if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || [change[NSKeyValueChangeNewKey] boolValue]) && ASInterfaceStateIncludesVisible(self.interfaceState)) { - if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { - [_delegate videoNodeDidRecoverFromStall:self]; + } else if (object == _player) { + if ([keyPath isEqualToString:kRate]) { + if ([change[NSKeyValueChangeNewKey] floatValue] == 0.0) { + if (self.playerState == ASVideoNodePlayerStatePlaying) { + 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__); AVAsset *asset = self.asset; // Return immediately if the asset is nil; - if (asset == nil || self.playerState == ASVideoNodePlayerStateInitialLoading) { + if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) { return; } - // FIXME: Nothing appears to prevent this method from sending the delegate notification / calling load on the asset - // 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; + self.playerState = ASVideoNodePlayerStateLoading; if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) { [_delegate videoNodeDidStartInitialLoading:self]; } @@ -396,6 +432,7 @@ static NSString * const kStatus = @"status"; self.player = nil; self.currentItem = nil; + self.playerState = ASVideoNodePlayerStateUnknown; } } @@ -514,6 +551,12 @@ static NSString * const kStatus = @"status"; return _player; } +- (AVPlayerLayer *)playerLayer +{ + ASDN::MutexLocker l(__instanceLock__); + return (AVPlayerLayer *)_playerNode.layer; +} + - (id)delegate{ return _delegate; } @@ -603,12 +646,6 @@ static NSString * const kStatus = @"status"; [_player play]; _shouldBePlaying = YES; - - if (![self ready]) { - self.playerState = ASVideoNodePlayerStateLoading; - } else { - self.playerState = ASVideoNodePlayerStatePlaying; - } } - (BOOL)ready @@ -622,7 +659,6 @@ static NSString * const kStatus = @"status"; if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) { return; } - self.playerState = ASVideoNodePlayerStatePaused; [_player pause]; _shouldBePlaying = NO; } @@ -731,9 +767,16 @@ static NSString * const kStatus = @"status"; - (void)setPlayer:(AVPlayer *)player { ASDN::MutexLocker l(__instanceLock__); + + [self removePlayerObservers:_player]; + _player = player; player.muted = _muted; ((AVPlayerLayer *)_playerNode.layer).player = player; + + if (player != nil) { + [self addPlayerObservers:player]; + } } - (BOOL)shouldBePlaying @@ -755,6 +798,7 @@ static NSString * const kStatus = @"status"; [_player removeTimeObserver:_timeObserver]; _timeObserver = nil; [self removePlayerItemObservers:_currentPlayerItem]; + [self removePlayerObservers:_player]; } @end diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index 31ff3ed9d4..84bd14d604 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -78,7 +78,7 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C - (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 diff --git a/AsyncDisplayKit/ASViewController.mm b/AsyncDisplayKit/ASViewController.mm index a0e3681381..d05e9b83a0 100644 --- a/AsyncDisplayKit/ASViewController.mm +++ b/AsyncDisplayKit/ASViewController.mm @@ -313,12 +313,13 @@ ASVisibilityDepthImplementation; { [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator]; - // here we take the new UITraitCollection and use it to create a new ASEnvironmentTraitCollection on self.node - // We will propagate when the corresponding viewWillTransitionToSize:withTransitionCoordinator: is called and we have the - // new windowSize. There are cases when viewWillTransitionToSize: is called when willTransitionToTraitCollection: is not. - // Since we do the propagation on viewWillTransitionToSize: our subnodes should always get the proper trait collection. - ASEnvironmentTraitCollection asyncTraitCollection = ASEnvironmentTraitCollectionFromUITraitCollection(newCollection); - self.node.environmentTraitCollection = asyncTraitCollection; + ASEnvironmentTraitCollection environmentTraitCollection = ASEnvironmentTraitCollectionFromUITraitCollection(newCollection); + // A call to willTransitionToTraitCollection:withTransitionCoordinator: is always followed by a call to viewWillTransitionToSize:withTransitionCoordinator:. + // However, it is not immediate and we may need the new trait collection before viewWillTransitionToSize:withTransitionCoordinator: is called. Our view already has the + // 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) + // then we won't we will skip the propagation in viewWillTransitionToSize:withTransitionCoordinator:. + environmentTraitCollection.containerSize = self.view.bounds.size; + [self progagateNewEnvironmentTraitCollection:environmentTraitCollection]; } - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator diff --git a/AsyncDisplayKit/ASVisibilityProtocols.m b/AsyncDisplayKit/ASVisibilityProtocols.m index 62f9879e14..6a85a7b4d0 100644 --- a/AsyncDisplayKit/ASVisibilityProtocols.m +++ b/AsyncDisplayKit/ASVisibilityProtocols.m @@ -22,4 +22,4 @@ ASLayoutRangeMode ASLayoutRangeModeForVisibilityDepth(NSUInteger visibilityDepth return ASLayoutRangeModeVisibleOnly; } return ASLayoutRangeModeLowMemory; -} \ No newline at end of file +} diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.h b/AsyncDisplayKit/AsyncDisplayKit+Debug.h index 09c20f2b66..a85dda03fc 100644 --- a/AsyncDisplayKit/AsyncDisplayKit+Debug.h +++ b/AsyncDisplayKit/AsyncDisplayKit+Debug.h @@ -12,6 +12,7 @@ #import "ASControlNode.h" #import "ASImageNode.h" +#import "ASRangeController.h" @interface ASImageNode (Debugging) @@ -29,16 +30,40 @@ @interface ASControlNode (Debugging) /** - 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!!! - 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 clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond - overlay rect, but can't be visualized). - @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class. + * 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!!! + * 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 clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond + * overlay rect, but can't be visualized). + * @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class. */ + (void)setEnableHitTestDebug:(BOOL)enable; + (BOOL)enableHitTestDebug; @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 + diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.m b/AsyncDisplayKit/AsyncDisplayKit+Debug.m index 86d77e0f3b..e456ce72f6 100644 --- a/AsyncDisplayKit/AsyncDisplayKit+Debug.m +++ b/AsyncDisplayKit/AsyncDisplayKit+Debug.m @@ -13,6 +13,16 @@ #import "AsyncDisplayKit+Debug.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNodeExtras.h" +#import "ASWeakSet.h" +#import "UIImage+ASConvenience.h" +#import +#import +#import +#import +#import + + +#pragma mark - ASImageNode (Debugging) static BOOL __shouldShowImageScalingOverlay = NO; @@ -30,6 +40,8 @@ static BOOL __shouldShowImageScalingOverlay = NO; @end +#pragma mark - ASControlNode (DebuggingInternal) + static BOOL __enableHitTestDebug = NO; @interface ASControlNode (DebuggingInternal) @@ -191,3 +203,551 @@ static BOOL __enableHitTestDebug = NO; } @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 () +@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 diff --git a/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch b/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch index c2e8081429..0062314263 100644 --- a/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch +++ b/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch @@ -16,4 +16,4 @@ // 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). #define PIN_REMOTE_IMAGE __has_include() -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.mm b/AsyncDisplayKit/Details/ASChangeSetDataController.mm index b85aacf817..208d84aebb 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.mm +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.mm @@ -84,6 +84,18 @@ 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) - (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index 2f19e09757..b99bb9e5dc 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -26,8 +26,6 @@ - (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController; -- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind; - - (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; @optional @@ -44,4 +42,4 @@ - (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; -@end \ No newline at end of file +@end diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 8f5d505815..4edc939ce1 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -43,17 +43,18 @@ return self; } -- (void)prepareForReloadData +- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount { + NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@", kind); NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withMutableContexts:contexts]; + [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; _pendingContexts[kind] = contexts; } } -- (void)willReloadData +- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount { [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, __unused BOOL * _Nonnull stop) { // 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]; // Insert each section - NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; - for (int i = 0; i < sectionCount; i++) { + NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount]; + for (int i = 0; i < newSectionCount; i++) { [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 *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; @@ -188,22 +188,6 @@ [_pendingContexts removeAllObjects]; } -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray *)contexts -{ - id environment = [self.environmentDelegate dataControllerEnvironment]; - ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; - - id 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 *)contexts { id environment = [self.environmentDelegate dataControllerEnvironment]; diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index 423c8302a7..0dafa8cbff 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASCollectionViewLayoutInspecting /** - * 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; @@ -33,11 +33,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (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. */ @@ -57,6 +52,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)didChangeCollectionViewDataSource:(nullable id)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 /** diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index 25ba06b71d..4d8aa8e287 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -34,7 +34,7 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView @implementation ASCollectionViewLayoutInspector { struct { unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; - } _dataSourceFlags; + } _delegateFlags; } #pragma mark Lifecycle @@ -43,26 +43,26 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView { self = [super init]; if (self != nil) { - [self didChangeCollectionViewDataSource:collectionView.asyncDataSource]; + [self didChangeCollectionViewDelegate:collectionView.asyncDelegate]; } return self; } #pragma mark ASCollectionViewLayoutInspecting -- (void)didChangeCollectionViewDataSource:(id)dataSource +- (void)didChangeCollectionViewDelegate:(id)delegate { - if (dataSource == nil) { - memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags)); + if (delegate == nil) { + memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { - _dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath = [dataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; + _delegateFlags.implementsConstrainedSizeForNodeAtIndexPath = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; } } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { - return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; + if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath) { + return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; } return NodeConstrainedSizeForScrollDirection(collectionView); @@ -74,12 +74,6 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView 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 { 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 { unsigned int implementsReferenceSizeForHeader:1; unsigned int implementsReferenceSizeForFooter:1; + unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; } _delegateFlags; struct { - unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; unsigned int implementsNumberOfSectionsInCollectionView:1; } _dataSourceFlags; } @@ -132,6 +126,7 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView } else { _delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; _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) { memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags)); } else { - _dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath = [dataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; _dataSourceFlags.implementsNumberOfSectionsInCollectionView = [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; } } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { - return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; + if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath) { + return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; } CGSize itemSize = _layout.itemSize; @@ -171,15 +165,6 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView 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 { return [self layoutHasSupplementaryViewOfKind:kind inSection:section collectionView:collectionView] ? 1 : 0; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index f738b55e01..d06e2b45b4 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -49,8 +49,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; std::vector _itemCountsFromDataSource; // Main thread only. 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_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. @@ -62,8 +61,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; BOOL _delegateDidDeleteSections; } -@property (nonatomic, assign) NSUInteger batchUpdateCounter; - @end @implementation ASDataController @@ -87,15 +84,11 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; _mainSerialQueue = [[ASMainSerialQueue alloc] init]; - _pendingEditCommandBlocks = [NSMutableArray array]; - const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL); _editingTransactionGroup = dispatch_group_create(); - _batchUpdateCounter = 0; - return self; } @@ -395,60 +388,55 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion { + ASDisplayNodeAssertMainThread(); + _initialReloadDataHasBeenCalled = YES; - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - [self invalidateDataSourceItemCounts]; - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; + [self invalidateDataSourceItemCounts]; + NSUInteger sectionCount = [self itemCountsFromDataSource].size(); + NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; + NSArray *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 - [self prepareForReloadData]; + // Remove everything that existed before the reload, now that we're ready to insert replacements + NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; + NSUInteger editingNodesSectionCount = editingNodes.count; - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - LOG(@"Edit Transaction - reloadData"); - - // Remove everything that existed before the reload, now that we're ready to insert replacements - 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]; + if (editingNodesSectionCount) { + NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)]; + [self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; } - }]; + + [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 { 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); @@ -516,10 +504,20 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)beginUpdates { + ASDisplayNodeAssertMainThread(); + // TODO: make this -waitUntilAllUpdatesAreCommitted? 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. - _batchUpdateCounter++; + + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [_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 @@ -529,118 +527,72 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - _batchUpdateCounter--; + LOG(@"endUpdatesWithCompletion - beginning"); + ASDisplayNodeAssertMainThread(); - if (_batchUpdateCounter == 0) { - LOG(@"endUpdatesWithCompletion - beginning"); - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_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(@"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]; - } + // 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. + 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]; + }]; + }); } #pragma mark - Section Editing (External API) - (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 *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 *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]; - - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); - }]; + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; + }); } - (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - deleteSections: %@", sections); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteSections:sections]; + ASDisplayNodeAssertMainThread(); + LOG(@"Edit Command - deleteSections: %@", sections); + if (!_initialReloadDataHasBeenCalled) { + return; + } - // remove elements - LOG(@"Edit Transaction - deleteSections: %@", sections); - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }); - }]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [self willDeleteSections:sections]; + + // remove elements + 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 @@ -650,44 +602,46 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - moveSection"); + ASDisplayNodeAssertMainThread(); + LOG(@"Edit Command - moveSection"); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willMoveSection:section toSection:newSection]; + if (!_initialReloadDataHasBeenCalled) { + return; + } - // remove elements - - LOG(@"Edit Transaction - moveSection"); - NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [self willMoveSection:section toSection:newSection]; - // update the section of indexpaths - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSIndexPath *indexPath in indexPaths) { - NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection]; - [updatedIndexPaths addObject:updatedIndexPath]; - } + // remove elements + + LOG(@"Edit Transaction - moveSection"); + NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - // Don't re-calculate size for moving - [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - }); - }]; + // update the section of indexpaths + 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) -- (void)prepareForReloadData +- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount { // Optional template hook for subclasses (See ASDataController+Subclasses.h) } -- (void)willReloadData +- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount { // Optional template hook for subclasses (See ASDataController+Subclasses.h) } @@ -736,59 +690,64 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - insertRows: %@", indexPaths); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + ASDisplayNodeAssertMainThread(); + if (!_initialReloadDataHasBeenCalled) { + return; + } - // Sort indexPath to avoid messing up the index when inserting in several batches - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + LOG(@"Edit Command - insertRows: %@", indexPaths); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - id 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]]; - } + // Sort indexPath to avoid messing up the index when inserting in several batches + NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - [self prepareForInsertRowsAtIndexPaths:indexPaths]; + id 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 willInsertRowsAtIndexPaths:indexPaths]; + [self prepareForInsertRowsAtIndexPaths:indexPaths]; - LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); - }]; + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [self willInsertRowsAtIndexPaths:indexPaths]; + + LOG(@"Edit Transaction - insertRows: %@", indexPaths); + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; + }); } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - deleteRows: %@", indexPaths); + ASDisplayNodeAssertMainThread(); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // 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:)]; + if (!_initialReloadDataHasBeenCalled) { + return; + } - [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; + LOG(@"Edit Command - deleteRows: %@", indexPaths); - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteRowsAtIndexPaths:sortedIndexPaths]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + + // 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 _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; - }); - }]; + [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; + + 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 @@ -798,22 +757,24 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)relayoutAllNodes { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - relayoutRows"); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + ASDisplayNodeAssertMainThread(); + if (!_initialReloadDataHasBeenCalled) { + return; + } - // Can't relayout right away because _completedNodes may not be up-to-date, - // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes - // (see _layoutNodes:atIndexPaths:withAnimationOptions:). - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - for (NSString *kind in _completedNodes) { - [self _relayoutNodesOfKind:kind]; - } - }]; - }); - }]; + LOG(@"Edit Command - relayoutRows"); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + + // Can't relayout right away because _completedNodes may not be up-to-date, + // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes + // (see _layoutNodes:atIndexPaths:withAnimationOptions:). + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [_mainSerialQueue performBlockOnMainThread:^{ + for (NSString *kind in _completedNodes) { + [self _relayoutNodesOfKind:kind]; + } + }]; + }); } - (void)_relayoutNodesOfKind:(NSString *)kind @@ -840,22 +801,24 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath); - 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]; + ASDisplayNodeAssertMainThread(); + if (!_initialReloadDataHasBeenCalled) { + return; + } - // Don't re-calculate size for moving - NSArray *newIndexPaths = @[newIndexPath]; - [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; - }); - }]; + LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath); + 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 + NSArray *newIndexPaths = @[newIndexPath]; + [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; + }); } #pragma mark - Data Querying (Subclass API) diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 13c0d211c9..110845bd1e 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -91,8 +91,7 @@ selector == @selector(collectionView:nodeForItemAtIndexPath:) || selector == @selector(collectionView:nodeBlockForItemAtIndexPath:) || selector == @selector(collectionView:numberOfItemsInSection:) || - selector == @selector(collectionView:constrainedSizeForNodeAtIndexPath:) || - selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) + selector == @selector(collectionView:constrainedSizeForNodeAtIndexPath:) ); } diff --git a/AsyncDisplayKit/Details/ASEnvironment.mm b/AsyncDisplayKit/Details/ASEnvironment.mm index 68aca910da..eb528d36e3 100644 --- a/AsyncDisplayKit/Details/ASEnvironment.mm +++ b/AsyncDisplayKit/Details/ASEnvironment.mm @@ -36,17 +36,17 @@ ASEnvironmentTraitCollection ASEnvironmentTraitCollectionMakeDefault() ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) { - ASEnvironmentTraitCollection asyncTraitCollection; + ASEnvironmentTraitCollection environmentTraitCollection = ASEnvironmentTraitCollectionMakeDefault(); if (AS_AT_LEAST_IOS8) { - asyncTraitCollection.displayScale = traitCollection.displayScale; - asyncTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; - asyncTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; - asyncTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; + environmentTraitCollection.displayScale = traitCollection.displayScale; + environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; + environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; + environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; if (AS_AT_LEAST_IOS9) { - asyncTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; + environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; } } - return asyncTraitCollection; + return environmentTraitCollection; } BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs) diff --git a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm index 8398207d2f..0d4032a318 100644 --- a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm +++ b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm @@ -11,6 +11,7 @@ #import "ASHighlightOverlayLayer.h" #import +#import #import "ASInternalHelpers.h" @@ -86,7 +87,7 @@ static const UIEdgeInsets padding = {2, 4, 1.5, 4}; if (targetLayer != nil) { 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 maxX = CGRectGetMaxX(rect) + padding.right; diff --git a/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h b/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h index 0fdda5b829..af00fe7195 100644 --- a/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h +++ b/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h @@ -61,4 +61,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index 70e3b194da..241fb71810 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -271,4 +271,4 @@ } @end -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h index d54d17ba11..72b45ea561 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h @@ -68,4 +68,4 @@ extern NSString *const ASPhotosURLScheme; @end // NS_ASSUME_NONNULL_END -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m index 79a1c060c3..aac715563c 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m @@ -163,4 +163,4 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; } @end -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index d682e07b9d..c46b1d99b5 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -134,6 +134,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *> *)completedNodes; +- (NSString *)nameForRangeControllerDataSource; + @end /** diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index d82073779d..e71e990123 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -11,13 +11,16 @@ #import "ASRangeController.h" #import "ASAssert.h" -#import "ASWeakSet.h" +#import "ASCellNode.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" #import "ASMultidimensionalArrayUtils.h" #import "ASInternalHelpers.h" +#import "ASMultiDimensionalArrayUtils.h" +#import "ASWeakSet.h" + #import "ASDisplayNode+FrameworkPrivate.h" -#import "ASCellNode.h" +#import "AsyncDisplayKit+Debug.h" #define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0 @@ -64,6 +67,10 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; #endif + if ([ASRangeController shouldShowRangeDebugOverlay]) { + [self addRangeControllerToRangeDebugOverlay]; + } + 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; #if ASRangeControllerLoggingEnabled diff --git a/AsyncDisplayKit/Details/ASThread.h b/AsyncDisplayKit/Details/ASThread.h index c88437d4ae..73c17163f5 100644 --- a/AsyncDisplayKit/Details/ASThread.h +++ b/AsyncDisplayKit/Details/ASThread.h @@ -34,6 +34,8 @@ static inline BOOL ASDisplayNodeThreadIsMain() #import #endif +#include + /** For use with ASDN::StaticMutex only. */ @@ -53,7 +55,7 @@ static inline BOOL ASDisplayNodeThreadIsMain() namespace ASDN { - + template class Locker { @@ -98,17 +100,72 @@ namespace ASDN { }; + template + class SharedLocker + { + std::shared_ptr _l; + +#if TIME_LOCKER + CFTimeInterval _ti; + const char *_name; +#endif + + public: +#if !TIME_LOCKER + + SharedLocker (std::shared_ptr const& l) ASDISPLAYNODE_NOTHROW : _l (l) { + assert(_l != nullptr); + _l->lock (); + } + + ~SharedLocker () { + _l->unlock (); + } + + // non-copyable. + SharedLocker(const SharedLocker&) = delete; + SharedLocker &operator=(const SharedLocker&) = delete; + +#else + + SharedLocker (std::shared_ptr 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 Unlocker { T &_l; public: - Unlocker (T &l) ASDISPLAYNODE_NOTHROW : _l (l) {_l.unlock ();} + Unlocker (T &l) ASDISPLAYNODE_NOTHROW : _l (l) { _l.unlock (); } ~Unlocker () {_l.lock ();} Unlocker(Unlocker&) = delete; Unlocker &operator=(Unlocker&) = delete; }; + + template + class SharedUnlocker + { + std::shared_ptr _l; + public: + SharedUnlocker (std::shared_ptr const& l) ASDISPLAYNODE_NOTHROW : _l (l) { _l->unlock (); } + ~SharedUnlocker () { _l->lock (); } + SharedUnlocker(SharedUnlocker&) = delete; + SharedUnlocker &operator=(SharedUnlocker&) = delete; + }; struct Mutex { @@ -164,7 +221,9 @@ namespace ASDN { }; typedef Locker MutexLocker; + typedef SharedLocker MutexSharedLocker; typedef Unlocker MutexUnlocker; + typedef SharedUnlocker MutexSharedUnlocker; /** If you are creating a static mutex, use StaticMutex and specify its default value as one of ASDISPLAYNODE_MUTEX_INITIALIZER diff --git a/AsyncDisplayKit/Details/ASWeakProxy.h b/AsyncDisplayKit/Details/ASWeakProxy.h index 1891085238..a3c2276b06 100644 --- a/AsyncDisplayKit/Details/ASWeakProxy.h +++ b/AsyncDisplayKit/Details/ASWeakProxy.h @@ -12,7 +12,7 @@ #import -@interface ASWeakProxy : NSObject +@interface ASWeakProxy : NSProxy @property (nonatomic, weak, readonly) id target; diff --git a/AsyncDisplayKit/Details/ASWeakProxy.m b/AsyncDisplayKit/Details/ASWeakProxy.m index 506f274e7a..d26439f178 100644 --- a/AsyncDisplayKit/Details/ASWeakProxy.m +++ b/AsyncDisplayKit/Details/ASWeakProxy.m @@ -16,7 +16,7 @@ - (instancetype)initWithTarget:(id)target { - if (self = [super init]) { + if (self) { _target = target; } return self; @@ -32,4 +32,24 @@ 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 diff --git a/AsyncDisplayKit/Private/ASWeakSet.h b/AsyncDisplayKit/Details/ASWeakSet.h similarity index 98% rename from AsyncDisplayKit/Private/ASWeakSet.h rename to AsyncDisplayKit/Details/ASWeakSet.h index 9a8707eb84..14d1a9d7cf 100644 --- a/AsyncDisplayKit/Private/ASWeakSet.h +++ b/AsyncDisplayKit/Details/ASWeakSet.h @@ -43,4 +43,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/ASWeakSet.m b/AsyncDisplayKit/Details/ASWeakSet.m similarity index 100% rename from AsyncDisplayKit/Private/ASWeakSet.m rename to AsyncDisplayKit/Details/ASWeakSet.m diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.h b/AsyncDisplayKit/Details/CGRect+ASConvenience.h index 348a896e14..c6148fd786 100644 --- a/AsyncDisplayKit/Details/CGRect+ASConvenience.h +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.h @@ -14,6 +14,14 @@ #import "ASBaseDefines.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 ASDISPLAYNODE_EXTERN_C_BEGIN diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.h b/AsyncDisplayKit/Details/NSArray+Diffing.h similarity index 100% rename from AsyncDisplayKit/Private/NSArray+Diffing.h rename to AsyncDisplayKit/Details/NSArray+Diffing.h diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.m b/AsyncDisplayKit/Details/NSArray+Diffing.m similarity index 77% rename from AsyncDisplayKit/Private/NSArray+Diffing.m rename to AsyncDisplayKit/Details/NSArray+Diffing.m index c34dfc2003..d7352a5c70 100644 --- a/AsyncDisplayKit/Private/NSArray+Diffing.m +++ b/AsyncDisplayKit/Details/NSArray+Diffing.m @@ -11,6 +11,7 @@ // #import "NSArray+Diffing.h" +#import "ASAssert.h" @implementation NSArray (Diffing) @@ -58,12 +59,24 @@ NSInteger selfCount = self.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++) { + id selfObj = i > 0 ? self[i-1] : nil; for (NSInteger j = 0; j <= arrayCount; j++) { if (i == 0 || 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]; } else { lengths[i][j] = MAX(lengths[i-1][j], lengths[i][j-1]); diff --git a/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h b/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h index 0486bb28c2..fe1e9cf98d 100644 --- a/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h +++ b/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h @@ -24,4 +24,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h index e064a7d58e..461c0966fd 100644 --- a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h +++ b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h @@ -18,4 +18,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 8fe1963538..4c70356f17 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -101,18 +101,11 @@ #endif - (void)layoutSublayers -{ +{ + ASDisplayNodeAssertMainThread(); [super layoutSublayers]; - ASDisplayNode *node = self.asyncdisplaykit_node; - if (ASDisplayNodeThreadIsMain()) { - [node __layout]; - } else { - ASDisplayNodeFailAssert(@"not reached assertion"); - dispatch_async(dispatch_get_main_queue(), ^ { - [node __layout]; - }); - } + [self.asyncdisplaykit_node __layout]; } - (void)setNeedsDisplay diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 2d4deb5259..b0d1c3a8ab 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -9,6 +9,7 @@ // #import "_ASDisplayView.h" +#import "_ASDisplayViewAccessiblity.h" #import "_ASCoreAnimationExtras.h" #import "ASDisplayNodeInternal.h" @@ -26,9 +27,12 @@ @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. + BOOL _inHitTest; BOOL _inPointInside; + NSArray *_accessibleElements; + CGRect _lastAccessibleElementsFrame; } @synthesize asyncdisplaykit_node = _node; @@ -39,10 +43,6 @@ } #pragma mark - NSObject Overrides -- (instancetype)init -{ - return [self initWithFrame:CGRectZero]; -} - (NSString *)description { @@ -53,14 +53,6 @@ #pragma mark - UIView Overrides -- (instancetype)initWithFrame:(CGRect)frame -{ - if (!(self = [super initWithFrame:frame])) - return nil; - - return self; -} - - (void)willMoveToWindow:(UIWindow *)newWindow { BOOL visible = (newWindow != nil); @@ -126,7 +118,6 @@ [newSuperview.asyncdisplaykit_node addSubnode:_node]; } } - } - (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:. - if (ASDisplayNodeThreadIsMain()) { - [self.layer setNeedsDisplay]; - } else { - dispatch_async(dispatch_get_main_queue(), ^ { - [self.layer setNeedsDisplay]; - }); - } + [super addSubview:view]; + +#ifndef ASDK_ACCESSIBILITY_DISABLE + self.accessibleElements = nil; +#endif } -- (void)setNeedsLayout +- (void)willRemoveSubview:(UIView *)subview { - if (ASDisplayNodeThreadIsMain()) { - [super setNeedsLayout]; - } else { - dispatch_async(dispatch_get_main_queue(), ^ { - [super setNeedsLayout]; - }); - } + [super willRemoveSubview:subview]; + +#ifndef ASDK_ACCESSIBILITY_DISABLE + self.accessibleElements = nil; +#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 diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h index 0041d667bf..b3dab91ab4 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h @@ -9,3 +9,7 @@ // #import + +@interface _ASDisplayView (UIAccessibilityContainer) +@property (copy, nonatomic) NSArray *accessibleElements; +@end diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm index baa2b1b7a2..641430f613 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -8,17 +8,55 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#ifndef ASDK_ACCESSIBILITY_DISABLE + #import "_ASDisplayView.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" #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.accessibilityLabel = node.accessibilityLabel; accessibilityElement.accessibilityHint = node.accessibilityHint; @@ -27,101 +65,116 @@ 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 -static NSArray *ASCollectUIAccessibilityElementsForNode(ASDisplayNode *viewNode, ASDisplayNode *subnode, id container) { - NSMutableArray *accessibleElements = [NSMutableArray array]; - ASDisplayNodePerformBlockOnEveryNodeBFS(subnode, ^(ASDisplayNode * _Nonnull currentNode) { +/// Collect all subnodes for the given node by walking down the subnode tree and calculates the screen coordinates based on the containerNode and container +static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplayNode *containerNode, id container, NSMutableArray *elements) +{ + 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 // we have to create a UIAccessibilityElement as no view for this node exists - if (currentNode != viewNode && currentNode.isAccessibilityElement) { - UIAccessibilityElement *accessibilityElement = [UIAccessibilityElement accessibilityElementWithContainer:container node:currentNode]; - // As the node hierarchy is flattened it's necessary to convert the frame for each subnode in the tree to the - // coordinate system of the supernode - CGRect frame = [viewNode convertRect:currentNode.bounds fromNode:currentNode]; - accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, container); - [accessibleElements addObject:accessibilityElement]; + if (currentNode != containerNode && currentNode.isAccessibilityElement) { + UIAccessibilityElement *accessibilityElement = [ASAccessibilityElement accessibilityElementWithContainer:container node:currentNode containerNode:containerNode]; + [elements 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 () { NSArray *_accessibleElements; } + @end @implementation _ASDisplayView (UIAccessibilityContainer) #pragma mark - UIAccessibility +- (void)setAccessibleElements:(NSArray *)accessibleElements +{ + _accessibleElements = nil; +} + - (NSArray *)accessibleElements { ASDisplayNode *viewNode = self.asyncdisplaykit_node; if (viewNode == nil) { - return nil; + return @[]; } - // Handle rasterize case - if (viewNode.shouldRasterizeDescendants) { - _accessibleElements = ASCollectUIAccessibilityElementsForNode(viewNode, viewNode, self); + if (_accessibleElements != nil) { return _accessibleElements; } - // Handle not rasterize case NSMutableArray *accessibleElements = [NSMutableArray array]; - - for (ASDisplayNode *subnode in viewNode.subnodes) { - if (subnode.isAccessibilityElement) { - // 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]; + CollectAccessibilityElementsForView(self, accessibleElements); + SortAccessibilityElements(accessibleElements); + _accessibleElements = accessibleElements; return _accessibleElements; } - (NSInteger)accessibilityElementCount { - return [self accessibleElements].count; + return self.accessibleElements.count; } - (id)accessibilityElementAtIndex:(NSInteger)index { - ASDisplayNodeAssertNotNil(_accessibleElements, @"At this point _accessibleElements should be created."); - if (_accessibleElements == nil) { - return nil; - } - - return _accessibleElements[index]; + return self.accessibleElements[index]; } - (NSInteger)indexOfAccessibilityElement:(id)element { - if (_accessibleElements == nil) { - return NSNotFound; - } - - return [_accessibleElements indexOfObject:element]; + return [self.accessibleElements indexOfObjectIdenticalTo:element]; } @end + +#endif diff --git a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m b/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m index e39f836aad..98f335ef1e 100644 --- a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m +++ b/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m @@ -10,6 +10,8 @@ #import "ASAsciiArtBoxCreator.h" +#import + static const NSUInteger kDebugBoxPadding = 2; typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) @@ -69,7 +71,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) for (NSString *child in children) { 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 lineLength = [lines[0] length]; @@ -98,7 +100,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) NSUInteger totalLineLength = [concatenatedLines[0] length]; if (totalLineLength < [parent length]) { 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; NSString *leftString = [@"|" debugbox_stringByAddingPadding:@" " count:leftPadding location:PIDebugBoxPaddingLocationEnd]; @@ -137,7 +139,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) if (maxChildLength < [parent length]) { NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - maxChildLength; - leftPadding = ceilf((CGFloat)difference/2.0); + leftPadding = ceil((CGFloat)difference/2.0); rightPadding = difference/2; } @@ -147,7 +149,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) for (NSString *child in children) { 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; for (NSString *line in lines) { @@ -171,7 +173,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) NSUInteger totalLineLength = [boxStrings[0] length]; [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; NSString *topLine = [parent debugbox_stringByAddingPadding:@"-" count:leftPadding location:PIDebugBoxPaddingLocationFront]; diff --git a/AsyncDisplayKit/Layout/ASDimension.h b/AsyncDisplayKit/Layout/ASDimension.h index 822a1c1963..79e7e2f64b 100644 --- a/AsyncDisplayKit/Layout/ASDimension.h +++ b/AsyncDisplayKit/Layout/ASDimension.h @@ -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. */ ASRelativeDimensionTypePoints, /** Multiplied to a provided parent amount to resolve a final amount. */ - ASRelativeDimensionTypePercent, + ASRelativeDimensionTypeFraction, }; typedef struct { @@ -44,7 +44,7 @@ extern ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, extern ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points); -extern ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent); +extern ASRelativeDimension ASRelativeDimensionMakeWithFraction(CGFloat fraction); extern ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension); diff --git a/AsyncDisplayKit/Layout/ASDimension.mm b/AsyncDisplayKit/Layout/ASDimension.mm index d7309d7370..a60b6b4ba9 100644 --- a/AsyncDisplayKit/Layout/ASDimension.mm +++ b/AsyncDisplayKit/Layout/ASDimension.mm @@ -19,9 +19,9 @@ ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloa { if (type == ASRelativeDimensionTypePoints) { 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. - // 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; } @@ -32,10 +32,10 @@ ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat 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); - return ASRelativeDimensionMake(ASRelativeDimensionTypePercent, percent); + // ASDisplayNodeCAssert( 0 <= fraction && fraction <= 1.0, @"ASRelativeDimension fraction value (%f) must be between 0 and 1.", fraction); + return ASRelativeDimensionMake(ASRelativeDimensionTypeFraction, fraction); } ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension) @@ -53,7 +53,7 @@ NSString *NSStringFromASRelativeDimension(ASRelativeDimension dimension) switch (dimension.type) { case ASRelativeDimensionTypePoints: return [NSString stringWithFormat:@"%.0fpt", dimension.value]; - case ASRelativeDimensionTypePercent: + case ASRelativeDimensionTypeFraction: return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0]; } } @@ -63,7 +63,7 @@ CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat parent switch (dimension.type) { case ASRelativeDimensionTypePoints: return dimension.value; - case ASRelativeDimensionTypePercent: + case ASRelativeDimensionTypeFraction: return dimension.value * parent; } } diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h index 9584094226..b41e66fb5f 100644 --- a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN /** 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. @example ASOuterLayoutSpec contains an ASInsetLayoutSpec with an ASInnerLayoutSpec. Suppose that: diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.h b/AsyncDisplayKit/Layout/ASLayoutSpec.h index 97f9fe1ca8..72d5b2445c 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.h @@ -40,11 +40,12 @@ NS_ASSUME_NONNULL_BEGIN * only require a single child. * * 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 - * setChild:forIdentifier:0. All other children should be set by defining convenience methods - * that call setChild:forIdentifier behind the scenes. + * a subclass should use this method to set the "primary" child. It can then use setChild:forIdentifier: + * to set any other required children. Ideally a subclass would hide this from the user, and use the + * setChild:forIdentifier: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild + * property that behind the scenes is calling setChild:forIdentifier:. */ -- (void)setChild:(id)child; +@property (nullable, strong, nonatomic) id child; /** * 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 * setChild: and setChild:forIdentifier: methods to do something appropriate or to assert. */ -- (void)setChildren:(NSArray> *)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)child; +@property (nullable, strong, nonatomic) NSArray> *children; /** * Returns the child added to this layout spec using the given index. @@ -99,11 +86,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable id)childForIndex:(NSUInteger)index; -/** - * Returns all children added to this layout spec. - */ -- (nullable NSArray> *)children; - @end @interface ASLayoutSpec (Debugging) diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h index 0f68d5e2dd..ad1d1f8b92 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.h +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -117,7 +117,7 @@ NS_ASSUME_NONNULL_BEGIN #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; diff --git a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm index 8b51c71148..32e1bdea84 100644 --- a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm @@ -11,6 +11,7 @@ #import "ASRatioLayoutSpec.h" #import +#import #import #import "ASAssert.h" @@ -64,7 +65,7 @@ // Choose the size closest to the desired ratio. 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. diff --git a/AsyncDisplayKit/Layout/ASRelativeSize.h b/AsyncDisplayKit/Layout/ASRelativeSize.h index 12f4845531..4f355044e9 100644 --- a/AsyncDisplayKit/Layout/ASRelativeSize.h +++ b/AsyncDisplayKit/Layout/ASRelativeSize.h @@ -39,11 +39,11 @@ NS_ASSUME_NONNULL_BEGIN 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); -/** Convenience constructor to provide size in Percentage. */ -extern ASRelativeSize ASRelativeSizeMakeWithPercent(CGFloat percent); +/** Convenience constructor to provide size as a fraction. */ +extern ASRelativeSize ASRelativeSizeMakeWithFraction(CGFloat fraction); /** Resolve this relative size relative to a parent size. */ extern CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize); @@ -61,7 +61,7 @@ extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelati extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact); -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactPercent(CGFloat percent); +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction); extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight); diff --git a/AsyncDisplayKit/Layout/ASRelativeSize.mm b/AsyncDisplayKit/Layout/ASRelativeSize.mm index 575c1f197e..3fc7abd92d 100644 --- a/AsyncDisplayKit/Layout/ASRelativeSize.mm +++ b/AsyncDisplayKit/Layout/ASRelativeSize.mm @@ -25,10 +25,10 @@ ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size) ASRelativeDimensionMakeWithPoints(size.height)); } -ASRelativeSize ASRelativeSizeMakeWithPercent(CGFloat percent) +ASRelativeSize ASRelativeSizeMakeWithFraction(CGFloat fraction) { - return ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(percent), - ASRelativeDimensionMakeWithPercent(percent)); + return ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(fraction), + ASRelativeDimensionMakeWithFraction(fraction)); } CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize) @@ -67,9 +67,9 @@ ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize 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, diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index 590c39bbf7..289dc234b1 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -85,4 +85,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutable.h b/AsyncDisplayKit/Layout/ASStaticLayoutable.h index 825b6ac40e..d1c3f74806 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutable.h +++ b/AsyncDisplayKit/Layout/ASStaticLayoutable.h @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @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; diff --git a/AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h b/AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h index e2675beba9..f26ac6b732 100644 --- a/AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h +++ b/AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h @@ -18,4 +18,4 @@ - (BOOL)isCancelled; - (void)cancel; -@end \ No newline at end of file +@end diff --git a/AsyncDisplayKit/Private/ASDataController+Subclasses.h b/AsyncDisplayKit/Private/ASDataController+Subclasses.h index dd3905d96c..ddb7af0dee 100644 --- a/AsyncDisplayKit/Private/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Private/ASDataController+Subclasses.h @@ -95,7 +95,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS * 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. */ - - (void)prepareForReloadData; + - (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount; /** * Notifies the subclass that the data controller is about to reload its data entirely @@ -104,7 +104,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS * concrete implementation. This is a great place to perform new node creation like supplementary views * or header/footer nodes. */ -- (void)willReloadData; +- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount; /** * Notifies the subclass to perform setup before sections are inserted in the data controller diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index aa99cfd031..6835071be6 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -344,7 +344,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo // The node is loaded but we're not on main. // We will call [self __setNeedsLayout] when we apply // 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]; } else { // The node is not loaded and we're not on main. diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index f6337ba03c..3e9bc3985b 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -21,8 +21,6 @@ #import "ASLayoutTransition.h" #import "ASEnvironment.h" -#include - @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @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 // 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 // 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 unsigned implementsInstanceDrawRect:1; @@ -124,7 +122,10 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // Main thread only _ASTransitionContext *_pendingLayoutTransitionContext; - BOOL _usesImplicitHierarchyManagement; + BOOL _automaticallyManagesSubnodes; + NSTimeInterval _defaultLayoutTransitionDuration; + NSTimeInterval _defaultLayoutTransitionDelay; + UIViewAnimationOptions _defaultLayoutTransitionOptions; int32_t _pendingTransitionID; ASLayoutTransition *_pendingLayoutTransition; diff --git a/AsyncDisplayKit/Private/ASImageNode+CGExtras.h b/AsyncDisplayKit/Private/ASImageNode+CGExtras.h index 74471ad62d..52b2109c50 100644 --- a/AsyncDisplayKit/Private/ASImageNode+CGExtras.h +++ b/AsyncDisplayKit/Private/ASImageNode+CGExtras.h @@ -20,7 +20,7 @@ ASDISPLAYNODE_EXTERN_C_BEGIN @param sourceImageSize The size of the encoded image. @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 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. @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. */ diff --git a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m b/AsyncDisplayKit/Private/ASImageNode+CGExtras.m index b988617198..446ec03da3 100644 --- a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m +++ b/AsyncDisplayKit/Private/ASImageNode+CGExtras.m @@ -9,6 +9,7 @@ // #import "ASImageNode+CGExtras.h" +#import // 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); @@ -20,7 +21,7 @@ static CGSize _ASSizeFillWithAspectRatio(CGFloat sizeToScaleAspectRatio, CGSize if (sizeToScaleAspectRatio > destinationAspectRatio) { return CGSizeMake(destinationSize.height * sizeToScaleAspectRatio, destinationSize.height); } 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. // 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; 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. // 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)) { - destinationWidth = (size_t)roundf(scaledSizeForImage.width); - destinationHeight = (size_t)roundf(scaledSizeForImage.height); + destinationWidth = (size_t)round(scaledSizeForImage.width); + destinationHeight = (size_t)round(scaledSizeForImage.height); if (destinationWidth == 0 || destinationHeight == 0) { *outBackingSize = CGSizeZero; *outDrawRect = CGRectZero; diff --git a/AsyncDisplayKit/Private/ASLayoutTransition.mm b/AsyncDisplayKit/Private/ASLayoutTransition.mm index e169b6e142..52af19fad9 100644 --- a/AsyncDisplayKit/Private/ASLayoutTransition.mm +++ b/AsyncDisplayKit/Private/ASLayoutTransition.mm @@ -16,6 +16,7 @@ #import "ASLayout.h" #import +#import #import "NSArray+Diffing.h" #import "ASEqualityHelpers.h" @@ -47,7 +48,8 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { } @implementation ASLayoutTransition { - ASDN::RecursiveMutex __instanceLock__; + std::shared_ptr __instanceLock__; + BOOL _calculatedSubnodeOperations; NSArray *_insertedSubnodes; NSArray *_removedSubnodes; @@ -61,6 +63,8 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { { self = [super init]; if (self) { + __instanceLock__ = std::make_shared(); + _node = node; _pendingLayout = pendingLayout; _previousLayout = previousLayout; @@ -70,7 +74,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (BOOL)isSynchronous { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); return !ASLayoutCanTransitionAsynchronous(_pendingLayout); } @@ -82,7 +86,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (void)applySubnodeInsertions { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; NSUInteger i = 0; @@ -95,7 +99,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (void)applySubnodeRemovals { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; for (ASDisplayNode *subnode in _removedSubnodes) { [subnode removeFromSupernode]; @@ -104,7 +108,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (void)calculateSubnodeOperationsIfNeeded { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if (_calculatedSubnodeOperations) { return; } @@ -134,27 +138,27 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); return _node.subnodes; } - (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; return _insertedSubnodes; } - (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; return _removedSubnodes; } - (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { return _previousLayout; } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { @@ -166,7 +170,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { return _previousLayout.constrainedSizeRange; } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 82cc1608be..629b4eee71 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -10,6 +10,8 @@ #import "ASStackPositionedLayout.h" +#import + #import "ASInternalHelpers.h" #import "ASLayoutSpecUtilities.h" @@ -105,16 +107,16 @@ ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnposition case ASStackLayoutJustifyContentStart: return stackedLayout(style, 0, unpositionedLayout, constrainedSize); case ASStackLayoutJustifyContentCenter: - return stackedLayout(style, floorf(violation / 2), unpositionedLayout, constrainedSize); + return stackedLayout(style, std::floor(violation / 2), unpositionedLayout, constrainedSize); case ASStackLayoutJustifyContentEnd: return stackedLayout(style, violation, unpositionedLayout, constrainedSize); case ASStackLayoutJustifyContentSpaceBetween: { 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: { // 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); } } diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm index e0026fcba9..0dfa11fb06 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm @@ -10,6 +10,7 @@ #import "ASStackUnpositionedLayout.h" +#import #import #import "ASLayoutSpecUtilities.h" @@ -87,7 +88,7 @@ static void stretchChildrenAlongCrossDimension(std::vector 0.01) { + if (alignItems == ASStackLayoutAlignItemsStretch && std::fabs(cross - childCrossMax) > 0.01) { l.layout = crossChildLayout(child, style, stack, stack, childCrossMax, childCrossMax); } } @@ -182,7 +183,7 @@ static const CGFloat kViolationEpsilon = 0.01; */ static std::function isFlexibleInViolationDirection(const CGFloat violation) { - if (fabs(violation) < kViolationEpsilon) { + if (std::fabs(violation) < kViolationEpsilon) { return [](const ASStackUnpositionedItem &l) { return NO; }; } else if (violation > 0) { return [](const ASStackUnpositionedItem &l) { return l.child.flexGrow; }; @@ -263,7 +264,7 @@ static void flexChildrenAlongStackDimension(std::vector } // 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 // contributions of the flexible children. const CGFloat violationRemainder = violation - (violationPerFlexChild * flexibleChildren); diff --git a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm index 3456308183..64eea95892 100644 --- a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm +++ b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm @@ -32,8 +32,8 @@ extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UI UIEdgeInsets insets = [image capInsets]; // These are lifted from what UIImageView does by experimentation. Without these exact values, the stretching is slightly off. - const float halfPixelFudge = 0.49f; - const float otherPixelFudge = 0.02f; + const CGFloat halfPixelFudge = 0.49f; + const CGFloat otherPixelFudge = 0.02f; // Convert to unit coordinates for the contentsCenter property. CGRect contentsCenter = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); if (insets.left > 0 || insets.right > 0) { diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm index 2b080cece7..649f39e8c3 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm @@ -15,6 +15,7 @@ #import "NSIndexSet+ASHelpers.h" #import "ASAssert.h" #import "ASDisplayNode+Beta.h" + #import #define ASFailUpdateValidation(...)\ diff --git a/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h b/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h index c558e859dc..20255c13ae 100644 --- a/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h +++ b/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h @@ -168,4 +168,4 @@ namespace ASTupleOperations } }; -} \ No newline at end of file +} diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.mm b/AsyncDisplayKit/TextKit/ASTextKitContext.mm index e1f65a0122..93958ce241 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitContext.mm @@ -10,13 +10,14 @@ #import "ASTextKitContext.h" #import "ASLayoutManager.h" +#import "ASThread.h" -#import +#include @implementation ASTextKitContext { // All TextKit operations (even non-mutative ones) must be executed serially. - std::mutex _textKitMutex; + std::shared_ptr __instanceLock__; NSLayoutManager *_layoutManager; NSTextStorage *_textStorage; @@ -35,8 +36,11 @@ { if (self = [super init]) { // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. - static std::mutex __static_mutex; - std::lock_guard l(__static_mutex); + static ASDN::Mutex __staticMutex; + ASDN::MutexLocker l(__staticMutex); + + __instanceLock__ = std::make_shared(); + // Create the TextKit component stack with our default configuration. if (textStorageCreationBlock) { _textStorage = textStorageCreationBlock(attributedString); @@ -60,13 +64,13 @@ - (CGSize)constrainedSize { - std::lock_guard l(_textKitMutex); + ASDN::MutexSharedLocker l(__instanceLock__); return _textContainer.size; } - (void)setConstrainedSize:(CGSize)constrainedSize { - std::lock_guard l(_textKitMutex); + ASDN::MutexSharedLocker l(__instanceLock__); _textContainer.size = constrainedSize; } @@ -74,7 +78,7 @@ NSTextStorage *, NSTextContainer *))block { - std::lock_guard l(_textKitMutex); + ASDN::MutexSharedLocker l(__instanceLock__); if (block) { block(_layoutManager, _textStorage, _textContainer); } diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm index bf910a10de..441aa5c921 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm @@ -8,12 +8,15 @@ // 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 #import +#import "ASTextKitContext.h" +#import "ASLayoutManager.h" + //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -48,7 +51,7 @@ [attrString enumerateAttributesInRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { if (attrs[NSFontAttributeName] != nil) { 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 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 scaleIndex++; - if (ceilf(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) { + if (std::ceil(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) { // we fit! we are done break; } diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm index 3a3abb5e47..1039f86494 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm @@ -11,6 +11,7 @@ #import "ASTextKitRenderer+Positioning.h" #import +#import #import "ASAssert.h" @@ -163,7 +164,7 @@ static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3; [self enumerateTextIndexesAtPosition:position usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { 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) { minimumGlyphDistance = currentDistance; minimumGlyphCharacterIndex = characterIndex; diff --git a/AsyncDisplayKit/TextKit/ASTextKitShadower.mm b/AsyncDisplayKit/TextKit/ASTextKitShadower.mm index 5da2b55b75..a4960e6699 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitShadower.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitShadower.mm @@ -10,6 +10,8 @@ #import "ASTextKitShadower.h" +#import + static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets) { 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 // blurRadius settings: - shadowPadding.top = fminf(0.0f, _shadowOffset.height - _shadowRadius); - shadowPadding.left = fminf(0.0f, _shadowOffset.width - _shadowRadius); + shadowPadding.top = std::fmin(0.0f, _shadowOffset.height - _shadowRadius); + shadowPadding.left = std::fmin(0.0f, _shadowOffset.width - _shadowRadius); - shadowPadding.bottom = fminf(0.0f, -_shadowOffset.height - _shadowRadius); - shadowPadding.right = fminf(0.0f, -_shadowOffset.width - _shadowRadius); + shadowPadding.bottom = std::fmin(0.0f, -_shadowOffset.height - _shadowRadius); + shadowPadding.right = std::fmin(0.0f, -_shadowOffset.width - _shadowRadius); _calculatedShadowPadding = shadowPadding; } diff --git a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h index 7ef30af71a..58df9cc83d 100644 --- a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h +++ b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h @@ -32,4 +32,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/UIImage+ASConvenience.m b/AsyncDisplayKit/UIImage+ASConvenience.m index 4b3ce606ec..afdaa39195 100644 --- a/AsyncDisplayKit/UIImage+ASConvenience.m +++ b/AsyncDisplayKit/UIImage+ASConvenience.m @@ -128,4 +128,4 @@ return result; } -@end \ No newline at end of file +@end diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index e8ad4e8c14..031810400c 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -11,7 +11,7 @@ // #import "_ASTransitionContext.h" - +#import "ASDisplayNode.h" #import "ASLayout.h" diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m index 3bfad70289..bc8972557b 100644 --- a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m +++ b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m @@ -45,7 +45,7 @@ }]; #pragma clang diagnostic pop - [self waitForExpectationsWithTimeout:3 handler:nil]; + [self waitForExpectationsWithTimeout:30 handler:nil]; } @end diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m index ba02de42e7..58de30ff19 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m @@ -294,34 +294,6 @@ 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: - (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheDelegate diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index 627960d12d..6ce6312aeb 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -47,28 +47,13 @@ @implementation ASDisplayNodeImplicitHierarchyTests -- (void)setUp { - [super setUp]; - [ASDisplayNode setUsesImplicitHierarchyManagement:YES]; -} - -- (void)tearDown { - [ASDisplayNode setUsesImplicitHierarchyManagement:NO]; - [super tearDown]; -} - - (void)testFeatureFlag { - XCTAssert([ASDisplayNode usesImplicitHierarchyManagement]); ASDisplayNode *node = [[ASDisplayNode alloc] init]; - XCTAssert(node.usesImplicitHierarchyManagement); - - [ASDisplayNode setUsesImplicitHierarchyManagement:NO]; - XCTAssertFalse([ASDisplayNode usesImplicitHierarchyManagement]); - XCTAssertFalse(node.usesImplicitHierarchyManagement); - - node.usesImplicitHierarchyManagement = YES; - XCTAssert(node.usesImplicitHierarchyManagement); + XCTAssertFalse(node.automaticallyManagesSubnodes); + + node.automaticallyManagesSubnodes = YES; + XCTAssertTrue(node.automaticallyManagesSubnodes); } - (void)testInitialNodeInsertionWithOrdering @@ -80,6 +65,7 @@ ASDisplayNode *node5 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASStaticLayoutSpec *staticLayout = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node4]]; @@ -106,6 +92,7 @@ ASDisplayNode *node3 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize){ ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; if ([strongNode.layoutState isEqualToNumber:@1]) { @@ -136,6 +123,7 @@ ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; if ([strongNode.layoutState isEqualToNumber:@1]) { @@ -179,6 +167,7 @@ ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; @@ -190,6 +179,8 @@ }; // Intentionally trigger view creation + [node view]; + [node1 view]; [node2 view]; XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"]; @@ -199,7 +190,7 @@ node.layoutState = @2; [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 dispatch_async(dispatch_get_main_queue(), ^{ XCTAssertEqual(node.subnodes[0], node2); diff --git a/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m b/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m new file mode 100644 index 0000000000..68f3c5b4be --- /dev/null +++ b/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m @@ -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 + +@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 diff --git a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m b/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m index 69d9e8bc7b..b922df272e 100644 --- a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m @@ -29,7 +29,7 @@ // trivial test case to ensure ASSnapshotTestCase works ASImageNode *imageNode = [[ASImageNode alloc] init]; imageNode.image = [self testImage]; - imageNode.frame = CGRectMake(0, 0, 100, 100); + [imageNode measure:CGSizeMake(100, 100)]; ASSnapshotVerifyNode(imageNode, nil); } diff --git a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m index bb182aa4a7..fe17358653 100644 --- a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m +++ b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m @@ -15,7 +15,7 @@ #import "ASLayout.h" @interface ASTestNode : ASDisplayNode -- (void)setLayoutSpecUnderTest:(ASLayoutSpec *)layoutSpecUnderTest sizeRange:(ASSizeRange)sizeRange; +@property (strong, nonatomic, nullable) ASLayoutSpec *layoutSpecUnderTest; @end @implementation ASLayoutSpecSnapshotTestCase @@ -37,18 +37,15 @@ [node addSubnode:subnode]; } - [node setLayoutSpecUnderTest:layoutSpec sizeRange:sizeRange]; + node.layoutSpecUnderTest = layoutSpec; + [node measureWithSizeRange:sizeRange]; ASSnapshotVerifyNode(node, identifier); } @end @implementation ASTestNode -{ - ASLayout *_layoutUnderTest; -} - - (instancetype)init { if (self = [super init]) { @@ -57,22 +54,9 @@ return self; } -- (void)setLayoutSpecUnderTest:(ASLayoutSpec *)layoutSpecUnderTest sizeRange:(ASSizeRange)sizeRange +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - ASLayout *layout = [layoutSpecUnderTest measureWithSizeRange:sizeRange]; - 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; + return _layoutSpecUnderTest; } @end diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.h b/AsyncDisplayKitTests/ASSnapshotTestCase.h index 5c1cf3d677..215d550ccd 100644 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.h +++ b/AsyncDisplayKitTests/ASSnapshotTestCase.h @@ -10,7 +10,7 @@ #import -#import +@class ASDisplayNode; #define ASSnapshotVerifyNode(node__, identifier__) \ { \ diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.m b/AsyncDisplayKitTests/ASSnapshotTestCase.m new file mode 100644 index 0000000000..ff5248bda2 --- /dev/null +++ b/AsyncDisplayKitTests/ASSnapshotTestCase.m @@ -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 diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.mm b/AsyncDisplayKitTests/ASSnapshotTestCase.mm deleted file mode 100644 index 3b2aaaadd0..0000000000 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.mm +++ /dev/null @@ -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 diff --git a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm index bf40f61133..3b30272d45 100644 --- a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm +++ b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm @@ -330,7 +330,7 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex) subnode2.staticSize = {50, 50}; ASRatioLayoutSpec *child1 = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.5 child:subnode1]; - child1.flexBasis = ASRelativeDimensionMakeWithPercent(1); + child1.flexBasis = ASRelativeDimensionMakeWithFraction(1); child1.flexGrow = YES; child1.flexShrink = YES; @@ -508,7 +508,7 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex) [self testStackLayoutSpecWithStyle:style sizeRange:kOverflowSize subnodes:subnodes identifier:@"overflow"]; } -- (void)testPercentageFlexBasisResolvesAgainstParentSize +- (void)testFractionalFlexBasisResolvesAgainstParentSize { 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. // 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}}; [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m new file mode 100644 index 0000000000..5fb5a3f556 --- /dev/null +++ b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m @@ -0,0 +1,56 @@ +// +// ASTextNodeSnapshotTests.m +// AsyncDisplayKit +// +// Created by Garrett Moon on 8/12/16. +// 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 + +@interface ASTextNodeSnapshotTests : ASSnapshotTestCase + +@end + +@implementation ASTextNodeSnapshotTests + +- (void)testTextContainerInset +{ + // trivial test case to ensure ASSnapshotTestCase works + ASTextNode *textNode = [[ASTextNode alloc] init]; + textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" + attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}]; + [textNode measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; + textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); + + ASSnapshotVerifyNode(textNode, nil); +} + +- (void)testTextContainerInsetHighlight +{ + UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; + backgroundView.layer.as_allowsHighlightDrawing = YES; + + ASTextNode *textNode = [[ASTextNode alloc] init]; + textNode.attributedText = [[NSAttributedString alloc] initWithString:@"yolo" + attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }]; + + [textNode measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; + textNode.frame = CGRectMake(50, 50, textNode.calculatedSize.width, textNode.calculatedSize.height); + textNode.textContainerInset = UIEdgeInsetsMake(5, 10, 10, 5); + + [backgroundView addSubview:textNode.view]; + backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50)); + + textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length); + + [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode]; + FBSnapshotVerifyLayer(backgroundView.layer, nil); +} + +@end diff --git a/AsyncDisplayKitTests/ASUICollectionViewTests.m b/AsyncDisplayKitTests/ASUICollectionViewTests.m new file mode 100644 index 0000000000..e325265741 --- /dev/null +++ b/AsyncDisplayKitTests/ASUICollectionViewTests.m @@ -0,0 +1,70 @@ +// +// ASUICollectionViewTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 8/18/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import + +@interface ASUICollectionViewTests : XCTestCase + +@end + +@implementation ASUICollectionViewTests + +/// Test normal item-affiliated supplementary node +- (void)testNormalTwoIndexSupplementaryElement +{ + [self _testSupplementaryNodeAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1] sectionCount:2 expectException:NO]; +} + +/// If your supp is indexPathForItem:inSection:, the section index must be in bounds +- (void)testThatSupplementariesWithItemIndexesMustBeWithinNormalSections +{ + [self _testSupplementaryNodeAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:3] sectionCount:2 expectException:YES]; +} + +/// If your supp is indexPathWithIndex:, that's OK even if that section is out of bounds! +- (void)testThatSupplementariesWithOneIndexAreOKOutOfSectionBounds +{ + [self _testSupplementaryNodeAtIndexPath:[NSIndexPath indexPathWithIndex:3] sectionCount:2 expectException:NO]; +} + +- (void)_testSupplementaryNodeAtIndexPath:(NSIndexPath *)indexPath sectionCount:(NSInteger)sectionCount expectException:(BOOL)shouldFail +{ + UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:@"SuppKind" withIndexPath:indexPath]; + attr.frame = CGRectMake(0, 0, 20, 20); + UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init]; + id layoutMock = [OCMockObject partialMockForObject:layout]; + + [[[[layoutMock expect] ignoringNonObjectArgs] andReturn:@[ attr ]] layoutAttributesForElementsInRect:CGRectZero]; + UICollectionView *cv = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:layoutMock]; + [cv registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:@"SuppKind" withReuseIdentifier:@"ReuseID"]; + + id dataSource = [OCMockObject niceMockForProtocol:@protocol(UICollectionViewDataSource)]; + __block id view = nil; + [[[dataSource expect] andDo:^(NSInvocation *invocation) { + NSIndexPath *indexPath = nil; + [invocation getArgument:&indexPath atIndex:4]; + view = [cv dequeueReusableSupplementaryViewOfKind:@"SuppKind" withReuseIdentifier:@"ReuseID" forIndexPath:indexPath]; + [invocation setReturnValue:&view]; + }] collectionView:cv viewForSupplementaryElementOfKind:@"SuppKind" atIndexPath:indexPath]; + [[[dataSource expect] andReturnValue:[NSNumber numberWithInteger:sectionCount]] numberOfSectionsInCollectionView:cv]; + + cv.dataSource = dataSource; + if (shouldFail) { + XCTAssertThrowsSpecificNamed([cv layoutIfNeeded], NSException, NSInternalInconsistencyException); + } else { + [cv layoutIfNeeded]; + XCTAssertEqualObjects(attr, [cv layoutAttributesForSupplementaryElementOfKind:@"SuppKind" atIndexPath:indexPath]); + XCTAssertEqual(view, [cv supplementaryViewForElementKind:@"SuppKind" atIndexPath:indexPath]); + } + + [dataSource verify]; + [layoutMock verify]; +} + +@end diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png new file mode 100644 index 0000000000..e5f40f70fd Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@2x.png rename to AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@3x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@3x.png rename to AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@3x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png new file mode 100644 index 0000000000..7e6cac14b8 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png new file mode 100644 index 0000000000..4a2fa33448 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png differ diff --git a/examples/ASDKLayoutTransition/Default-568h@2x.png b/examples/ASDKLayoutTransition/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples/ASDKLayoutTransition/Default-568h@2x.png differ diff --git a/examples/ASDKLayoutTransition/Default-667h@2x.png b/examples/ASDKLayoutTransition/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples/ASDKLayoutTransition/Default-667h@2x.png differ diff --git a/examples/ASDKLayoutTransition/Default-736h@3x.png b/examples/ASDKLayoutTransition/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples/ASDKLayoutTransition/Default-736h@3x.png differ diff --git a/examples/ASDKLayoutTransition/Podfile b/examples/ASDKLayoutTransition/Podfile new file mode 100644 index 0000000000..919de4b311 --- /dev/null +++ b/examples/ASDKLayoutTransition/Podfile @@ -0,0 +1,5 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '7.0' +target 'Sample' do + pod 'AsyncDisplayKit', :path => '../..' +end diff --git a/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..494f407d91 --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,368 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; + DFE855DDBC731242D3515B58 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C284F7E957985CA251284B05 /* libPods-Sample.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + 79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + C284F7E957985CA251284B05 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DFE855DDBC731242D3515B58 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + C284F7E957985CA251284B05 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + 79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */, + 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0710; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/ASDKLayoutTransition/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ASDKLayoutTransition/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/ASDKLayoutTransition/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..0b71c455d1 --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASDKLayoutTransition/Sample/AppDelegate.h b/examples/ASDKLayoutTransition/Sample/AppDelegate.h new file mode 100644 index 0000000000..27e560aafe --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/AppDelegate.h @@ -0,0 +1,24 @@ +// +// AppDelegate.h +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/ASDKLayoutTransition/Sample/AppDelegate.m b/examples/ASDKLayoutTransition/Sample/AppDelegate.m new file mode 100644 index 0000000000..c62355c06c --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/AppDelegate.m @@ -0,0 +1,33 @@ +// +// AppDelegate.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "AppDelegate.h" + +#import "ViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[ViewController alloc] init]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/ASDKLayoutTransition/Sample/Info.plist b/examples/ASDKLayoutTransition/Sample/Info.plist new file mode 100644 index 0000000000..fb4115c84c --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/ASDKLayoutTransition/Sample/ViewController.h b/examples/ASDKLayoutTransition/Sample/ViewController.h new file mode 100644 index 0000000000..fc52c022f2 --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/ViewController.h @@ -0,0 +1,22 @@ +// +// ViewController.h +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import + +@interface ViewController : UIViewController + +@end diff --git a/examples/ASDKLayoutTransition/Sample/ViewController.m b/examples/ASDKLayoutTransition/Sample/ViewController.m new file mode 100644 index 0000000000..c15ae1418b --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/ViewController.m @@ -0,0 +1,199 @@ +// +// ViewController.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "ViewController.h" + +#import + +#pragma mark - TransitionNode + +#define USE_CUSTOM_LAYOUT_TRANSITION 0 + +@interface TransitionNode : ASDisplayNode +@property (nonatomic, assign) BOOL enabled; +@property (nonatomic, strong) ASButtonNode *buttonNode; +@property (nonatomic, strong) ASTextNode *textNodeOne; +@property (nonatomic, strong) ASTextNode *textNodeTwo; +@end + +@implementation TransitionNode + + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super init]; + if (self == nil) { return self; } + + self.usesImplicitHierarchyManagement = YES; + + // Define the layout transition duration for the default transition + self.defaultLayoutTransitionDuration = 1.0; + + _enabled = NO; + + // Setup text nodes + _textNodeOne = [[ASTextNode alloc] init]; + _textNodeOne.attributedText = [[NSAttributedString alloc] initWithString:@"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled"]; + + _textNodeTwo = [[ASTextNode alloc] init]; + _textNodeTwo.attributedText = [[NSAttributedString alloc] initWithString:@"It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."]; + + // Setup button + NSString *buttonTitle = @"Start Layout Transition"; + UIFont *buttonFont = [UIFont systemFontOfSize:16.0]; + UIColor *buttonColor = [UIColor blueColor]; + + _buttonNode = [[ASButtonNode alloc] init]; + [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:ASControlStateNormal]; + + // Note: Currently we have to set all the button properties to the same one as for ASControlStateNormal. Otherwise + // if the button is involved in the layout transition it would break the transition as it does a layout pass + // while changing the title. This needs and will be fixed in the future! + [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:ASControlStateHighlighted]; + + + // Some debug colors + _textNodeOne.backgroundColor = [UIColor orangeColor]; + _textNodeTwo.backgroundColor = [UIColor greenColor]; + + + return self; +} + +- (void)didLoad +{ + [super didLoad]; + + [self.buttonNode addTarget:self action:@selector(buttonPressed:) forControlEvents:ASControlNodeEventTouchDown]; +} + +#pragma mark - Actions + +- (void)buttonPressed:(id)sender +{ + self.enabled = !self.enabled; + + [self transitionLayoutAnimated:YES measurementCompletion:nil]; +} + + +#pragma mark - Layout + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASTextNode *nextTextNode = self.enabled ? self.textNodeTwo : self.textNodeOne; + nextTextNode.flexGrow = YES; + nextTextNode.flexShrink = YES; + + ASStackLayoutSpec *horizontalStackLayout = [ASStackLayoutSpec horizontalStackLayoutSpec]; + horizontalStackLayout.children = @[nextTextNode]; + + self.buttonNode.alignSelf = ASStackLayoutAlignSelfCenter; + + ASStackLayoutSpec *verticalStackLayout = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStackLayout.spacing = 10.0; + verticalStackLayout.children = @[horizontalStackLayout, self.buttonNode]; + + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(15.0, 15.0, 15.0, 15.0) child:verticalStackLayout]; +} + + +#pragma mark - Transition + +#if USE_CUSTOM_LAYOUT_TRANSITION + +- (void)animateLayoutTransition:(id)context +{ + ASDisplayNode *fromNode = [[context removedSubnodes] objectAtIndex:0]; + ASDisplayNode *toNode = [[context insertedSubnodes] objectAtIndex:0]; + + ASButtonNode *buttonNode = nil; + for (ASDisplayNode *node in [context subnodesForKey:ASTransitionContextToLayoutKey]) { + if ([node isKindOfClass:[ASButtonNode class]]) { + buttonNode = (ASButtonNode *)node; + break; + } + } + + CGRect toNodeFrame = [context finalFrameForNode:toNode]; + toNodeFrame.origin.x += (self.enabled ? toNodeFrame.size.width : -toNodeFrame.size.width); + toNode.frame = toNodeFrame; + toNode.alpha = 0.0; + + CGRect fromNodeFrame = fromNode.frame; + fromNodeFrame.origin.x += (self.enabled ? -fromNodeFrame.size.width : fromNodeFrame.size.width); + + // We will use the same transition duration as the default transition + [UIView animateWithDuration:self.defaultLayoutTransitionDuration animations:^{ + toNode.frame = [context finalFrameForNode:toNode]; + toNode.alpha = 1.0; + + fromNode.frame = fromNodeFrame; + fromNode.alpha = 0.0; + + // Update frame of self + CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size; + CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size; + BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO); + if (isResized == YES) { + CGPoint position = self.frame.origin; + self.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height); + } + + buttonNode.frame = [context finalFrameForNode:buttonNode]; + } completion:^(BOOL finished) { + [context completeTransition:finished]; + }]; +} + +#endif + +@end + + +#pragma mark - ViewController + +@interface ViewController () +@property (nonatomic, strong) TransitionNode *transitionNode; +@end + +@implementation ViewController + +#pragma mark - UIViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _transitionNode = [TransitionNode new]; + [self.view addSubnode:_transitionNode]; + + // Some debug colors + _transitionNode.backgroundColor = [UIColor grayColor]; +} + +- (void)viewDidLayoutSubviews +{ + [super viewDidLayoutSubviews]; + + CGSize size = [self.transitionNode measure:self.view.frame.size]; + self.transitionNode.frame = CGRectMake(0, 20, size.width, size.height); +} + +@end diff --git a/examples/ASDKLayoutTransition/Sample/main.m b/examples/ASDKLayoutTransition/Sample/main.m new file mode 100644 index 0000000000..756080fb2b --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/main.m @@ -0,0 +1,26 @@ +// +// main.m +// Sample +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/ASDKgram/Sample/Utilities.m b/examples/ASDKgram/Sample/Utilities.m index b5a144a0be..556a3a2d2c 100644 --- a/examples/ASDKgram/Sample/Utilities.m +++ b/examples/ASDKgram/Sample/Utilities.m @@ -21,6 +21,85 @@ #define StrokeRoundedImages 0 +#define IsDigit(v) (v >= '0' && v <= '9') + +static time_t parseRfc3339ToTimeT(const char *string) +{ + int dy, dm, dd; + int th, tm, ts; + int oh, om, osign; + char current; + + if (!string) + return (time_t)0; + + // date + if (sscanf(string, "%04d-%02d-%02d", &dy, &dm, &dd) == 3) { + string += 10; + + if (*string++ != 'T') + return (time_t)0; + + // time + if (sscanf(string, "%02d:%02d:%02d", &th, &tm, &ts) == 3) { + string += 8; + + current = *string; + + // optional: second fraction + if (current == '.') { + ++string; + while(IsDigit(*string)) + ++string; + + current = *string; + } + + if (current == 'Z') { + oh = om = 0; + osign = 1; + } else if (current == '-') { + ++string; + if (sscanf(string, "%02d:%02d", &oh, &om) != 2) + return (time_t)0; + osign = -1; + } else if (current == '+') { + ++string; + if (sscanf(string, "%02d:%02d", &oh, &om) != 2) + return (time_t)0; + osign = 1; + } else { + return (time_t)0; + } + + struct tm timeinfo; + timeinfo.tm_wday = timeinfo.tm_yday = 0; + timeinfo.tm_zone = NULL; + timeinfo.tm_isdst = -1; + + timeinfo.tm_year = dy - 1900; + timeinfo.tm_mon = dm - 1; + timeinfo.tm_mday = dd; + + timeinfo.tm_hour = th; + timeinfo.tm_min = tm; + timeinfo.tm_sec = ts; + + // convert to utc + return timegm(&timeinfo) - (((oh * 60 * 60) + (om * 60)) * osign); + } + } + + return (time_t)0; +} + +static NSDate *parseRfc3339ToNSDate(NSString *rfc3339DateTimeString) +{ + time_t t = parseRfc3339ToTimeT([rfc3339DateTimeString cStringUsingEncoding:NSUTF8StringEncoding]); + return [NSDate dateWithTimeIntervalSince1970:t]; +} + + @implementation UIColor (Additions) + (UIColor *)darkBlueColor @@ -142,26 +221,15 @@ @implementation NSString (Additions) -// Returns a user-visible date time string that corresponds to the -// specified RFC 3339 date time string. Note that this does not handle -// all possible RFC 3339 date time strings, just one of the most common -// styles. +/* + * Returns a user-visible date time string that corresponds to the + * specified RFC 3339 date time string. Note that this does not handle + * all possible RFC 3339 date time strings, just one of the most common + * styles. + */ + (NSDate *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString { - NSDateFormatter * rfc3339DateFormatter; - NSLocale * enUSPOSIXLocale; - - // Convert the RFC 3339 date time string to an NSDate. - - rfc3339DateFormatter = [[NSDateFormatter alloc] init]; - - enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; - - [rfc3339DateFormatter setLocale:enUSPOSIXLocale]; - [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ'"]; - [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; - - return [rfc3339DateFormatter dateFromString:rfc3339DateTimeString]; + return parseRfc3339ToNSDate(rfc3339DateTimeString); } + (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString diff --git a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj index b7da6bb298..1bc3e8c8b3 100644 --- a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj +++ b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj @@ -14,13 +14,14 @@ 694993D81C8B334F00491CA5 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D71C8B334F00491CA5 /* ViewController.m */; }; 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 694993DC1C8B334F00491CA5 /* Assets.xcassets */; }; 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */; }; + 905C815E1D362E9400EA2625 /* CustomMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5E5E62821D13F39400D81E38 /* MapHandlerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MapHandlerNode.h; sourceTree = ""; }; - 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MapHandlerNode.m; sourceTree = ""; }; + 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = MapHandlerNode.m; sourceTree = ""; tabWidth = 2; }; 694993CD1C8B334F00491CA5 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 694993D11C8B334F00491CA5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 694993D31C8B334F00491CA5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -30,6 +31,8 @@ 694993DC1C8B334F00491CA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 694993DF1C8B334F00491CA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 694993E11C8B334F00491CA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 905C815C1D362E9400EA2625 /* CustomMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMapAnnotation.h; sourceTree = ""; }; + 905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMapAnnotation.m; sourceTree = ""; }; 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -89,6 +92,8 @@ 694993D71C8B334F00491CA5 /* ViewController.m */, 5E5E62821D13F39400D81E38 /* MapHandlerNode.h */, 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */, + 905C815C1D362E9400EA2625 /* CustomMapAnnotation.h */, + 905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */, 694993DC1C8B334F00491CA5 /* Assets.xcassets */, 694993D01C8B334F00491CA5 /* Supporting Files */, ); @@ -229,6 +234,7 @@ 694993D81C8B334F00491CA5 /* ViewController.m in Sources */, 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */, 694993D21C8B334F00491CA5 /* main.m in Sources */, + 905C815E1D362E9400EA2625 /* CustomMapAnnotation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Contents.json b/examples/ASMapNode/Sample/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/examples/ASMapNode/Sample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json new file mode 100644 index 0000000000..273884cba6 --- /dev/null +++ b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "hill.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "hill@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "hill@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill.png b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill.png new file mode 100644 index 0000000000..8998668eb0 Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@2x.png b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@2x.png new file mode 100644 index 0000000000..d64af0dd9d Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@2x.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@3x.png b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@3x.png new file mode 100644 index 0000000000..761c66684a Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@3x.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/Contents.json b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/Contents.json new file mode 100644 index 0000000000..f54c1c3b60 --- /dev/null +++ b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "water.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "water@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "water@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water.png b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water.png new file mode 100644 index 0000000000..cdff6fd035 Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@2x.png b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@2x.png new file mode 100644 index 0000000000..2cd019f20c Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@2x.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@3x.png b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@3x.png new file mode 100644 index 0000000000..e45cd67f2d Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@3x.png differ diff --git a/examples/ASMapNode/Sample/CustomMapAnnotation.h b/examples/ASMapNode/Sample/CustomMapAnnotation.h new file mode 100644 index 0000000000..d94f0153f4 --- /dev/null +++ b/examples/ASMapNode/Sample/CustomMapAnnotation.h @@ -0,0 +1,28 @@ +// +// CustomMapAnnotation.h +// ASDKMapTest +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import +#import + +@interface CustomMapAnnotation : NSObject + +@property (assign, nonatomic) CLLocationCoordinate2D coordinate; +@property (copy, nonatomic, nullable) UIImage *image; +@property (copy, nonatomic, nullable) NSString *title; +@property (copy, nonatomic, nullable) NSString *subtitle; + +@end diff --git a/examples/ASMapNode/Sample/CustomMapAnnotation.m b/examples/ASMapNode/Sample/CustomMapAnnotation.m new file mode 100644 index 0000000000..a5da10ac94 --- /dev/null +++ b/examples/ASMapNode/Sample/CustomMapAnnotation.m @@ -0,0 +1,22 @@ +// +// CustomMapAnnotation.m +// ASDKMapTest +// +// 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. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "CustomMapAnnotation.h" + +@implementation CustomMapAnnotation + +@end diff --git a/examples/ASMapNode/Sample/MapHandlerNode.m b/examples/ASMapNode/Sample/MapHandlerNode.m index 8a78445bb2..fc55eb6bc6 100644 --- a/examples/ASMapNode/Sample/MapHandlerNode.m +++ b/examples/ASMapNode/Sample/MapHandlerNode.m @@ -16,6 +16,7 @@ // #import "MapHandlerNode.h" +#import "CustomMapAnnotation.h" #import @@ -90,6 +91,22 @@ [_liveMapToggleButton setTitle:[self liveMapStr] withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal]; [_liveMapToggleButton setTitle:[self liveMapStr] withFont:[UIFont systemFontOfSize:14] withColor:[UIColor blueColor] forState:ASControlStateHighlighted]; [_liveMapToggleButton addTarget:self action:@selector(toggleLiveMap) forControlEvents:ASControlNodeEventTouchUpInside]; + + // avoiding retain cycles + __weak MapHandlerNode *weakSelf = self; + + self.mapNode.imageForStaticMapAnnotationBlock = ^UIImage *(id annotation, CGPoint *centerOffset){ + MapHandlerNode *grabbedSelf = weakSelf; + if (grabbedSelf) { + if ([annotation isKindOfClass:[CustomMapAnnotation class]]) { + CustomMapAnnotation *customAnnotation = (CustomMapAnnotation *)annotation; + return customAnnotation.image; + } + } + return nil; + }; + + [self addAnnotations]; } #pragma mark - Layout @@ -183,6 +200,30 @@ #pragma mark - Helpers +- (void)addAnnotations { + + MKPointAnnotation *brno = [MKPointAnnotation new]; + brno.coordinate = CLLocationCoordinate2DMake(49.2002211, 16.6078411); + brno.title = @"Brno city"; + + CustomMapAnnotation *atlantic = [CustomMapAnnotation new]; + atlantic.coordinate = CLLocationCoordinate2DMake(38.6442228, -29.9956942); + atlantic.title = @"Atlantic ocean"; + atlantic.image = [UIImage imageNamed:@"Water"]; + + CustomMapAnnotation *kilimanjaro = [CustomMapAnnotation new]; + kilimanjaro.coordinate = CLLocationCoordinate2DMake(-3.075833, 37.353333); + kilimanjaro.title = @"Kilimanjaro"; + kilimanjaro.image = [UIImage imageNamed:@"Hill"]; + + CustomMapAnnotation *mtblanc = [CustomMapAnnotation new]; + mtblanc.coordinate = CLLocationCoordinate2DMake(45.8325, 6.864444); + mtblanc.title = @"Mont Blanc"; + mtblanc.image = [UIImage imageNamed:@"Hill"]; + + self.mapNode.annotations = @[brno, atlantic, kilimanjaro, mtblanc]; +} + -(NSString *)liveMapStr { return _mapNode.liveMap ? @"Live Map is ON" : @"Live Map is OFF"; @@ -235,6 +276,21 @@ return YES; } +- (MKAnnotationView *)annotationViewForAnnotation:(id)annotation +{ + MKAnnotationView *av; + if ([annotation isKindOfClass:[CustomMapAnnotation class]]) { + av = [[MKAnnotationView alloc] init]; + av.centerOffset = CGPointMake(21, 21); + av.image = [(CustomMapAnnotation *)annotation image]; + } else { + av = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; + } + + av.opaque = NO; + return av; +} + #pragma mark - MKMapViewDelegate - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { @@ -244,4 +300,9 @@ _deltaLonEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", mapView.region.span.longitudeDelta]]; } +- (MKAnnotationView *)mapView:(MKMapView *)__unused mapView viewForAnnotation:(id)annotation +{ + return [self annotationViewForAnnotation:annotation]; +} + @end diff --git a/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASPagerNode.m b/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASPagerNode.m index 8b7f39d840..b2a4499b89 100644 --- a/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASPagerNode.m +++ b/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASPagerNode.m @@ -47,7 +47,7 @@ static UIColor *OverViewASPagerNodeRandomColor() { #pragma mark - OverviewASPagerNode -@interface OverviewASPagerNode () +@interface OverviewASPagerNode () @property (nonatomic, strong) ASPagerNode *node; @property (nonatomic, copy) NSArray *data; @end @@ -61,6 +61,7 @@ static UIColor *OverViewASPagerNodeRandomColor() { _node = [ASPagerNode new]; _node.dataSource = self; + _node.delegate = self; [self addSubnode:_node]; return self; diff --git a/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m b/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m index 54bd530b1a..2734bb5265 100644 --- a/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m +++ b/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m @@ -425,8 +425,8 @@ typedef ASLayoutSpec *(^OverviewDisplayNodeSizeThatFitsBlock)(ASSizeRange constr verticalStackLayoutSpec.spacing = 5.0; // Spacing between children // Layout the stack layout with 100% width and 100% height of the parent node - ASRelativeSizeRange sizeRange = ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimensionMakeWithPercent(1), - ASRelativeDimensionMakeWithPercent(1)); + ASRelativeSizeRange sizeRange = ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimensionMakeWithFraction(1), + ASRelativeDimensionMakeWithFraction(1)); verticalStackLayoutSpec.sizeRange = sizeRange; // Wrap the static stack layout in a static spec so it will grow to the whole parent node size diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.m b/examples/CatDealsCollectionView/Sample/ItemNode.m index e383abf16f..34da74cfbc 100644 --- a/examples/CatDealsCollectionView/Sample/ItemNode.m +++ b/examples/CatDealsCollectionView/Sample/ItemNode.m @@ -107,7 +107,7 @@ const CGFloat kSoldOutGBHeight = 50.0; self.soldOutLabelFlat.layerBacked = YES; self.soldOutLabelBackground = [[ASDisplayNode alloc] init]; - self.soldOutLabelBackground.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight))); + self.soldOutLabelBackground.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight))); self.soldOutLabelBackground.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9]; self.soldOutLabelBackground.flexGrow = YES; self.soldOutLabelBackground.layerBacked = YES; @@ -289,7 +289,7 @@ const CGFloat kSoldOutGBHeight = 50.0; ASRatioLayoutSpec *imagePlace = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:imageRatio child:self.dealImageView]; self.badge.layoutPosition = CGPointMake(0, constrainedSize.max.height - kFixedLabelsAreaHeight - kBadgeHeight); - self.badge.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(0), ASRelativeDimensionMakeWithPoints(kBadgeHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kBadgeHeight))); + self.badge.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(0), ASRelativeDimensionMakeWithPoints(kBadgeHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(1), ASRelativeDimensionMakeWithPoints(kBadgeHeight))); ASStaticLayoutSpec *badgePosition = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.badge]]; ASOverlayLayoutSpec *badgeOverImage = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imagePlace overlay:badgePosition]; diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m index 74a5f53cc8..c474bbbbae 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m @@ -19,7 +19,7 @@ #import "ViewController.h" #import "GradientTableNode.h" -@interface ViewController () +@interface ViewController () { ASPagerNode *_pagerNode; } @@ -38,6 +38,8 @@ _pagerNode = [[ASPagerNode alloc] init]; _pagerNode.dataSource = self; + _pagerNode.delegate = self; + [ASRangeController setShouldShowRangeDebugOverlay:YES]; // Could implement ASCollectionDelegate if we wanted extra callbacks, like from UIScrollView. //_pagerNode.delegate = self; diff --git a/plans/section-infos-api/ASCollectionSection.h b/plans/section-infos-api/ASCollectionSection.h new file mode 100644 index 0000000000..cbef6af785 --- /dev/null +++ b/plans/section-infos-api/ASCollectionSection.h @@ -0,0 +1,27 @@ +/** + * Information about a section of items in a collection. + * + * This class is private to ASDK. + * Data sources may override -collectionView:infoForSectionAtIndex: to provide the + * "userInfo" object when the section is initially inserted. ASCollectionView + * vends the user info object publicly via "infoForSectionAtIndex:". +*/ +@interface ASCollectionSection : NSObject + +// Autoincrementing value, set by collection view immediately after creation. +@property NSInteger sectionID; + +@property NSMutableDictionary *> *editingNodesByKind; +@property NSMutableDictionary *> *completedNodesByKind; + +@property (strong, nullable) id userInfo; + +@end + +@protocol ASSectionUserInfo +// This will be set once, immediately after the object is returned by the data source. +@property (weak, nonatomic, nullable) ASCollectionView *collectionView; + +// Could be optional, but need to cache -respondsToSelector: dynamically. +@property (nullable, readonly, copy) NSString *sectionName; +@end diff --git a/plans/section-infos-api/ASExampleLayoutSectionInfo.m b/plans/section-infos-api/ASExampleLayoutSectionInfo.m new file mode 100644 index 0000000000..26fef393fe --- /dev/null +++ b/plans/section-infos-api/ASExampleLayoutSectionInfo.m @@ -0,0 +1,22 @@ +/** + * An example of what a section info class might look like. + */ +@interface ASExampleLayoutSectionInfo : NSObject + +@property (nonatomic, weak, nullable) ASCollectionView *collectionView; +@property (nullable, copy) NSString *sectionName; + +@property CGSize cellSpacing; +@property NSInteger numberOfColumns; +@property CGFloat columnWidth; +@property CGSize headerSize; +@property CGSize footerSize; +@property UIEdgeInsets headerInsets; +@property UIEdgeInsets footerInsets; +@property ASExampleLayoutBackgroundType backgroundType; +@property ASExampleLayoutRowAlignmentType rowAlignmentType; +@end + +@implementation ASExampleLayoutSectionInfo + +@end \ No newline at end of file diff --git a/plans/section-infos-api/Misc.m b/plans/section-infos-api/Misc.m new file mode 100644 index 0000000000..a114acfe77 --- /dev/null +++ b/plans/section-infos-api/Misc.m @@ -0,0 +1,31 @@ +// Added to ASCollectionDataSource: + +/** + * Data sources can override this method to return custom info associated with a + * section of the collection view. + * + * These section info objects can be read by a UICollectionViewLayout subclass + * and used to configure the layout for that section. @see ASSectionUserInfo + */ +@optional +- (nullable id)collectionView:(ASCollectionView *)collectionView infoForSectionAtIndex:(NSInteger)sectionIndex; + +// ---- +// Added to ASCollectionView: + +// Reads from data controller's _completedSections. Asserts that section index is in bounds. +- (nullable id)infoForSectionAtIndex:(NSInteger)sectionIndex; + +// ---- +// In ASDataController.mm: + +// Replace _editingNodes and _completedNodes with: +NSMutableArray *_editingSections; +NSMutableArray *_completedSections; + +// Modify _reloadDataWithAnimationOptions and insertSections:withAnimationOptions:. +// In those methods we use _populateFromDataSourceWithSectionIndexSet to get the node blocks. +// Now we will also need to create the ASCollectionSections and ask for UserInfos, just before we get the node blocks. + +// In essence, wherever we use an NSMutableArray of nodes to represent a section, we now +// will use an ASCollectionSection instead. diff --git a/plans/section-infos-api/Overview.md b/plans/section-infos-api/Overview.md new file mode 100644 index 0000000000..485c86d587 --- /dev/null +++ b/plans/section-infos-api/Overview.md @@ -0,0 +1,34 @@ +# Overview + +There is an established pattern where UICollectionViewLayout talks directly to the collection view's delegate to get additional layout info e.g. the size of a header in a given section. + +This pattern is established by Apple's flow layout, and it is used by Pinterest and I'm sure others. It is dangerous when used with ASDK because we update asynchronously, so +for instance if you delete a section from your data source, the layout won't find out until later and in the meantime it may ask the delegate about a section that doesn't exist! + +The solution is to capture this kind of information from the data source immediately when a section is inserted, and make it available to the layout as we update the UICollectionView so that everyone is on the same page. + +Enter: ASSectionUserInfo + +Internally, we use a private object ASCollectionSection to represent one version of a section of items and supplementaries. If the user wants, they can provide us with an ASSectionUserInfo object to accompany the section, which will be read synchronously when the section is inserted. + +## Usage During Layout + +The collection view will make these section infos available in the same way that finished nodes are currently available. + +#### [Sequence Diagram:][diag-layout-usage] + +![][image-layout-usage] + +## Creation When Inserting Sections + +The section infos for any inserted/reloaded sections are queried synchronously, before the node blocks for their items. The top part of this diagram is the same as the current behavior but I think it's useful info =) + +#### [Sequence Diagram:][diag-inserting-sections] + +![][image-inserting-sections] + + +[diag-inserting-sections]: https://www.websequencediagrams.com/?lz=dGl0bGUgSW5zZXJ0aW5nL1JlbG9hZGluZyBTZWN0aW9ucwoKRGF0YSBTb3VyY2UtPkNWOiBpACoFABkIQXRJbmRleGVzOgpDVi0-Q2hhbmdlU2V0IAAzBUNvbnRyb2xsZXIAHRsAURFlbmRVcGRhdGVzADQgAB4MAGIYLT4AehFiZWdpbgAFNACBYxlsb29wIGZvciBlYWNoIHMAgjgGIGluZGV4CiAgIACBAhJDVjoAHwhJbmZvAIJABzoAKAVDVgCBLQcAgnEGAA8aAIMQDACDFwZSZXR1cm4gaWQ8QVMAgz0HVXNlckluZm8-AFQIAIF-EwAWIQCBUg5pdGVtIGluAIFgCACBXQUAgU0Zbm9kZUJsb2NrAIQkB1BhdGgAgWIGAIFXFQARHgCBWRlub2RlIGJsb2NrAE8MAIFRGgAhD2VuZAplbmQAhBktAIUcCwCEYxAAhW0cAIUPGwCGWwYKAIMaCgCDeQgKCg&s=napkin +[image-inserting-sections]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgSW5zZXJ0aW5nL1JlbG9hZGluZyBTZWN0aW9ucwoKRGF0YSBTb3VyY2UtPkNWOiBpACoFABkIQXRJbmRleGVzOgpDVi0-Q2hhbmdlU2V0IAAzBUNvbnRyb2xsZXIAHRsAURFlbmRVcGRhdGVzADQgAB4MAGIYLT4AehFiZWdpbgAFNACBYxlsb29wIGZvciBlYWNoIHMAgjgGIGluZGV4CiAgIACBAhJDVjoAHwhJbmZvAIJABzoAKAVDVgCBLQcAgnEGAA8aAIMQDACDFwZSZXR1cm4gaWQ8QVMAgz0HVXNlckluZm8-AFQIAIF-EwAWIQCBUg5pdGVtIGluAIFgCACBXQUAgU0Zbm9kZUJsb2NrAIQkB1BhdGgAgWIGAIFXFQARHgCBWRlub2RlIGJsb2NrAE8MAIFRGgAhD2VuZAplbmQAhBktAIUcCwCEYxAAhW0cAIUPGwCGWwYKAIMaCgCDeQgKCg&s=napkin +[image-layout-usage]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgcHJlcGFyZUxheW91dCAtIHNlY3Rpb24gbWV0cmljcwoKQ1YtPgAYBjoAHw4KbG9vcCBmb3IgZWFjaAAxCAogICAgCiAgICAATQYtPkNWOgBOCEluZm9BdEluZGV4OgAkBUNWLQBUClJldHVybiBQSU1hc29ucnlTACsKCgBFDQAOFDogY29sdW1uQ291bnQAgQAFADQUAFwSACYQAEYed2lkdGhPZkMAZgUAgT0NAEgmADsFAIIRDQCCUwhVc2UAgmsJZW5kCgoAgjgHAII6BihPSykK&s=napkin +[diag-layout-usage]: https://www.websequencediagrams.com/?lz=dGl0bGUgcHJlcGFyZUxheW91dCAtIHNlY3Rpb24gbWV0cmljcwoKQ1YtPgAYBjoAHw4KbG9vcCBmb3IgZWFjaAAxCAogICAgCiAgICAATQYtPkNWOgBOCEluZm9BdEluZGV4OgAkBUNWLQBUClJldHVybiBQSU1hc29ucnlTACsKCgBFDQAOFDogY29sdW1uQ291bnQAgQAFADQUAFwSACYQAEYed2lkdGhPZkMAZgUAgT0NAEgmADsFAIIRDQCCUwhVc2UAgmsJZW5kCgoAgjgHAII6BihPSykK&s=napkin