diff --git a/.github/GITHUB_RULES.md b/.github/GITHUB_RULES.md index 156168ac16..e81cbf29f3 100644 --- a/.github/GITHUB_RULES.md +++ b/.github/GITHUB_RULES.md @@ -56,6 +56,7 @@ Comment for moving to Ship: @\ The community is planning an exciting long term road map for the project and getting organized around how to deliver these feature requests. -If you are interested in helping contribute to this component or any other, don’t hesitate to send us an email at AsyncDisplayKit@gmail.com or ping us on ASDK's Slack (#1582). If you would like to contribute for a few weeks, we can also add you to our Ship bug tracker so that you can see what everyone is working on and actively coordinate with us. +If you are interested in helping contribute to this component or any other, don’t hesitate to send us an email at AsyncDisplayKit@gmail.com or ping us on +ASDK's Slack channel. If you would like to contribute for a few weeks, we can also add you to our Ship bug tracker so that you can see what everyone is working on and actively coordinate with us. -As always, keep filing issues and submitting pull requests here on Github and we will only move things to the new tracker if they require long term coordination. \ No newline at end of file +As always, keep filing issues and submitting pull requests here on Github and we will only move things to the new tracker if they require long term coordination. diff --git a/.gitignore b/.gitignore index 84de6767f4..253f8380d5 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,6 @@ Pods DerivedData build -docs/_site -docs/htdocs -docs/.sass-cache - *.swp *.lock diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 8505b88f61..bdcdb556ce 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |spec| spec.name = 'AsyncDisplayKit' - spec.version = '1.9.80' + spec.version = '2.0-beta.1' spec.license = { :type => 'BSD' } spec.homepage = 'http://asyncdisplaykit.org' spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' } spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' - spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.80' } + spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.90' } spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/' @@ -59,7 +59,7 @@ Pod::Spec.new do |spec| spec.subspec 'PINRemoteImage' do |pin| pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } - pin.dependency 'PINRemoteImage/iOS', '>= 3.0.0-beta.2' + pin.dependency 'PINRemoteImage/iOS', '>= 3.0.0-beta.3' pin.dependency 'AsyncDisplayKit/Core' end diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e0cac1442b..33330578e8 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -10,38 +10,26 @@ 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */; }; 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */; }; 044284FF1BAA3BD600D16268 /* UICollectionViewLayout+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 044285071BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; }; 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; }; 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; }; 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; }; - 0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; }; 044285101BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; }; 0515EA211A15769900BA8B9A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 0515EA221A1576A100BA8B9A /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; - 0516FA3C1A15563400B4EBED /* ASAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3A1A15563400B4EBED /* ASAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0516FA3D1A15563400B4EBED /* ASLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */; }; 051943131A1575630030A7D0 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; 051943151A1575670030A7D0 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 052EE06B1A15A0D8002C6279 /* TestResources in Resources */ = {isa = PBXBuildFile; fileRef = 052EE06A1A15A0D8002C6279 /* TestResources */; }; - 054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */; }; - 055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */; }; - 055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */; }; - 055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */; }; - 055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */; }; - 0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; 057D02C41AC0A66700C7AC3C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C31AC0A66700C7AC3C /* main.m */; }; 057D02C71AC0A66700C7AC3C /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C61AC0A66700C7AC3C /* AppDelegate.mm */; }; - 0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */; }; 058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; 058D09BE195D04C000B7D73C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09BD195D04C000B7D73C /* XCTest.framework */; }; @@ -77,59 +65,20 @@ 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */; }; 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A36195D057000B7D73C /* ASTextNodeTests.m */; }; 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */; }; - 058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D5195D050800B7D73C /* ASControlNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D8195D050800B7D73C /* ASDisplayNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A4F195D05CB00B7D73C /* ASImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09DD195D050800B7D73C /* ASImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09DF195D050800B7D73C /* ASTextNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E4195D050800B7D73C /* _ASDisplayView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A59195D05DC00B7D73C /* ASMutableAttributedStringBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A6A195D05EC00B7D73C /* _ASAsyncTransactionContainer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A6B195D05EC00B7D73C /* _ASAsyncTransactionContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FB195D050800B7D73C /* _ASAsyncTransactionContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A6D195D05EC00B7D73C /* _ASAsyncTransactionGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A6F195D05EC00B7D73C /* UIView+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A02195D050800B7D73C /* _AS-objc-internal.h */; }; - 058D0A72195D05F800B7D73C /* _ASCoreAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; }; - 058D0A74195D05F800B7D73C /* _ASPendingState.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A05195D050800B7D73C /* _ASPendingState.h */; }; - 058D0A76195D05F900B7D73C /* _ASScopeTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A07195D050800B7D73C /* _ASScopeTimer.h */; }; - 058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */; }; - 058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */; }; - 058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */; }; - 058D0A7F195D05F900B7D73C /* ASSentinel.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A10195D050800B7D73C /* ASSentinel.h */; }; - 058D0A81195D05F900B7D73C /* ASThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A82195D060300B7D73C /* ASAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A43195D058D00B7D73C /* ASAssert.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 058D0A83195D060300B7D73C /* ASBaseDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A44195D058D00B7D73C /* ASBaseDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; - 05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; - 1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; 204C979E1B362CB3002B1083 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 204C979D1B362CB3002B1083 /* Default-568h@2x.png */; }; - 205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */; }; 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; - 205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205F0E1A1B37339C007741D0 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; }; - 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; - 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; - 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; }; 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; - 251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; - 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; }; 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; }; 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */; }; 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */; }; @@ -160,42 +109,23 @@ 254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; }; 254C6B8D1BF94F8A003EC431 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */; }; 257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */; }; - 257754A51BEE44CD00737CA5 /* ASTextKitRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; }; - 257754A71BEE44CD00737CA5 /* ASTextKitAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754971BEE44CD00737CA5 /* ASTextKitContext.mm */; }; - 257754AA1BEE44CD00737CA5 /* ASTextKitEntityAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */; }; 257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */; }; - 257754AD1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */; }; - 257754AF1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; }; - 257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; }; - 257754B31BEE44CD00737CA5 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; }; - 257754B51BEE44CD00737CA5 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; }; 257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; }; - 257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 257754C11BEE458E00737CA5 /* ASTextKitComponents.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; }; - 25E327561C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25E327571C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25E327581C16819500A2170C /* ASPagerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25E327551C16819500A2170C /* ASPagerNode.m */; }; 25E327591C16819500A2170C /* ASPagerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25E327551C16819500A2170C /* ASPagerNode.m */; }; 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; - 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2967F9E21AB0A5190072E4AB /* ASBasicImageDownloaderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */; }; 296A0A351A951ABF005ACEAA /* ASBatchFetchingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */; }; - 299DA1A91A828D2900162D41 /* ASBatchContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 299DA1A71A828D2900162D41 /* ASBatchContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */; }; 2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -203,7 +133,7 @@ 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; }; - 34EFC75E1B701BF000AD841F /* ASInternalHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.mm */; }; + 34EFC75E1B701BF000AD841F /* ASInternalHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */; }; 34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */; }; 34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -232,15 +162,11 @@ 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */; }; 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */; }; 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; }; - 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */; }; 430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */; }; - 464052201A3F83C40061C0BA /* ASDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 464052211A3F83C40061C0BA /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; }; - 464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */; }; - 464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521D1A3F83C40061C0BA /* ASLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; }; @@ -254,85 +180,68 @@ 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; 68355B3A1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; }; - 68355B3B1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68355B3C1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */; }; - 68355B3D1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; }; 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; }; - 68355B3F1CB57A64001D4E68 /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; }; + 68355B3F1CB57A64001D4E68 /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */; }; 68355B411CB57A6C001D4E68 /* ASImageContainerProtocolCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */; }; - 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68B8A4DC1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */; }; - 68B8A4E11CBDB958007E4543 /* ASWeakProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */; }; 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */; }; 68B8A4E31CBDB958007E4543 /* ASWeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B8A4E01CBDB958007E4543 /* ASWeakProxy.m */; }; 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B8A4E01CBDB958007E4543 /* ASWeakProxy.m */; }; - 68EE0DBD1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; }; 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; }; 68EE0DBF1C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; - 68FC85DE1CE29AB700EDD713 /* ASNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68FC85DF1CE29AB700EDD713 /* ASNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85DD1CE29AB700EDD713 /* ASNavigationController.m */; }; - 68FC85E21CE29B7E00EDD713 /* ASTabBarController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68FC85E31CE29B7E00EDD713 /* ASTabBarController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68FC85E41CE29B7E00EDD713 /* ASTabBarController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E11CE29B7E00EDD713 /* ASTabBarController.m */; }; 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E11CE29B7E00EDD713 /* ASTabBarController.m */; }; 68FC85E61CE29B9400EDD713 /* ASNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85DD1CE29AB700EDD713 /* ASNavigationController.m */; }; - 68FC85E91CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68FC85EB1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */; }; 68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */; }; 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */; }; - 697C0DE31CF38F28001DE0D4 /* ASLayoutValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = 697C0DE11CF38F28001DE0D4 /* ASLayoutValidation.h */; }; 697C0DE41CF38F28001DE0D4 /* ASLayoutValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = 697C0DE11CF38F28001DE0D4 /* ASLayoutValidation.h */; }; 697C0DE51CF38F28001DE0D4 /* ASLayoutValidation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 697C0DE21CF38F28001DE0D4 /* ASLayoutValidation.mm */; }; 697C0DE61CF38F28001DE0D4 /* ASLayoutValidation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 697C0DE21CF38F28001DE0D4 /* ASLayoutValidation.mm */; }; - 698548631CA9E025008A345F /* ASEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = 698548611CA9E025008A345F /* ASEnvironment.h */; settings = {ATTRIBUTES = (Public, ); }; }; 698548641CA9E025008A345F /* ASEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = 698548611CA9E025008A345F /* ASEnvironment.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 698C8B611CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 698C8B621CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 69CB62AB1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; }; 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; }; 69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; }; 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; }; - 69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; }; 69E1006E1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; }; 69E1006F1CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; }; 69E100701CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; }; - 69F10C861C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; settings = {ATTRIBUTES = (Public, ); }; }; 764D83D61C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */; }; - 767E7F8D1C9019130066C000 /* AsyncDisplayKit+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; settings = {ATTRIBUTES = (Public, ); }; }; 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */; }; 7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */; }; - 7A06A73B1C35F08800FE8DAA /* ASRelativeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */; }; 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */; }; - 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; - 8B0768B31CE752EC002E1453 /* ASDefaultPlaybackButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; }; + 83A7D95A1D44542100BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; + 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; + 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A7D9581D44542100BF333E /* ASWeakMap.h */; }; + 83A7D95E1D446A6E00BF333E /* ASWeakMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */; }; 8B0768B41CE752EC002E1453 /* ASDefaultPlaybackButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */; }; 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; }; 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */; }; - 8BDA5FC51CDBDDE1007D13B2 /* ASVideoPlayerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; }; 8BDA5FC61CDBDDE1007D13B2 /* ASVideoPlayerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */; }; 8BDA5FC71CDBDF91007D13B2 /* ASVideoPlayerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; }; 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */; }; - 92074A611CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */; }; 92074A621CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */; }; 92074A631CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */; }; 92074A641CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */; }; - 92074A671CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */; }; 92074A681CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */; }; 92074A691CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */; }; 92074A6A1CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */; }; - 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; @@ -341,15 +250,11 @@ 92DD2FEA1BF4D49B0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; - 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C5586691BD549CB00B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; }; 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; }; 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C70F2031CDA4EFA007D6C76 /* ASTraitCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C70F2041CDA4EFA007D6C76 /* ASTraitCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */; }; 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */; }; 9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -361,72 +266,44 @@ 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; }; 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; - 9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; 9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; }; 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; }; 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; }; - 9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9CC606651D24DF9E006581A0 /* NSIndexSet+ASHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */; }; 9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9CFFC6BE1CCAC52B006A6476 /* ASEnvironment.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */; }; 9CFFC6C01CCAC73C006A6476 /* ASViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6BF1CCAC73C006A6476 /* ASViewController.mm */; }; 9CFFC6C21CCAC768006A6476 /* ASTableNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */; }; - 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; }; - A2763D791CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */; }; + 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */; }; A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */; }; - A32FEDD51C501B6A004F642A /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A373200F1C571B730011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */; }; - AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */; }; - AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */; }; - AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; }; + AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */; }; + AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */; }; AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; }; - AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */; }; - AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */; }; - AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */; }; + AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */; }; AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A501A1139C100143C57 /* ASCollectionView.mm */; }; - AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC47D9421B3B891B00AAEE9D /* ASCellNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC6456071B0A335000CF11B8 /* ASCellNode.mm */; }; - AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */ = {isa = PBXBuildFile; fileRef = AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC47D9441B3BB41900AAEE9D /* ASRelativeSize.mm */; }; AC6456091B0A335000CF11B8 /* ASCellNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC6456071B0A335000CF11B8 /* ASCellNode.mm */; }; - AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; }; AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; }; - ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - ACF6ED1A1B17843500DA7C62 /* ASBackgroundLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED1B1B17843500DA7C62 /* ASBackgroundLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */; }; - ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */; }; - ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; - ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */; }; - ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED0B1B17843500DA7C62 /* ASLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */; }; - ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED0E1B17843500DA7C62 /* ASLayoutSpec.mm */; }; - ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED111B17843500DA7C62 /* ASLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */; }; - ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */; }; - ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */; }; - ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */; }; - ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; }; - ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.mm */; }; - ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */; }; - ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */; }; - ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */; }; + ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */; }; ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */; }; - ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */; }; ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */; }; ACF6ED5C1B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */; }; ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */; }; @@ -435,17 +312,11 @@ ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */; }; ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */; }; ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */; }; - AEB7B01A1C5962EA00662EF4 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; }; AEB7B01B1C5962EA00662EF4 /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; - AEEC47E11C20C2DD00EC1693 /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; AEEC47E21C20C2DD00EC1693 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */; }; - B0F8805A1BEAEC7500D17647 /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B13CA0F71C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B13CA1001C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B30BF6521C5964B0004FCD53 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; B30BF6531C5964B0004FCD53 /* ASLayoutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */; }; B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */; }; B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -535,35 +406,29 @@ 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, ); }; }; - CC3B20831C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; - CC3B20891C3F7A5400798563 /* ASWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; }; CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; }; 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 */; }; CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */; }; CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */; }; - CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC4981BD1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */; }; 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, ); }; }; - D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; - DB55C2611C6408D6004EDCF5 /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; }; DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; }; - DB55C2661C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; }; DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; }; - DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */; }; DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */; }; - DBDB83941C6E879900D0098C /* ASPagerFlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; DBDB83961C6E879900D0098C /* ASPagerFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */; }; DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */; }; @@ -571,31 +436,166 @@ DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; DE4843DB1C93EAB100A1F33B /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; }; DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; }; - DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; + DE72E2AC1D4D90E000DDB258 /* ASLayoutSpec+Private.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 69F6058C1D3DA27E00C8CA38 /* ASLayoutSpec+Private.h */; }; 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 */; }; - DE8BEAC11C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; - DEC146B61C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; }; DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; }; DEC146B81C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; }; DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; }; - DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; E52405B31C8FEF03004DC8E7 /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; }; - E52405B51C8FEF16004DC8E7 /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; }; E55D86321CA8A14000A0C26F /* ASLayoutable.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutable.mm */; }; E55D86331CA8A14000A0C26F /* ASLayoutable.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutable.mm */; }; - E5711A2B1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; }; E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */; }; E5711A301C840C96009619D4 /* ASIndexedNodeContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */; }; + F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */; }; + F7CE6C131D2CDB3E00BE4C15 /* ASPagerFlowLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */; }; + F7CE6C141D2CDB3E00BE4C15 /* ASMapNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; }; + F7CE6C151D2CDB3E00BE4C15 /* ASVideoNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; }; + F7CE6C161D2CDB3E00BE4C15 /* ASCellNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; }; + F7CE6C171D2CDB3E00BE4C15 /* ASCollectionNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; }; + F7CE6C181D2CDB3E00BE4C15 /* ASCollectionNode+Beta.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; }; + F7CE6C191D2CDB3E00BE4C15 /* ASCollectionView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; }; + F7CE6C1A1D2CDB3E00BE4C15 /* ASCollectionViewProtocols.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */; }; + F7CE6C1B1D2CDB3E00BE4C15 /* ASCollectionViewLayoutFacilitatorProtocol.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; }; + F7CE6C1C1D2CDB3E00BE4C15 /* ASControlNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09D5195D050800B7D73C /* ASControlNode.h */; }; + F7CE6C1D1D2CDB3E00BE4C15 /* ASButtonNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; }; + F7CE6C1E1D2CDB3E00BE4C15 /* ASControlNode+Subclasses.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */; }; + F7CE6C1F1D2CDB3E00BE4C15 /* ASDisplayNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09D8195D050800B7D73C /* ASDisplayNode.h */; }; + F7CE6C201D2CDB3E00BE4C15 /* ASDisplayNode+Beta.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; }; + F7CE6C211D2CDB3E00BE4C15 /* ASDisplayNode+Subclasses.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */; }; + F7CE6C221D2CDB3E00BE4C15 /* ASDisplayNodeExtras.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */; }; + F7CE6C231D2CDB3E00BE4C15 /* ASEditableTextNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */; }; + F7CE6C241D2CDB3E00BE4C15 /* ASImageNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09DD195D050800B7D73C /* ASImageNode.h */; }; + F7CE6C251D2CDB3E00BE4C15 /* UIImage+ASConvenience.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; }; + F7CE6C261D2CDB3E00BE4C15 /* ASMultiplexImageNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */; }; + F7CE6C271D2CDB3E00BE4C15 /* ASNavigationController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */; }; + F7CE6C281D2CDB3E00BE4C15 /* ASNetworkImageNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */; }; + F7CE6C291D2CDB3E00BE4C15 /* ASPagerNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; }; + F7CE6C2A1D2CDB3E00BE4C15 /* ASScrollNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; }; + F7CE6C2B1D2CDB3E00BE4C15 /* ASTabBarController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */; }; + F7CE6C2C1D2CDB3E00BE4C15 /* ASTableNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; }; + F7CE6C2D1D2CDB3E00BE4C15 /* ASTableView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */; }; + F7CE6C2E1D2CDB3E00BE4C15 /* ASTableViewProtocols.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */; }; + F7CE6C2F1D2CDB3E00BE4C15 /* ASTextNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09DF195D050800B7D73C /* ASTextNode.h */; }; + F7CE6C301D2CDB3E00BE4C15 /* ASTextNode+Beta.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; }; + F7CE6C311D2CDB3E00BE4C15 /* ASViewController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; }; + F7CE6C321D2CDB3E00BE4C15 /* AsyncDisplayKit.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; }; + F7CE6C331D2CDB3E00BE4C15 /* AsyncDisplayKit+Debug.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; }; + F7CE6C341D2CDB3E00BE4C15 /* ASContextTransitioning.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; }; + F7CE6C351D2CDB3E00BE4C15 /* ASVisibilityProtocols.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */; }; + F7CE6C361D2CDB3E00BE4C15 /* _ASDisplayLayer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */; }; + F7CE6C371D2CDB3E00BE4C15 /* _ASDisplayView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09E4195D050800B7D73C /* _ASDisplayView.h */; }; + F7CE6C381D2CDB3E00BE4C15 /* ASAbstractLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; }; + F7CE6C391D2CDB3E00BE4C15 /* ASBasicImageDownloader.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */; }; + F7CE6C3A1D2CDB3E00BE4C15 /* ASBatchContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 299DA1A71A828D2900162D41 /* ASBatchContext.h */; }; + F7CE6C3B1D2CDB3E00BE4C15 /* ASCollectionViewFlowLayoutInspector.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; }; + F7CE6C3D1D2CDB3E00BE4C15 /* ASCollectionViewLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; }; + F7CE6C3E1D2CDB3E00BE4C15 /* ASDealloc2MainObject.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */; }; + F7CE6C3F1D2CDB3E00BE4C15 /* ASEnvironment.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 698548611CA9E025008A345F /* ASEnvironment.h */; }; + F7CE6C401D2CDB3E00BE4C15 /* ASFlowLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; }; + F7CE6C411D2CDB3E00BE4C15 /* ASHighlightOverlayLayer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; }; + F7CE6C421D2CDB3E00BE4C15 /* ASIndexPath.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; }; + F7CE6C431D2CDB3E00BE4C15 /* ASImageProtocols.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; }; + F7CE6C441D2CDB3E00BE4C15 /* ASImageContainerProtocolCategories.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; }; + F7CE6C451D2CDB3E00BE4C15 /* ASPINRemoteImageDownloader.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; }; + F7CE6C461D2CDB3E00BE4C15 /* ASLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4640521D1A3F83C40061C0BA /* ASLayoutController.h */; }; + F7CE6C471D2CDB3E00BE4C15 /* ASLayoutRangeType.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; }; + F7CE6C481D2CDB3E00BE4C15 /* ASMutableAttributedStringBuilder.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */; }; + F7CE6C491D2CDB3E00BE4C15 /* ASPhotosFrameworkImageRequest.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; }; + F7CE6C4A1D2CDB3E00BE4C15 /* ASRangeController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; }; + F7CE6C4B1D2CDB3E00BE4C15 /* ASRangeControllerUpdateRangeProtocol+Beta.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; }; + F7CE6C4C1D2CDB3E00BE4C15 /* ASRunLoopQueue.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; }; + F7CE6C4D1D2CDB3E00BE4C15 /* ASScrollDirection.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; }; + F7CE6C4E1D2CDB3E00BE4C15 /* ASThread.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; }; + F7CE6C4F1D2CDB3E00BE4C15 /* CGRect+ASConvenience.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; }; + F7CE6C501D2CDB3E00BE4C15 /* ASDataController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; }; + F7CE6C511D2CDB3E00BE4C15 /* ASChangeSetDataController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; }; + F7CE6C521D2CDB3E00BE4C15 /* ASIndexedNodeContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; }; + F7CE6C531D2CDB3E00BE4C15 /* NSMutableAttributedString+TextKitAdditions.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; }; + F7CE6C541D2CDB3E00BE4C15 /* _ASAsyncTransaction.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */; }; + F7CE6C551D2CDB3E00BE4C15 /* _ASAsyncTransactionContainer+Private.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; }; + F7CE6C561D2CDB3E00BE4C15 /* _ASAsyncTransactionContainer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09FB195D050800B7D73C /* _ASAsyncTransactionContainer.h */; }; + F7CE6C571D2CDB3E00BE4C15 /* _ASAsyncTransactionGroup.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */; }; + F7CE6C581D2CDB3E00BE4C15 /* UICollectionViewLayout+ASConvenience.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; }; + F7CE6C591D2CDB3E00BE4C15 /* UIView+ASConvenience.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */; }; + F7CE6C5A1D2CDB3E00BE4C15 /* ASTraitCollection.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */; }; + F7CE6C5B1D2CDB3E00BE4C15 /* ASAsciiArtBoxCreator.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; }; + F7CE6C5C1D2CDB3E00BE4C15 /* ASBackgroundLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */; }; + F7CE6C5D1D2CDB3E00BE4C15 /* ASCenterLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */; }; + F7CE6C5E1D2CDB3E00BE4C15 /* ASDimension.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; }; + F7CE6C5F1D2CDB3E00BE4C15 /* ASInsetLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */; }; + F7CE6C601D2CDB3E00BE4C15 /* ASLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED0B1B17843500DA7C62 /* ASLayout.h */; }; + F7CE6C611D2CDB3E00BE4C15 /* ASLayoutable.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED111B17843500DA7C62 /* ASLayoutable.h */; }; + F7CE6C621D2CDB3E00BE4C15 /* ASLayoutablePrivate.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; }; + F7CE6C631D2CDB3E00BE4C15 /* ASLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED0D1B17843500DA7C62 /* ASLayoutSpec.h */; }; + F7CE6C641D2CDB3E00BE4C15 /* ASOverlayLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED121B17843500DA7C62 /* ASOverlayLayoutSpec.h */; }; + F7CE6C651D2CDB3E00BE4C15 /* ASRatioLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */; }; + F7CE6C661D2CDB3E00BE4C15 /* ASRelativeLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; }; + F7CE6C671D2CDB3E00BE4C15 /* ASRelativeSize.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */; }; + F7CE6C681D2CDB3E00BE4C15 /* ASStackLayoutable.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; }; + F7CE6C691D2CDB3E00BE4C15 /* ASStackLayoutDefines.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */; }; + F7CE6C6A1D2CDB3E00BE4C15 /* ASStackLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */; }; + F7CE6C6B1D2CDB3E00BE4C15 /* ASStaticLayoutable.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; }; + F7CE6C6C1D2CDB3E00BE4C15 /* ASStaticLayoutSpec.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */; }; + F7CE6C6D1D2CDB3E00BE4C15 /* ASTextKitComponents.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */; }; + F7CE6C6E1D2CDB3E00BE4C15 /* ASTextNodeWordKerner.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; }; + F7CE6C6F1D2CDB3E00BE4C15 /* ASTextNodeTypes.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; }; + F7CE6C701D2CDB3E00BE4C15 /* ASTextKitAttributes.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; }; + F7CE6C711D2CDB3F00BE4C15 /* ASTextKitContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; }; + F7CE6C721D2CDB3F00BE4C15 /* ASTextKitEntityAttribute.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; }; + F7CE6C731D2CDB3F00BE4C15 /* ASTextKitRenderer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */; }; + F7CE6C741D2CDB3F00BE4C15 /* ASTextKitRenderer+Positioning.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */; }; + F7CE6C751D2CDB3F00BE4C15 /* ASTextKitRenderer+TextChecking.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */; }; + F7CE6C761D2CDB3F00BE4C15 /* ASTextKitShadower.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; }; + F7CE6C771D2CDB3F00BE4C15 /* ASTextKitTailTruncater.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; }; + F7CE6C781D2CDB3F00BE4C15 /* ASTextKitFontSizeAdjuster.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; }; + F7CE6C791D2CDB3F00BE4C15 /* ASTextKitTruncating.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; }; + F7CE6C7A1D2CDB3F00BE4C15 /* ASEqualityHashHelpers.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */; }; + F7CE6C7B1D2CDB3F00BE4C15 /* ASAssert.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A43195D058D00B7D73C /* ASAssert.h */; }; + F7CE6C7C1D2CDB3F00BE4C15 /* ASAvailability.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0516FA3A1A15563400B4EBED /* ASAvailability.h */; }; + F7CE6C7D1D2CDB3F00BE4C15 /* ASBaseDefines.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A44195D058D00B7D73C /* ASBaseDefines.h */; }; + F7CE6C7E1D2CDB3F00BE4C15 /* ASEqualityHelpers.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; }; + F7CE6C7F1D2CDB3F00BE4C15 /* ASLog.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; }; + F7CE6C801D2CDB5800BE4C15 /* ASVideoPlayerNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; }; + F7CE6C811D2CDB5800BE4C15 /* ASCollectionInternal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; }; + F7CE6C821D2CDB5800BE4C15 /* ASTableViewInternal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; }; + F7CE6C831D2CDB5800BE4C15 /* ASImageNode+tvOS.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */; }; + F7CE6C841D2CDB5800BE4C15 /* ASControlNode+tvOS.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */; }; + F7CE6C851D2CDB5800BE4C15 /* NSIndexSet+ASHelpers.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; }; + F7CE6C861D2CDB5800BE4C15 /* _ASDisplayViewAccessiblity.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; }; + F7CE6C871D2CDB5800BE4C15 /* ASDataController+Subclasses.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; }; + F7CE6C8B1D2CDB5800BE4C15 /* ASCollectionDataController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; }; + F7CE6C8C1D2CDB5800BE4C15 /* _ASCoreAnimationExtras.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; }; + F7CE6C8D1D2CDB5800BE4C15 /* _ASHierarchyChangeSet.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; }; + F7CE6C8F1D2CDB5800BE4C15 /* _ASScopeTimer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A07195D050800B7D73C /* _ASScopeTimer.h */; }; + F7CE6C901D2CDB5800BE4C15 /* _ASTransitionContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; }; + F7CE6C941D2CDB5800BE4C15 /* ASDisplayNodeInternal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */; }; + F7CE6C951D2CDB5800BE4C15 /* ASLayoutTransition.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; }; + F7CE6C961D2CDB5800BE4C15 /* ASEnvironmentInternal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; }; + F7CE6C971D2CDB5800BE4C15 /* ASImageNode+CGExtras.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */; }; + F7CE6C981D2CDB5800BE4C15 /* ASInternalHelpers.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; }; + F7CE6C991D2CDB5800BE4C15 /* ASLayoutSpecUtilities.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */; }; + F7CE6C9A1D2CDB5800BE4C15 /* ASMultidimensionalArrayUtils.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; + F7CE6C9B1D2CDB5800BE4C15 /* ASPendingStateController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; + F7CE6C9C1D2CDB5800BE4C15 /* ASSentinel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A10195D050800B7D73C /* ASSentinel.h */; }; + F7CE6C9D1D2CDB5800BE4C15 /* ASStackBaselinePositionedLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; + F7CE6C9E1D2CDB5800BE4C15 /* ASStackLayoutSpecUtilities.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */; }; + F7CE6C9F1D2CDB5800BE4C15 /* ASStackPositionedLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */; }; + F7CE6CA01D2CDB5800BE4C15 /* ASStackUnpositionedLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */; }; + F7CE6CA11D2CDB5800BE4C15 /* ASWeakSet.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; }; + F7CE6CA21D2CDB5800BE4C15 /* ASDefaultPlaybackButton.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; }; + F7CE6CA31D2CDB5800BE4C15 /* ASLayoutValidation.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 697C0DE11CF38F28001DE0D4 /* ASLayoutValidation.h */; }; + F7CE6CA41D2CDB5800BE4C15 /* ASLayoutManager.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; + F7CE6CB71D2CE2D000BE4C15 /* ASLayoutableExtensibility.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -629,6 +629,146 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + F7CE6C981D2CDB5800BE4C15 /* ASInternalHelpers.h in CopyFiles */, + F7CE6CB71D2CE2D000BE4C15 /* ASLayoutableExtensibility.h in CopyFiles */, + F7CE6C131D2CDB3E00BE4C15 /* ASPagerFlowLayout.h in CopyFiles */, + F7CE6C141D2CDB3E00BE4C15 /* ASMapNode.h in CopyFiles */, + F7CE6C151D2CDB3E00BE4C15 /* ASVideoNode.h in CopyFiles */, + F7CE6C161D2CDB3E00BE4C15 /* ASCellNode.h in CopyFiles */, + F7CE6C171D2CDB3E00BE4C15 /* ASCollectionNode.h in CopyFiles */, + F7CE6C181D2CDB3E00BE4C15 /* ASCollectionNode+Beta.h in CopyFiles */, + F7CE6C191D2CDB3E00BE4C15 /* ASCollectionView.h in CopyFiles */, + F7CE6C1A1D2CDB3E00BE4C15 /* ASCollectionViewProtocols.h in CopyFiles */, + F7CE6C1B1D2CDB3E00BE4C15 /* ASCollectionViewLayoutFacilitatorProtocol.h in CopyFiles */, + F7CE6C1C1D2CDB3E00BE4C15 /* ASControlNode.h in CopyFiles */, + F7CE6C1D1D2CDB3E00BE4C15 /* ASButtonNode.h in CopyFiles */, + F7CE6C1E1D2CDB3E00BE4C15 /* ASControlNode+Subclasses.h in CopyFiles */, + F7CE6C1F1D2CDB3E00BE4C15 /* ASDisplayNode.h in CopyFiles */, + F7CE6C201D2CDB3E00BE4C15 /* ASDisplayNode+Beta.h in CopyFiles */, + F7CE6C211D2CDB3E00BE4C15 /* ASDisplayNode+Subclasses.h in CopyFiles */, + F7CE6C221D2CDB3E00BE4C15 /* ASDisplayNodeExtras.h in CopyFiles */, + F7CE6C231D2CDB3E00BE4C15 /* ASEditableTextNode.h in CopyFiles */, + F7CE6C241D2CDB3E00BE4C15 /* ASImageNode.h in CopyFiles */, + F7CE6C251D2CDB3E00BE4C15 /* UIImage+ASConvenience.h in CopyFiles */, + F7CE6C261D2CDB3E00BE4C15 /* ASMultiplexImageNode.h in CopyFiles */, + F7CE6C271D2CDB3E00BE4C15 /* ASNavigationController.h in CopyFiles */, + F7CE6C281D2CDB3E00BE4C15 /* ASNetworkImageNode.h in CopyFiles */, + F7CE6C291D2CDB3E00BE4C15 /* ASPagerNode.h in CopyFiles */, + F7CE6C2A1D2CDB3E00BE4C15 /* ASScrollNode.h in CopyFiles */, + F7CE6C2B1D2CDB3E00BE4C15 /* ASTabBarController.h in CopyFiles */, + F7CE6C2C1D2CDB3E00BE4C15 /* ASTableNode.h in CopyFiles */, + F7CE6C2D1D2CDB3E00BE4C15 /* ASTableView.h in CopyFiles */, + F7CE6C2E1D2CDB3E00BE4C15 /* ASTableViewProtocols.h in CopyFiles */, + F7CE6C2F1D2CDB3E00BE4C15 /* ASTextNode.h in CopyFiles */, + F7CE6C301D2CDB3E00BE4C15 /* ASTextNode+Beta.h in CopyFiles */, + F7CE6C311D2CDB3E00BE4C15 /* ASViewController.h in CopyFiles */, + F7CE6C321D2CDB3E00BE4C15 /* AsyncDisplayKit.h in CopyFiles */, + F7CE6C331D2CDB3E00BE4C15 /* AsyncDisplayKit+Debug.h in CopyFiles */, + F7CE6C341D2CDB3E00BE4C15 /* ASContextTransitioning.h in CopyFiles */, + F7CE6C351D2CDB3E00BE4C15 /* ASVisibilityProtocols.h in CopyFiles */, + F7CE6C361D2CDB3E00BE4C15 /* _ASDisplayLayer.h in CopyFiles */, + F7CE6C371D2CDB3E00BE4C15 /* _ASDisplayView.h in CopyFiles */, + F7CE6C381D2CDB3E00BE4C15 /* ASAbstractLayoutController.h in CopyFiles */, + F7CE6C391D2CDB3E00BE4C15 /* ASBasicImageDownloader.h in CopyFiles */, + F7CE6C3A1D2CDB3E00BE4C15 /* ASBatchContext.h in CopyFiles */, + F7CE6C3B1D2CDB3E00BE4C15 /* ASCollectionViewFlowLayoutInspector.h in CopyFiles */, + F7CE6C3D1D2CDB3E00BE4C15 /* ASCollectionViewLayoutController.h in CopyFiles */, + F7CE6C3E1D2CDB3E00BE4C15 /* ASDealloc2MainObject.h in CopyFiles */, + F7CE6C3F1D2CDB3E00BE4C15 /* ASEnvironment.h in CopyFiles */, + F7CE6C401D2CDB3E00BE4C15 /* ASFlowLayoutController.h in CopyFiles */, + F7CE6C411D2CDB3E00BE4C15 /* ASHighlightOverlayLayer.h in CopyFiles */, + F7CE6C421D2CDB3E00BE4C15 /* ASIndexPath.h in CopyFiles */, + F7CE6C431D2CDB3E00BE4C15 /* ASImageProtocols.h in CopyFiles */, + F7CE6C441D2CDB3E00BE4C15 /* ASImageContainerProtocolCategories.h in CopyFiles */, + F7CE6C461D2CDB3E00BE4C15 /* ASLayoutController.h in CopyFiles */, + F7CE6C471D2CDB3E00BE4C15 /* ASLayoutRangeType.h in CopyFiles */, + F7CE6C481D2CDB3E00BE4C15 /* ASMutableAttributedStringBuilder.h in CopyFiles */, + F7CE6C491D2CDB3E00BE4C15 /* ASPhotosFrameworkImageRequest.h in CopyFiles */, + F7CE6C4A1D2CDB3E00BE4C15 /* ASRangeController.h in CopyFiles */, + F7CE6C4B1D2CDB3E00BE4C15 /* ASRangeControllerUpdateRangeProtocol+Beta.h in CopyFiles */, + F7CE6C4C1D2CDB3E00BE4C15 /* ASRunLoopQueue.h in CopyFiles */, + F7CE6C4D1D2CDB3E00BE4C15 /* ASScrollDirection.h in CopyFiles */, + F7CE6C4E1D2CDB3E00BE4C15 /* ASThread.h in CopyFiles */, + F7CE6C4F1D2CDB3E00BE4C15 /* CGRect+ASConvenience.h in CopyFiles */, + F7CE6C501D2CDB3E00BE4C15 /* ASDataController.h in CopyFiles */, + F7CE6C511D2CDB3E00BE4C15 /* ASChangeSetDataController.h in CopyFiles */, + F7CE6C521D2CDB3E00BE4C15 /* ASIndexedNodeContext.h in CopyFiles */, + F7CE6C531D2CDB3E00BE4C15 /* NSMutableAttributedString+TextKitAdditions.h in CopyFiles */, + F7CE6C541D2CDB3E00BE4C15 /* _ASAsyncTransaction.h in CopyFiles */, + F7CE6C551D2CDB3E00BE4C15 /* _ASAsyncTransactionContainer+Private.h in CopyFiles */, + F7CE6C561D2CDB3E00BE4C15 /* _ASAsyncTransactionContainer.h in CopyFiles */, + F7CE6C571D2CDB3E00BE4C15 /* _ASAsyncTransactionGroup.h in CopyFiles */, + F7CE6C581D2CDB3E00BE4C15 /* UICollectionViewLayout+ASConvenience.h in CopyFiles */, + F7CE6C5B1D2CDB3E00BE4C15 /* ASAsciiArtBoxCreator.h in CopyFiles */, + F7CE6C5C1D2CDB3E00BE4C15 /* ASBackgroundLayoutSpec.h in CopyFiles */, + F7CE6C5D1D2CDB3E00BE4C15 /* ASCenterLayoutSpec.h in CopyFiles */, + F7CE6C5E1D2CDB3E00BE4C15 /* ASDimension.h in CopyFiles */, + F7CE6C5F1D2CDB3E00BE4C15 /* ASInsetLayoutSpec.h in CopyFiles */, + F7CE6C601D2CDB3E00BE4C15 /* ASLayout.h in CopyFiles */, + F7CE6C611D2CDB3E00BE4C15 /* ASLayoutable.h in CopyFiles */, + F7CE6C621D2CDB3E00BE4C15 /* ASLayoutablePrivate.h in CopyFiles */, + F7CE6C631D2CDB3E00BE4C15 /* ASLayoutSpec.h in CopyFiles */, + DE72E2AC1D4D90E000DDB258 /* ASLayoutSpec+Private.h in CopyFiles */, + F7CE6C641D2CDB3E00BE4C15 /* ASOverlayLayoutSpec.h in CopyFiles */, + F7CE6C651D2CDB3E00BE4C15 /* ASRatioLayoutSpec.h in CopyFiles */, + F7CE6C661D2CDB3E00BE4C15 /* ASRelativeLayoutSpec.h in CopyFiles */, + F7CE6C671D2CDB3E00BE4C15 /* ASRelativeSize.h in CopyFiles */, + F7CE6C681D2CDB3E00BE4C15 /* ASStackLayoutable.h in CopyFiles */, + F7CE6C691D2CDB3E00BE4C15 /* ASStackLayoutDefines.h in CopyFiles */, + F7CE6C6A1D2CDB3E00BE4C15 /* ASStackLayoutSpec.h in CopyFiles */, + F7CE6C6B1D2CDB3E00BE4C15 /* ASStaticLayoutable.h in CopyFiles */, + F7CE6C6C1D2CDB3E00BE4C15 /* ASStaticLayoutSpec.h in CopyFiles */, + F7CE6C6D1D2CDB3E00BE4C15 /* ASTextKitComponents.h in CopyFiles */, + F7CE6C6E1D2CDB3E00BE4C15 /* ASTextNodeWordKerner.h in CopyFiles */, + F7CE6C6F1D2CDB3E00BE4C15 /* ASTextNodeTypes.h in CopyFiles */, + F7CE6C701D2CDB3E00BE4C15 /* ASTextKitAttributes.h in CopyFiles */, + F7CE6C711D2CDB3F00BE4C15 /* ASTextKitContext.h in CopyFiles */, + F7CE6C721D2CDB3F00BE4C15 /* ASTextKitEntityAttribute.h in CopyFiles */, + F7CE6C731D2CDB3F00BE4C15 /* ASTextKitRenderer.h in CopyFiles */, + F7CE6C741D2CDB3F00BE4C15 /* ASTextKitRenderer+Positioning.h in CopyFiles */, + F7CE6C751D2CDB3F00BE4C15 /* ASTextKitRenderer+TextChecking.h in CopyFiles */, + F7CE6C761D2CDB3F00BE4C15 /* ASTextKitShadower.h in CopyFiles */, + F7CE6C771D2CDB3F00BE4C15 /* ASTextKitTailTruncater.h in CopyFiles */, + F7CE6C781D2CDB3F00BE4C15 /* ASTextKitFontSizeAdjuster.h in CopyFiles */, + F7CE6C791D2CDB3F00BE4C15 /* ASTextKitTruncating.h in CopyFiles */, + F7CE6C7A1D2CDB3F00BE4C15 /* ASEqualityHashHelpers.h in CopyFiles */, + F7CE6C7B1D2CDB3F00BE4C15 /* ASAssert.h in CopyFiles */, + F7CE6C7C1D2CDB3F00BE4C15 /* ASAvailability.h in CopyFiles */, + F7CE6C7D1D2CDB3F00BE4C15 /* ASBaseDefines.h in CopyFiles */, + F7CE6C7E1D2CDB3F00BE4C15 /* ASEqualityHelpers.h in CopyFiles */, + F7CE6C7F1D2CDB3F00BE4C15 /* ASLog.h in CopyFiles */, + F7CE6C801D2CDB5800BE4C15 /* ASVideoPlayerNode.h in CopyFiles */, + F7CE6C811D2CDB5800BE4C15 /* ASCollectionInternal.h in CopyFiles */, + F7CE6C821D2CDB5800BE4C15 /* ASTableViewInternal.h in CopyFiles */, + F7CE6C831D2CDB5800BE4C15 /* ASImageNode+tvOS.h in CopyFiles */, + F7CE6C841D2CDB5800BE4C15 /* ASControlNode+tvOS.h in CopyFiles */, + F7CE6C851D2CDB5800BE4C15 /* NSIndexSet+ASHelpers.h in CopyFiles */, + F7CE6C861D2CDB5800BE4C15 /* _ASDisplayViewAccessiblity.h in CopyFiles */, + F7CE6C871D2CDB5800BE4C15 /* ASDataController+Subclasses.h in CopyFiles */, + F7CE6C451D2CDB3E00BE4C15 /* ASPINRemoteImageDownloader.h in CopyFiles */, + F7CE6C8B1D2CDB5800BE4C15 /* ASCollectionDataController.h in CopyFiles */, + F7CE6C591D2CDB3E00BE4C15 /* UIView+ASConvenience.h in CopyFiles */, + F7CE6C5A1D2CDB3E00BE4C15 /* ASTraitCollection.h in CopyFiles */, + F7CE6C8C1D2CDB5800BE4C15 /* _ASCoreAnimationExtras.h in CopyFiles */, + F7CE6C8D1D2CDB5800BE4C15 /* _ASHierarchyChangeSet.h in CopyFiles */, + F7CE6C8F1D2CDB5800BE4C15 /* _ASScopeTimer.h in CopyFiles */, + F7CE6C901D2CDB5800BE4C15 /* _ASTransitionContext.h in CopyFiles */, + F7CE6C941D2CDB5800BE4C15 /* ASDisplayNodeInternal.h in CopyFiles */, + F7CE6C951D2CDB5800BE4C15 /* ASLayoutTransition.h in CopyFiles */, + F7CE6C961D2CDB5800BE4C15 /* ASEnvironmentInternal.h in CopyFiles */, + F7CE6C971D2CDB5800BE4C15 /* ASImageNode+CGExtras.h in CopyFiles */, + F7CE6C991D2CDB5800BE4C15 /* ASLayoutSpecUtilities.h in CopyFiles */, + F7CE6C9A1D2CDB5800BE4C15 /* ASMultidimensionalArrayUtils.h in CopyFiles */, + F7CE6C9B1D2CDB5800BE4C15 /* ASPendingStateController.h in CopyFiles */, + F7CE6C9C1D2CDB5800BE4C15 /* ASSentinel.h in CopyFiles */, + F7CE6C9D1D2CDB5800BE4C15 /* ASStackBaselinePositionedLayout.h in CopyFiles */, + F7CE6C9E1D2CDB5800BE4C15 /* ASStackLayoutSpecUtilities.h in CopyFiles */, + F7CE6C9F1D2CDB5800BE4C15 /* ASStackPositionedLayout.h in CopyFiles */, + F7CE6CA01D2CDB5800BE4C15 /* ASStackUnpositionedLayout.h in CopyFiles */, + F7CE6CA11D2CDB5800BE4C15 /* ASWeakSet.h in CopyFiles */, + F7CE6CA21D2CDB5800BE4C15 /* ASDefaultPlaybackButton.h in CopyFiles */, + F7CE6CA31D2CDB5800BE4C15 /* ASLayoutValidation.h in CopyFiles */, + F7CE6CA41D2CDB5800BE4C15 /* ASLayoutManager.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -835,8 +975,13 @@ 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRelativeLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm; sourceTree = ""; }; 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRelativeLayoutSpec.h; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; + 83A7D9591D44542100BF333E /* ASWeakMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakMap.m; sourceTree = ""; }; + 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakMapTests.m; sourceTree = ""; }; 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDefaultPlaybackButton.h; sourceTree = ""; }; 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDefaultPlaybackButton.m; sourceTree = ""; }; 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVideoPlayerNode.h; sourceTree = ""; }; @@ -861,16 +1006,16 @@ 9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironment.mm; sourceTree = ""; }; 9CFFC6BF1CCAC73C006A6476 /* ASViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASViewController.mm; sourceTree = ""; }; 9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTableNode.mm; sourceTree = ""; }; - 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionViewTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASPINRemoteImageDownloader.h; path = Details/ASPINRemoteImageDownloader.h; sourceTree = ""; }; A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASPINRemoteImageDownloader.m; path = Details/ASPINRemoteImageDownloader.m; sourceTree = ""; }; A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitFontSizeAdjuster.h; path = TextKit/ASTextKitFontSizeAdjuster.h; sourceTree = ""; }; A373200E1C571B050011FC94 /* ASTextNode+Beta.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASTextNode+Beta.h"; sourceTree = ""; }; AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASStaticLayoutSpecSnapshotTests.m; sourceTree = ""; }; AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASChangeSetDataController.h; sourceTree = ""; }; - AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASChangeSetDataController.m; sourceTree = ""; }; + AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASChangeSetDataController.mm; sourceTree = ""; }; AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASHierarchyChangeSet.h; sourceTree = ""; }; - AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASHierarchyChangeSet.m; sourceTree = ""; }; + AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASHierarchyChangeSet.mm; sourceTree = ""; }; AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = ""; }; AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionView.h; sourceTree = ""; }; AC3C4A501A1139C100143C57 /* ASCollectionView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionView.mm; sourceTree = ""; }; @@ -902,7 +1047,7 @@ ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStaticLayoutSpec.h; path = AsyncDisplayKit/Layout/ASStaticLayoutSpec.h; sourceTree = ""; }; ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASStaticLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm; sourceTree = ""; }; ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASInternalHelpers.h; sourceTree = ""; }; - ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASInternalHelpers.mm; sourceTree = ""; }; + ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASInternalHelpers.m; sourceTree = ""; }; ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecUtilities.h; sourceTree = ""; }; ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutSpecUtilities.h; sourceTree = ""; }; ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackPositionedLayout.h; sourceTree = ""; }; @@ -937,6 +1082,8 @@ CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSetTests.m; sourceTree = ""; }; CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBridgedPropertiesTests.mm; sourceTree = ""; }; CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewThrashTests.m; sourceTree = ""; }; + CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexSet+ASHelpers.h"; sourceTree = ""; }; + CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexSet+ASHelpers.m"; sourceTree = ""; }; 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 = ""; }; @@ -965,6 +1112,7 @@ E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexedNodeContext.h; sourceTree = ""; }; E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASIndexedNodeContext.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeExtrasTests.m; sourceTree = ""; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1116,6 +1264,8 @@ 058D09DD195D050800B7D73C /* ASImageNode.h */, 058D09DE195D050800B7D73C /* ASImageNode.mm */, 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */, + 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */, + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */, 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */, 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */, 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */, @@ -1170,6 +1320,7 @@ 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = { isa = PBXGroup; children = ( + 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */, DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */, @@ -1191,7 +1342,7 @@ 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */, CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */, 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */, - 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */, + 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */, 2911485B1A77147A005D0878 /* ASControlNodeTests.m */, ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */, 058D0A2D195D057000B7D73C /* ASDisplayLayerTests.m */, @@ -1210,6 +1361,7 @@ 058D0A36195D057000B7D73C /* ASTextNodeTests.m */, 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */, AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */, + F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */, 058D09C6195D04C000B7D73C /* Supporting Files */, 052EE06A1A15A0D8002C6279 /* TestResources */, 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */, @@ -1229,6 +1381,8 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( + CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */, + CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */, 9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */, 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */, 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, @@ -1246,7 +1400,6 @@ 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */, 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */, - 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */, 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */, 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */, 698548611CA9E025008A345F /* ASEnvironment.h */, @@ -1315,7 +1468,7 @@ 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */, 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */, AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */, - AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */, + AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */, 058D0A05195D050800B7D73C /* _ASPendingState.h */, 058D0A06195D050800B7D73C /* _ASPendingState.mm */, 058D0A07195D050800B7D73C /* _ASScopeTimer.h */, @@ -1324,6 +1477,7 @@ 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */, 044285051BAA63FE00D16268 /* ASBatchFetching.h */, 044285061BAA63FE00D16268 /* ASBatchFetching.m */, + 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */, AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */, AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */, 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */, @@ -1340,7 +1494,7 @@ 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */, ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */, - ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.mm */, + ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */, ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */, 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */, 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */, @@ -1355,6 +1509,8 @@ ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */, ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */, ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */, + 83A7D9581D44542100BF333E /* ASWeakMap.h */, + 83A7D9591D44542100BF333E /* ASWeakMap.m */, CC3B20871C3F7A5400798563 /* ASWeakSet.h */, CC3B20881C3F7A5400798563 /* ASWeakSet.m */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, @@ -1424,7 +1580,7 @@ 464052191A3F83C40061C0BA /* ASDataController.h */, 4640521A1A3F83C40061C0BA /* ASDataController.mm */, AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */, - AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */, + AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */, E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */, E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */, ); @@ -1515,164 +1671,6 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - 058D0A46195D05C300B7D73C /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 698C8B611CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */, - 698548631CA9E025008A345F /* ASEnvironment.h in Headers */, - E5711A2B1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */, - 257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */, - A373200F1C571B730011FC94 /* ASTextNode+Beta.h in Headers */, - 69F10C861C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */, - 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */, - AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, - 058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */, - 058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */, - 058D0A6A195D05EC00B7D73C /* _ASAsyncTransactionContainer+Private.h in Headers */, - A32FEDD51C501B6A004F642A /* ASTextKitFontSizeAdjuster.h in Headers */, - 058D0A6B195D05EC00B7D73C /* _ASAsyncTransactionContainer.h in Headers */, - 058D0A6D195D05EC00B7D73C /* _ASAsyncTransactionGroup.h in Headers */, - 058D0A72195D05F800B7D73C /* _ASCoreAnimationExtras.h in Headers */, - 7A06A73B1C35F08800FE8DAA /* ASRelativeLayoutSpec.h in Headers */, - 68B8A4DC1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h in Headers */, - A2763D791CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */, - 058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */, - 058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */, - B13CA0F71C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */, - 058D0A74195D05F800B7D73C /* _ASPendingState.h in Headers */, - 9C5586691BD549CB00B50E3A /* ASAsciiArtBoxCreator.h in Headers */, - 058D0A76195D05F900B7D73C /* _ASScopeTimer.h in Headers */, - 68EE0DBD1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, - 257754B31BEE44CD00737CA5 /* ASTextKitTailTruncater.h in Headers */, - 205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */, - 058D0A82195D060300B7D73C /* ASAssert.h in Headers */, - 0516FA3C1A15563400B4EBED /* ASAvailability.h in Headers */, - AEB7B01A1C5962EA00662EF4 /* ASDefaultPlayButton.h in Headers */, - 68FC85E21CE29B7E00EDD713 /* ASTabBarController.h in Headers */, - ACF6ED1A1B17843500DA7C62 /* ASBackgroundLayoutSpec.h in Headers */, - 058D0A83195D060300B7D73C /* ASBaseDefines.h in Headers */, - 054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */, - 2967F9E21AB0A5190072E4AB /* ASBasicImageDownloaderInternal.h in Headers */, - 25E327561C16819500A2170C /* ASPagerNode.h in Headers */, - 299DA1A91A828D2900162D41 /* ASBatchContext.h in Headers */, - 251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */, - 044285071BAA63FE00D16268 /* ASBatchFetching.h in Headers */, - 055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */, - ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */, - 18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, - 257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */, - AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */, - AEEC47E11C20C2DD00EC1693 /* ASVideoNode.h in Headers */, - DE8BEAC11C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, - 68FC85DE1CE29AB700EDD713 /* ASNavigationController.h in Headers */, - 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */, - AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */, - 058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */, - 058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */, - 257754A51BEE44CD00737CA5 /* ASTextKitRenderer.h in Headers */, - 464052201A3F83C40061C0BA /* ASDataController.h in Headers */, - 05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */, - ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */, - 058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */, - DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */, - 68B8A4E11CBDB958007E4543 /* ASWeakProxy.h in Headers */, - DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */, - 058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */, - 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */, - AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, - 058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */, - 92074A671CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */, - 697C0DE31CF38F28001DE0D4 /* ASLayoutValidation.h in Headers */, - 68355B3B1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h in Headers */, - 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */, - 767E7F8D1C9019130066C000 /* AsyncDisplayKit+Debug.h in Headers */, - 257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */, - 058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */, - 0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */, - DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, - 1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */, - 68FC85E91CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */, - 257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */, - DB55C2611C6408D6004EDCF5 /* _ASTransitionContext.h in Headers */, - 464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */, - 9C70F2031CDA4EFA007D6C76 /* ASTraitCollection.h in Headers */, - 257754AF1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h in Headers */, - 058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */, - 058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */, - DBDB83941C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, - 058D0A4F195D05CB00B7D73C /* ASImageNode.h in Headers */, - 05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */, - 8BDA5FC51CDBDDE1007D13B2 /* ASVideoPlayerNode.h in Headers */, - 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */, - ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */, - ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */, - DB55C2661C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, - 68355B3D1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h in Headers */, - ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */, - 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */, - ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */, - 9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */, - B0F8805A1BEAEC7500D17647 /* ASTableNode.h in Headers */, - 464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */, - 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */, - 257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */, - ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */, - ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */, - AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */, - 0516FA3D1A15563400B4EBED /* ASLog.h in Headers */, - 257754AA1BEE44CD00737CA5 /* ASTextKitEntityAttribute.h in Headers */, - B13CA1001C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, - 0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, - 0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */, - 8B0768B31CE752EC002E1453 /* ASDefaultPlaybackButton.h in Headers */, - 058D0A59195D05DC00B7D73C /* ASMutableAttributedStringBuilder.h in Headers */, - 055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */, - ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */, - 055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */, - E52405B51C8FEF16004DC8E7 /* ASLayoutTransition.h in Headers */, - ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */, - AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */, - 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */, - D785F6621A74327E00291744 /* ASScrollNode.h in Headers */, - 058D0A7F195D05F900B7D73C /* ASSentinel.h in Headers */, - 9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, - 257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */, - 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */, - 69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */, - AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */, - CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, - ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */, - ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */, - 257754B51BEE44CD00737CA5 /* ASTextKitTruncating.h in Headers */, - ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */, - 257754A71BEE44CD00737CA5 /* ASTextKitAttributes.h in Headers */, - CC3B20891C3F7A5400798563 /* ASWeakSet.h in Headers */, - ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */, - 9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */, - ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */, - 055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */, - 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */, - 257754C11BEE458E00737CA5 /* ASTextKitComponents.h in Headers */, - B30BF6521C5964B0004FCD53 /* ASLayoutManager.h in Headers */, - 0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */, - 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */, - CC3B20831C3F76D600798563 /* ASPendingStateController.h in Headers */, - 69CB62AB1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, - 058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */, - 058D0A81195D05F900B7D73C /* ASThread.h in Headers */, - ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */, - 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */, - 257754AD1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h in Headers */, - DEC146B61C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, - 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */, - 058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */, - 205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */, - 058D0A6F195D05EC00B7D73C /* UIView+ASConvenience.h in Headers */, - 92074A611CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; B35061D71B010EDF0018CF92 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -1741,6 +1739,7 @@ A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */, DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */, 9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */, + 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */, 254C6B801BF94DF4003EC431 /* ASEqualityHashHelpers.h in Headers */, B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */, @@ -1792,6 +1791,8 @@ 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, + CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */, + 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */, B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, 254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, @@ -1859,7 +1860,6 @@ 058D09A8195D04C000B7D73C /* Sources */, 058D09A9195D04C000B7D73C /* Frameworks */, 058D09AA195D04C000B7D73C /* CopyFiles */, - 058D0A46195D05C300B7D73C /* Headers */, ); buildRules = ( ); @@ -1874,12 +1874,12 @@ isa = PBXNativeTarget; buildConfigurationList = 058D09D2195D04C000B7D73C /* Build configuration list for PBXNativeTarget "AsyncDisplayKitTests" */; buildPhases = ( - 2E61B6A0DB0F436A9DDBE86F /* 📦 Check Pods Manifest.lock */, + 2E61B6A0DB0F436A9DDBE86F /* [CP] Check Pods Manifest.lock */, 058D09B8195D04C000B7D73C /* Sources */, 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, - 3B9D88CDF51B429C8409E4B6 /* 📦 Copy Pods Resources */, - B130AB1AC0A1E5162E211C19 /* 📦 Embed Pods Frameworks */, + 3B9D88CDF51B429C8409E4B6 /* [CP] Copy Pods Resources */, + B130AB1AC0A1E5162E211C19 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1983,14 +1983,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 2E61B6A0DB0F436A9DDBE86F /* 📦 Check Pods Manifest.lock */ = { + 2E61B6A0DB0F436A9DDBE86F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1998,14 +1998,14 @@ 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; }; - 3B9D88CDF51B429C8409E4B6 /* 📦 Copy Pods Resources */ = { + 3B9D88CDF51B429C8409E4B6 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -2013,14 +2013,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - B130AB1AC0A1E5162E211C19 /* 📦 Embed Pods Frameworks */ = { + B130AB1AC0A1E5162E211C19 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -2056,7 +2056,7 @@ 257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */, 68B8A4E31CBDB958007E4543 /* ASWeakProxy.m in Sources */, 69E1006F1CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */, - AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */, + AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */, 257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */, 058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */, 68355B3C1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, @@ -2083,6 +2083,7 @@ B30BF6531C5964B0004FCD53 /* ASLayoutManager.m in Sources */, 05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */, ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */, + 8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, 058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */, 058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */, 764D83D61C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m in Sources */, @@ -2101,9 +2102,10 @@ 058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */, 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */, ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */, - ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.mm in Sources */, + ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.m in Sources */, 68FC85DF1CE29AB700EDD713 /* ASNavigationController.m in Sources */, ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */, + CC4981BD1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m in Sources */, DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */, 92074A631CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */, 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */, @@ -2140,12 +2142,13 @@ 697C0DE51CF38F28001DE0D4 /* ASLayoutValidation.mm in Sources */, ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */, ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, + 83A7D95A1D44542100BF333E /* ASWeakMap.m in Sources */, 257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */, 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */, 9C70F2041CDA4EFA007D6C76 /* ASTraitCollection.m in Sources */, 92074A691CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */, ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */, - AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, + AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */, 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */, 9CFFC6C01CCAC73C006A6476 /* ASViewController.mm in Sources */, 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */, @@ -2169,9 +2172,10 @@ 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */, 296A0A351A951ABF005ACEAA /* ASBatchFetchingTests.m in Sources */, ACF6ED5C1B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm in Sources */, - 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */, + 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.mm in Sources */, 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */, CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */, + F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */, ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, 058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */, 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */, @@ -2179,6 +2183,7 @@ 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */, CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */, 058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */, + 83A7D95E1D446A6E00BF333E /* ASWeakMapTests.m in Sources */, 056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */, AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */, ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */, @@ -2219,7 +2224,7 @@ 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */, B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.mm in Sources */, B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */, - AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */, + AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */, B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */, B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */, 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */, @@ -2248,6 +2253,7 @@ B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, B350621A1B010EFD0018CF92 /* ASDealloc2MainObject.m in Sources */, 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */, @@ -2269,6 +2275,7 @@ CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */, B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */, B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */, + 9CC606651D24DF9E006581A0 /* NSIndexSet+ASHelpers.m in Sources */, 92074A641CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */, B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, @@ -2276,7 +2283,7 @@ 254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */, 430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */, 34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */, - 34EFC75E1B701BF000AD841F /* ASInternalHelpers.mm in Sources */, + 34EFC75E1B701BF000AD841F /* ASInternalHelpers.m in Sources */, 34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */, DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */, @@ -2305,10 +2312,11 @@ 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */, 697C0DE61CF38F28001DE0D4 /* ASLayoutValidation.mm in Sources */, 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.m in Sources */, + 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */, 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */, - AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, + AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */, 34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */, 92074A6A1CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */, DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */, diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme index ae3c3d3a9d..193b302afe 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme @@ -1,6 +1,6 @@ 0) && newTitle != self.titleNode.attributedString) { + if ((_titleNode != nil || newTitle.length > 0) && [self.titleNode.attributedString isEqualToAttributedString:newTitle] == NO) { _titleNode.attributedString = newTitle; self.accessibilityLabel = _titleNode.accessibilityLabel; [self setNeedsLayout]; @@ -192,7 +193,7 @@ - (void)updateBackgroundImage { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); UIImage *newImage; if (self.enabled == NO && _disabledBackgroundImage) { @@ -215,13 +216,13 @@ - (CGFloat)contentSpacing { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _contentSpacing; } - (void)setContentSpacing:(CGFloat)contentSpacing { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (contentSpacing == _contentSpacing) return; @@ -231,13 +232,13 @@ - (BOOL)laysOutHorizontally { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _laysOutHorizontally; } - (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (laysOutHorizontally == _laysOutHorizontally) return; @@ -247,37 +248,37 @@ - (ASVerticalAlignment)contentVerticalAlignment { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _contentVerticalAlignment; } - (void)setContentVerticalAlignment:(ASVerticalAlignment)contentVerticalAlignment { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _contentVerticalAlignment = contentVerticalAlignment; } - (ASHorizontalAlignment)contentHorizontalAlignment { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _contentHorizontalAlignment; } - (void)setContentHorizontalAlignment:(ASHorizontalAlignment)contentHorizontalAlignment { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _contentHorizontalAlignment = contentHorizontalAlignment; } - (UIEdgeInsets)contentEdgeInsets { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _contentEdgeInsets; } - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _contentEdgeInsets = contentEdgeInsets; } @@ -298,7 +299,7 @@ - (NSAttributedString *)attributedTitleForState:(ASControlState)state { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); switch (state) { case ASControlStateNormal: return _normalAttributedTitle; @@ -322,7 +323,7 @@ - (void)setAttributedTitle:(NSAttributedString *)title forState:(ASControlState)state { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); switch (state) { case ASControlStateNormal: _normalAttributedTitle = [title copy]; @@ -353,7 +354,7 @@ - (UIImage *)imageForState:(ASControlState)state { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); switch (state) { case ASControlStateNormal: return _normalImage; @@ -377,7 +378,7 @@ - (void)setImage:(UIImage *)image forState:(ASControlState)state { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); switch (state) { case ASControlStateNormal: _normalImage = image; @@ -407,7 +408,7 @@ - (UIImage *)backgroundImageForState:(ASControlState)state { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); switch (state) { case ASControlStateNormal: return _normalBackgroundImage; @@ -431,7 +432,7 @@ - (void)setBackgroundImage:(UIImage *)image forState:(ASControlState)state { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); switch (state) { case ASControlStateNormal: _normalBackgroundImage = image; @@ -465,7 +466,7 @@ ASLayoutSpec *spec; ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); stack.direction = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; stack.spacing = _contentSpacing; stack.horizontalAlignment = _contentHorizontalAlignment; @@ -491,9 +492,13 @@ spec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentEdgeInsets child:spec]; } + if (CGSizeEqualToSize(self.preferredFrameSize, CGSizeZero) == NO) { + stack.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(self.preferredFrameSize); + spec = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[stack]]; + } + if (_backgroundImageNode.image) { - spec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:spec - background:_backgroundImageNode]; + spec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:spec background:_backgroundImageNode]; } return spec; diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h index c497fc07d5..58c099f2ab 100644 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -12,7 +12,7 @@ #import "ASCellNode.h" -@protocol ASCellNodeLayoutDelegate +@protocol ASCellNodeInteractionDelegate /** * Notifies the delegate that the specified cell node has done a relayout. @@ -27,18 +27,26 @@ */ - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged; +/* + * Methods to be called whenever the selection or highlight state changes + * on ASCellNode. UIKit internally stores these values to update reusable cells. + */ + +- (void)nodeSelectedStateDidChange:(ASCellNode *)node; +- (void)nodeHighlightedStateDidChange:(ASCellNode *)node; + @end @interface ASCellNode () -/* - * A delegate to be notified (on main thread) after a relayout. - */ -@property (nonatomic, weak) id layoutDelegate; +@property (nonatomic, weak) id interactionDelegate; /* * Back-pointer to the containing scrollView instance, set only for visible cells. Used for Cell Visibility Event callbacks. */ @property (nonatomic, weak) UIScrollView *scrollView; +- (void)__setSelectedFromUIKit:(BOOL)selected; +- (void)__setHighlightedFromUIKit:(BOOL)highlighted; + @end diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index e70d67efb5..1e79ee2684 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -77,18 +77,20 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { /* * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. */ -//@property (atomic, retain) UIColor *backgroundColor; +//@property (nonatomic, retain) UIColor *backgroundColor; @property (nonatomic) UITableViewCellSelectionStyle selectionStyle; /** - * A Boolean value that indicates whether the node is selected. + * A Boolean value that is synchronized with the underlying collection or tableView cell property. + * Setting this value is equivalent to calling selectItem / deselectItem on the collection or table. */ -@property (nonatomic, assign) BOOL selected; +@property (nonatomic, assign, getter=isSelected) BOOL selected; /** - * A Boolean value that indicates whether the node is highlighted. + * A Boolean value that is synchronized with the underlying collection or tableView cell property. + * Setting this value is equivalent to calling highlightItem / unHighlightItem on the collection or table. */ -@property (nonatomic, assign) BOOL highlighted; +@property (nonatomic, assign, getter=isHighlighted) BOOL highlighted; /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding @@ -118,7 +120,17 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ - (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock; -- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; +- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(nullable UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; + +@end + +@interface ASCellNode (Unavailable) + +- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; + +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; + +- (void)setLayerBacked:(BOOL)layerBacked AS_UNAVAILABLE("ASCellNode does not support layer-backing"); @end diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index 1bc660c41b..151578d735 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -10,7 +10,6 @@ #import "ASCellNode+Internal.h" -#import "ASInternalHelpers.h" #import "ASEqualityHelpers.h" #import "ASDisplayNodeInternal.h" #import @@ -20,7 +19,6 @@ #import #import -#import #pragma mark - #pragma mark ASCellNode @@ -31,12 +29,13 @@ ASDisplayNodeDidLoadBlock _viewControllerDidLoadBlock; ASDisplayNode *_viewControllerNode; UIViewController *_viewController; + BOOL _suspendInteractionDelegate; } @end @implementation ASCellNode -@synthesize layoutDelegate = _layoutDelegate; +@synthesize interactionDelegate = _interactionDelegate; - (instancetype)init { @@ -105,24 +104,6 @@ _viewControllerNode.frame = self.bounds; } -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (void)setLayerBacked:(BOOL)layerBacked -{ - // ASRangeController expects ASCellNodes to be view-backed. (Layer-backing is supported on ASCellNode subnodes.) - ASDisplayNodeAssert(!layerBacked, @"ASCellNode does not support layer-backing."); -} - - (void)__setNeedsLayout { CGSize oldSize = self.calculatedSize; @@ -130,7 +111,7 @@ //Adding this lock because lock used to be held when this method was called. Not sure if it's necessary for //didRelayoutFromOldSize:toNewSize: - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; } @@ -170,14 +151,52 @@ - (void)didRelayoutFromOldSize:(CGSize)oldSize toNewSize:(CGSize)newSize { - if (_layoutDelegate != nil) { + if (_interactionDelegate != nil) { ASPerformBlockOnMainThread(^{ BOOL sizeChanged = !CGSizeEqualToSize(oldSize, newSize); - [_layoutDelegate nodeDidRelayout:self sizeChanged:sizeChanged]; + [_interactionDelegate nodeDidRelayout:self sizeChanged:sizeChanged]; }); } } +- (void)setSelected:(BOOL)selected +{ + if (_selected != selected) { + _selected = selected; + if (!_suspendInteractionDelegate) { + [_interactionDelegate nodeSelectedStateDidChange:self]; + } + } +} + +- (void)setHighlighted:(BOOL)highlighted +{ + if (_highlighted != highlighted) { + _highlighted = highlighted; + if (!_suspendInteractionDelegate) { + [_interactionDelegate nodeHighlightedStateDidChange:self]; + } + } +} + +- (void)__setSelectedFromUIKit:(BOOL)selected; +{ + if (selected != _selected) { + _suspendInteractionDelegate = YES; + self.selected = selected; + _suspendInteractionDelegate = NO; + } +} + +- (void)__setHighlightedFromUIKit:(BOOL)highlighted; +{ + if (highlighted != _highlighted) { + _suspendInteractionDelegate = YES; + self.highlighted = highlighted; + _suspendInteractionDelegate = NO; + } +} + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-missing-super-calls" @@ -225,13 +244,26 @@ { [super visibleStateDidChange:isVisible]; - CGRect cellFrame = CGRectZero; - if (_scrollView) { - // It is not safe to message nil with a structure return value, so ensure our _scrollView has not died. - cellFrame = [self.view convertRect:self.bounds toView:_scrollView]; + if (isVisible && self.neverShowPlaceholders) { + [self recursivelyEnsureDisplaySynchronously:YES]; } + + // NOTE: This assertion is failing in some apps and will be enabled soon. + // ASDisplayNodeAssert(self.isNodeLoaded, @"Node should be loaded in order for it to become visible or invisible. If not in this situation, we shouldn't trigger creating the view."); + UIView *view = self.view; + CGRect cellFrame = CGRectZero; + + // Ensure our _scrollView is still valid before converting. It's also possible that we have already been removed from the _scrollView, + // in which case it is not valid to perform a convertRect (this actually crashes on iOS 7 and 8). + UIScrollView *scrollView = (_scrollView != nil && view.superview != nil && [view isDescendantOfView:_scrollView]) ? _scrollView : nil; + if (scrollView) { + cellFrame = [view convertRect:view.bounds toView:_scrollView]; + } + + // If we did not convert, we'll pass along CGRectZero and a nil scrollView. The EventInvisible call is thus equivalent to + // visibleStateDidChange:NO, but is more convenient for the developer than implementing multiple methods. [self cellNodeVisibilityEvent:isVisible ? ASCellNodeVisibilityEventVisible : ASCellNodeVisibilityEventInvisible - inScrollView:_scrollView + inScrollView:scrollView withCellFrame:cellFrame]; } diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index b65c79a244..568f9d1df9 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -10,9 +10,15 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import +#import +#import #import @protocol ASCollectionViewLayoutFacilitatorProtocol; +@protocol ASCollectionDelegate; +@protocol ASCollectionDataSource; +@class ASCollectionView; NS_ASSUME_NONNULL_BEGIN @@ -20,16 +26,16 @@ NS_ASSUME_NONNULL_BEGIN * ASCollectionNode is a node based class that wraps an ASCollectionView. It can be used * as a subnode of another node, and provide room for many (great) features and improvements later on. */ -@interface ASCollectionNode : ASDisplayNode +@interface ASCollectionNode : ASDisplayNode - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; +@property (strong, nonatomic, readonly) ASCollectionView *view; + @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; -@property (nonatomic, readonly) ASCollectionView *view; - /** * Tuning parameters for a range type in full mode. * diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index 2784c9a9d1..c8d7aa2573 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -10,54 +10,77 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASCollectionNode.h" #import "ASCollectionInternal.h" #import "ASCollectionViewLayoutFacilitatorProtocol.h" +#import "ASCollectionNode.h" #import "ASDisplayNode+Subclasses.h" #import "ASEnvironmentInternal.h" #import "ASInternalHelpers.h" -#import "ASRangeControllerUpdateRangeProtocol+Beta.h" -#include +#import "ASCellNode+Internal.h" + +#pragma mark - _ASCollectionPendingState @interface _ASCollectionPendingState : NSObject @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; +@property (assign, nonatomic) ASLayoutRangeMode rangeMode; @end @implementation _ASCollectionPendingState -@end -#if 0 // This is not used yet, but will provide a way to avoid creating the view to set range values. -@implementation _ASCollectionPendingState +- (instancetype)init { - std::vector _tuningParameters; + self = [super init]; + if (self) { + _rangeMode = ASLayoutRangeModeCount; + } + return self; +} +@end + +// TODO: Add support for tuning parameters in the pending state +#if 0 // This is not used yet, but will provide a way to avoid creating the view to set range values. +@implementation _ASCollectionPendingState { + std::vector> _tuningParameters; } - (instancetype)init { - if (!(self = [super init])) { - return nil; + self = [super init]; + if (self) { + _tuningParameters = std::vector> (ASLayoutRangeModeCount, std::vector (ASLayoutRangeTypeCount)); + _rangeMode = ASLayoutRangeModeCount; } - _tuningParameters = std::vector(ASLayoutRangeTypeCount); return self; } - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); - return _tuningParameters[rangeType]; + return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); - ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, @"Must not set Visible range tuning parameters (always 0, 0)"); - _tuningParameters[rangeType] = tuningParameters; + return [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; +} + +- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters"); + return _tuningParameters[rangeMode][rangeType]; +} + +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters"); + _tuningParameters[rangeMode][rangeType] = tuningParameters; } @end #endif +#pragma mark - ASCollectionNode + @interface ASCollectionNode () { ASDN::RecursiveMutex _environmentStateLock; @@ -67,6 +90,8 @@ @implementation ASCollectionNode +#pragma mark Lifecycle + - (instancetype)init { ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); @@ -109,6 +134,8 @@ return nil; } +#pragma mark ASDisplayNode + - (void)didLoad { [super didLoad]; @@ -121,9 +148,39 @@ self.pendingState = nil; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; + if (pendingState.rangeMode != ASLayoutRangeModeCount) { + [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; + } } } +- (ASCollectionView *)view +{ + return (ASCollectionView *)[super view]; +} + +- (void)clearContents +{ + [super clearContents]; + [self.view clearContents]; +} + +- (void)clearFetchedData +{ + [super clearFetchedData]; + [self.view clearFetchedData]; +} + +#if ASRangeControllerLoggingEnabled +- (void)visibleStateDidChange:(BOOL)isVisible +{ + [super visibleStateDidChange:isVisible]; + NSLog(@"%@ - visible: %d", self, isVisible); +} +#endif + +#pragma mark Setter / Getter + - (_ASCollectionPendingState *)pendingState { if (!_pendingState && ![self isNodeLoaded]) { @@ -171,47 +228,7 @@ } } -- (ASCollectionView *)view -{ - return (ASCollectionView *)[super view]; -} - -#if ASRangeControllerLoggingEnabled -- (void)visibleStateDidChange:(BOOL)isVisible -{ - [super visibleStateDidChange:isVisible]; - NSLog(@"%@ - visible: %d", self, isVisible); -} -#endif - -- (void)clearContents -{ - [super clearContents]; - [self.view clearContents]; -} - -- (void)clearFetchedData -{ - [super clearFetchedData]; - [self.view clearFetchedData]; -} - -- (void)beginUpdates -{ - [self.view.dataController beginUpdates]; -} - -- (void)endUpdatesAnimated:(BOOL)animated -{ - [self endUpdatesAnimated:animated completion:nil]; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - [self.view.dataController endUpdatesAnimated:animated completion:completion]; -} - -#pragma mark - ASCollectionView Forwards +#pragma mark ASCollectionView Forwards - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { @@ -233,11 +250,6 @@ return [self.view.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; } -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; -{ - [self.view.rangeController updateCurrentRangeWithMode:rangeMode]; -} - - (void)reloadDataWithCompletion:(void (^)())completion { [self.view reloadDataWithCompletion:completion]; @@ -253,6 +265,34 @@ [self.view reloadDataImmediately]; } +- (void)beginUpdates +{ + [self.view.dataController beginUpdates]; +} + +- (void)endUpdatesAnimated:(BOOL)animated +{ + [self endUpdatesAnimated:animated completion:nil]; +} + +- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion +{ + [self.view.dataController endUpdatesAnimated:animated completion:completion]; +} + +#pragma mark - ASRangeControllerUpdateRangeProtocol + +- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; +{ + if ([self pendingState]) { + _pendingState.rangeMode = rangeMode; + } else { + [self.view.rangeController updateCurrentRangeWithMode:rangeMode]; + } +} + +#pragma mark ASEnvironment + ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock) @end diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index a2d5e2ec44..62f508f173 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -14,13 +14,12 @@ #import #import #import -#import -#import @class ASCellNode; @class ASCollectionNode; @protocol ASCollectionDataSource; @protocol ASCollectionDelegate; +@protocol ASCollectionViewLayoutInspecting; NS_ASSUME_NONNULL_BEGIN @@ -278,9 +277,20 @@ NS_ASSUME_NONNULL_BEGIN * * @param indexPath The index path of the requested node. * - * @returns a node for display at this indexpath. + * @returns a node for display at this indexpath or nil */ -- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath; +- (nullable ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath; + + +/** + * Similar to -supplementaryViewForElementKind:atIndexPath: + * + * @param elementKind The kind of supplementary node to locate. + * @param indexPath The index path of the requested supplementary node. + * + * @returns The specified supplementary node or nil + */ +- (nullable ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath; /** * Similar to -indexPathForCell:. @@ -407,8 +417,9 @@ NS_ASSUME_NONNULL_BEGIN * due to the data access in async mode. * * @param collectionView The sender. + * @deprecated The data source is always accessed on the main thread, and this method will not be called. */ -- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView; +- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED; /** * Indicator to unlock the data source for data fetching in async mode. @@ -416,8 +427,9 @@ NS_ASSUME_NONNULL_BEGIN * due to the data access in async mode. * * @param collectionView The sender. + * @deprecated The data source is always accessed on the main thread, and this method will not be called. */ -- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView; +- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED; @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 0c979f4320..5e71ffd40c 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -9,10 +9,10 @@ // #import "ASAssert.h" +#import "ASAvailability.h" #import "ASBatchFetching.h" #import "ASDelegateProxy.h" #import "ASCellNode+Internal.h" -#import "ASCollectionNode.h" #import "ASCollectionDataController.h" #import "ASCollectionViewLayoutController.h" #import "ASCollectionViewFlowLayoutInspector.h" @@ -21,11 +21,23 @@ #import "ASDisplayNode+Beta.h" #import "ASInternalHelpers.h" #import "UICollectionViewLayout+ASConvenience.h" -#import "ASRangeControllerUpdateRangeProtocol+Beta.h" +#import "ASRangeController.h" +#import "ASCollectionNode.h" #import "_ASDisplayLayer.h" +#import "ASCollectionViewLayoutFacilitatorProtocol.h" + + +/// What, if any, invalidation should we perform during the next -layoutSubviews. +typedef NS_ENUM(NSUInteger, ASCollectionViewInvalidationStyle) { + /// Perform no invalidation. + ASCollectionViewInvalidationStyleNone, + /// Perform invalidation with animation (use an empty batch update). + ASCollectionViewInvalidationStyleWithoutAnimation, + /// Perform invalidation without animation (use -invalidateLayout). + ASCollectionViewInvalidationStyleWithAnimation, +}; static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; -static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero}; static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma mark - @@ -42,20 +54,27 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)setNode:(ASCellNode *)node { _node = node; - node.selected = self.selected; - node.highlighted = self.highlighted; + [node __setSelectedFromUIKit:self.selected]; + [node __setHighlightedFromUIKit:self.highlighted]; } - (void)setSelected:(BOOL)selected { [super setSelected:selected]; - _node.selected = selected; + [_node __setSelectedFromUIKit:selected]; } - (void)setHighlighted:(BOOL)highlighted { [super setHighlighted:highlighted]; - _node.highlighted = highlighted; + [_node __setHighlightedFromUIKit:highlighted]; +} + +- (void)prepareForReuse +{ + // Need to clear node pointer before UIKit calls setSelected:NO / setHighlighted:NO on its cells + self.node = nil; + [super prepareForReuse]; } - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes @@ -65,55 +84,25 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; @end -#pragma mark - -#pragma mark _ASCollectionViewNodeSizeUpdateContext - -/** - * This class contains all the nodes that have a new size and UICollectionView should requery them all at once. - * It is intended to be used strictly on main thread and is not thread safe. - */ -@interface _ASCollectionViewNodeSizeInvalidationContext : NSObject -/** - * It's possible that a node triggered multiple size changes before main thread has a chance to execute `requeryNodeSizes`. - * Therefore, a set is preferred here, to avoid asking ASDataController to search for index path of the same node multiple times. - */ -@property (nonatomic, strong) NSMutableSet *invalidatedNodes; -@property (nonatomic, assign) BOOL shouldAnimate; -@end - -@implementation _ASCollectionViewNodeSizeInvalidationContext - -- (instancetype)init -{ - self = [super init]; - if (self) { - _invalidatedNodes = [NSMutableSet set]; - _shouldAnimate = YES; - } - return self; -} - -@end - #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { ASCollectionViewProxy *_proxyDataSource; ASCollectionViewProxy *_proxyDelegate; ASCollectionDataController *_dataController; ASRangeController *_rangeController; ASCollectionViewLayoutController *_layoutController; - ASCollectionViewFlowLayoutInspector *_flowLayoutInspector; + id _defaultLayoutInspector; + id _layoutInspector; NSMutableSet *_cellsForVisibilityUpdates; id _layoutFacilitator; BOOL _performingBatchUpdates; + NSUInteger _superBatchUpdateCount; NSMutableArray *_batchUpdateBlocks; - - BOOL _asyncDataFetchingEnabled; - _ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only + BOOL _isDeallocating; ASBatchContext *_batchContext; @@ -125,6 +114,17 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; CGPoint _deceleratingVelocity; + ASCollectionViewInvalidationStyle _nextLayoutInvalidationStyle; + + /** + * Our layer, retained. Under iOS < 9, when collection views are removed from the hierarchy, + * their layers may be deallocated and become dangling pointers. This puts the collection view + * into a very dangerous state where pretty much any call will crash it. So we manually retain our layer. + * + * You should never access this, and it will be nil under iOS >= 9. + */ + CALayer *_retainedLayer; + /** * If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it. @@ -155,14 +155,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; unsigned int asyncDataSourceNodeForItemAtIndexPath:1; unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1; unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1; - unsigned int asyncDataSourceCollectionViewLockDataSource:1; - unsigned int asyncDataSourceCollectionViewUnlockDataSource:1; unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1; } _asyncDataSourceFlags; + + struct { + unsigned int layoutInspectorDidChangeCollectionViewDataSource:1; + unsigned int layoutInspectorDidChangeCollectionViewDelegate:1; + } _layoutInspectorFlags; } -@property (atomic, assign) BOOL asyncDataSourceLocked; - // Used only when ASCollectionView is created directly rather than through ASCollectionNode. // We create a node so that logic related to appearance, memory management, etc can be located there // for both the node-based and view-based version of the table. @@ -231,18 +232,14 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _rangeController.delegate = self; _rangeController.layoutController = _layoutController; - _dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:NO]; + _dataController = [[ASCollectionDataController alloc] initWithDataSource:self]; _dataController.delegate = _rangeController; - _dataController.dataSource = self; _dataController.environmentDelegate = self; _batchContext = [[ASBatchContext alloc] init]; _leadingScreensForBatching = 2.0; - _asyncDataFetchingEnabled = NO; - _asyncDataSourceLocked = NO; - _performingBatchUpdates = NO; _batchUpdateBlocks = [NSMutableArray array]; @@ -253,11 +250,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; // and should not trigger a relayout. _ignoreMaxSizeChange = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero); - // Register the default layout inspector delegate for flow layouts only, custom layouts - // will need to roll their own ASCollectionViewLayoutInspecting implementation and set a layout delegate - if ([layout asdk_isFlowLayout]) { - _layoutInspector = [self flowLayoutInspector]; - } _layoutFacilitator = layoutFacilitator; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; @@ -273,6 +265,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kCellReuseIdentifier]; + if (!AS_AT_LEAST_IOS9) { + _retainedLayer = self.layer; + } + return self; } @@ -284,19 +280,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [self setAsyncDataSource:nil]; } -/** - * A layout inspector implementation specific for the sizing behavior of UICollectionViewFlowLayouts - */ -- (ASCollectionViewFlowLayoutInspector *)flowLayoutInspector -{ - if (_flowLayoutInspector == nil) { - UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; - ASDisplayNodeAssertNotNil(layout, @"Collection view layout must be a flow layout to use the built-in inspector"); - _flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self flowLayout:layout]; - } - return _flowLayoutInspector; -} - #pragma mark - #pragma mark Overrides. @@ -375,15 +358,17 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; - _asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]; - _asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]; - _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];; + _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath); } super.dataSource = (id)_proxyDataSource; + + if (_layoutInspectorFlags.layoutInspectorDidChangeCollectionViewDataSource) { + [self.layoutInspector didChangeCollectionViewDataSource:asyncDataSource]; + } } - (void)setAsyncDelegate:(id)asyncDelegate @@ -416,27 +401,67 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; super.delegate = (id)_proxyDelegate; - [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; + if (_layoutInspectorFlags.layoutInspectorDidChangeCollectionViewDelegate) { + [self.layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; + } +} + +- (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout +{ + [super setCollectionViewLayout:collectionViewLayout]; + + // Trigger recreation of layout inspector with new collection view layout + if (_layoutInspector != nil) { + _layoutInspector = nil; + [self layoutInspector]; + } +} + +- (id)layoutInspector +{ + if (_layoutInspector == nil) { + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + if ([layout asdk_isFlowLayout]) { + // Register the default layout inspector delegate for flow layouts only + _defaultLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self flowLayout:layout]; + } else { + // Register the default layout inspector delegate for custom collection view layouts + _defaultLayoutInspector = [[ASCollectionViewLayoutInspector alloc] initWithCollectionView:self]; + } + + // Explicitly call the setter to wire up the _layoutInspectorFlags + self.layoutInspector = _defaultLayoutInspector; + } + + return _layoutInspector; +} + +- (void)setLayoutInspector:(id)layoutInspector +{ + _layoutInspector = layoutInspector; + + _layoutInspectorFlags.layoutInspectorDidChangeCollectionViewDataSource = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDataSource:)]; + _layoutInspectorFlags.layoutInspectorDidChangeCollectionViewDelegate = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDelegate:)]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { - [_collectionNode setTuningParameters:tuningParameters forRangeType:rangeType]; + [_rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { - return [_collectionNode tuningParametersForRangeType:rangeType]; + return [_rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - [_collectionNode setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; + [_rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; } - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - return [_collectionNode tuningParametersForRangeMode:rangeMode rangeType:rangeType]; + return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } - (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath @@ -454,6 +479,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return [_dataController nodeAtIndexPath:indexPath]; } +- (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath +{ + return [_dataController supplementaryNodeOfKind:elementKind atIndexPath:indexPath]; +} + - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode { return [_dataController indexPathForNode:cellNode]; @@ -475,6 +505,39 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return visibleNodes; } +- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated +{ + ASDisplayNodeAssertMainThread(); + + [self waitUntilAllUpdatesAreCommitted]; + [super scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; +} + +- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition +{ + ASDisplayNodeAssertMainThread(); + + [self waitUntilAllUpdatesAreCommitted]; + [super selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; +} + +#pragma mark Internal + +/** + Performing nested batch updates with super (e.g. resizing a cell node & updating collection view during same frame) + can cause super to throw data integrity exceptions because it checks the data source counts before + the update is complete. + + Always call [self _superPerform:] rather than [super performBatch:] so that we can keep our `superPerformingBatchUpdates` flag updated. +*/ +- (void)_superPerformBatchUpdates:(void(^)())updates completion:(void(^)(BOOL finished))completion +{ + ASDisplayNodeAssertMainThread(); + + _superBatchUpdateCount++; + [super performBatchUpdates:updates completion:completion]; + _superBatchUpdateCount--; +} #pragma mark Assertions. @@ -508,18 +571,21 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)insertSections:(NSIndexSet *)sections { ASDisplayNodeAssertMainThread(); + if (sections.count == 0) { return; } [_dataController insertSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)deleteSections:(NSIndexSet *)sections { ASDisplayNodeAssertMainThread(); + if (sections.count == 0) { return; } [_dataController deleteSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)reloadSections:(NSIndexSet *)sections { ASDisplayNodeAssertMainThread(); + if (sections.count == 0) { return; } [_dataController reloadSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; } @@ -532,18 +598,21 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); + if (indexPaths.count == 0) { return; } [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); + if (indexPaths.count == 0) { return; } [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); + if (indexPaths.count == 0) { return; } [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; } @@ -582,6 +651,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; NSString *identifier = [self __reuseIdentifierForKind:kind]; UICollectionReusableView *view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:identifier forIndexPath:indexPath]; ASCellNode *node = [_dataController supplementaryNodeOfKind:kind atIndexPath:indexPath]; + ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self); [_rangeController configureContentView:view forCellNode:node]; return view; } @@ -596,7 +666,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; cell.node = node; [_rangeController configureContentView:cell.contentView forCellNode:node]; - if (ASRunningOnOS7()) { + if (!AS_AT_LEAST_IOS8) { // Even though UICV was introduced in iOS 6, and UITableView has always had the equivalent method, // -willDisplayCell: was not introduced until iOS 8 for UICV. didEndDisplayingCell, however, is available. [self collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; @@ -614,11 +684,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; } - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + [_rangeController setNeedsUpdate]; - if (cellNode.neverShowPlaceholders) { - [cellNode recursivelyEnsureDisplaySynchronously:YES]; - } if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) { [_cellsForVisibilityUpdates addObject:cell]; } @@ -626,8 +693,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; - ASCellNode *cellNode = [cell node]; if (_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath) { @@ -635,9 +700,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath]; } - if ([_cellsForVisibilityUpdates containsObject:cell]) { - [_cellsForVisibilityUpdates removeObject:cell]; - } + [_rangeController setNeedsUpdate]; + + [_cellsForVisibilityUpdates removeObject:cell]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -671,9 +736,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { + CGPoint contentOffset = scrollView.contentOffset; _deceleratingVelocity = CGPointMake( - scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), - scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) + contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), + contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) ); if (targetContentOffset != NULL) { @@ -682,7 +748,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } if (_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; + [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:(targetContentOffset ? : &contentOffset)]; } } @@ -796,11 +862,37 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [self performBatchAnimated:YES updates:^{ [_dataController relayoutAllNodes]; } completion:nil]; + // We need to ensure the size requery is done before we update our layout. + [self waitUntilAllUpdatesAreCommitted]; + [self.collectionViewLayout invalidateLayout]; } } + // Flush any pending invalidation action if needed. + ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle; + _nextLayoutInvalidationStyle = ASCollectionViewInvalidationStyleNone; + switch (invalidationStyle) { + case ASCollectionViewInvalidationStyleWithAnimation: + if (0 == _superBatchUpdateCount) { + [self _superPerformBatchUpdates:^{ } completion:nil]; + } + break; + case ASCollectionViewInvalidationStyleWithoutAnimation: + [self.collectionViewLayout invalidateLayout]; + break; + default: + break; + } + // To ensure _maxSizeForNodesConstrainedSize is up-to-date for every usage, this call to super must be done last [super layoutSubviews]; + + // Update range controller immediately if possible & needed. + // Calling -updateIfNeeded in here with self.window == nil (early in the collection view's life) + // may cause UICollectionView data related crashes. We'll update in -didMoveToWindow anyway. + if (self.window != nil) { + [_rangeController updateIfNeeded]; + } } @@ -874,8 +966,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return ^{ __typeof__(self) strongSelf = weakSelf; [node enterHierarchyState:ASHierarchyStateRangeManaged]; - if (node.layoutDelegate == nil) { - node.layoutDelegate = strongSelf; + if (node.interactionDelegate == nil) { + node.interactionDelegate = strongSelf; } return node; }; @@ -889,8 +981,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASCellNode *node = block(); [node enterHierarchyState:ASHierarchyStateRangeManaged]; - if (node.layoutDelegate == nil) { - node.layoutDelegate = strongSelf; + if (node.interactionDelegate == nil) { + node.interactionDelegate = strongSelf; } return node; }; @@ -898,30 +990,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - ASSizeRange constrainedSize = kInvalidSizeRange; - if (_layoutInspector) { - constrainedSize = [_layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; - } - - if (!ASSizeRangeEqualToSizeRange(constrainedSize, kInvalidSizeRange)) { - return constrainedSize; - } - - // TODO: Move this logic into the flow layout inspector. Create a simple inspector for non-flow layouts that don't - // implement a custom inspector. - if (_asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode) { - constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; - } else { - CGSize maxSize = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero) ? self.bounds.size : _maxSizeForNodesConstrainedSize; - if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) { - maxSize.width = FLT_MAX; - } else { - maxSize.height = FLT_MAX; - } - constrainedSize = ASSizeRangeMake(CGSizeZero, maxSize); - } - - return constrainedSize; + return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; } - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section @@ -937,26 +1006,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)dataControllerLockDataSource -{ - ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); - - self.asyncDataSourceLocked = YES; - if (_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource) { - [_asyncDataSource collectionViewLockDataSource:self]; - } -} - -- (void)dataControllerUnlockDataSource -{ - ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked"); - - self.asyncDataSourceLocked = NO; - if (_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource) { - [_asyncDataSource collectionViewUnlockDataSource:self]; - } -} - - (id)dataControllerEnvironment { if (self.collectionNode) { @@ -981,20 +1030,17 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return [_layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; + return [self.layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; } - (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section { - ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return [_layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section]; + return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section]; } - (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind; { - ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return [_layoutInspector collectionView:self numberOfSectionsForSupplementaryNodeOfKind:kind]; + return [self.layoutInspector collectionView:self numberOfSectionsForSupplementaryNodeOfKind:kind]; } #pragma mark - ASRangeControllerDataSource @@ -1007,13 +1053,17 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); - - // Calling visibleNodeIndexPathsForRangeController: will trigger UIKit to call reloadData if it never has, which can result + // Calling -indexPathsForVisibleItems will trigger UIKit to call reloadData if it never has, which can result // in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast. - BOOL isZeroSized = CGRectEqualToRect(self.bounds, CGRectZero); + BOOL isZeroSized = CGSizeEqualToSize(self.bounds.size, CGSizeZero); return isZeroSized ? @[] : [self indexPathsForVisibleItems]; } +- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController +{ + return self.scrollDirection; +} + - (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); @@ -1025,11 +1075,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return ASInterfaceStateForDisplayNode(self.collectionNode, self.window); } -- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths -{ - return [_dataController nodesAtIndexPaths:indexPaths]; -} - - (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath { return [_dataController nodeAtIndexPath:indexPath]; @@ -1046,25 +1091,29 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)rangeController:(ASRangeController *)rangeController didEndUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { if (completion) { completion(NO); } + _performingBatchUpdates = NO; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - NSUInteger numberOfUpdateBlocks = _batchUpdateBlocks.count; ASPerformBlockWithoutAnimation(!animated, ^{ + NSUInteger numberOfUpdateBlocks = _batchUpdateBlocks.count; [_layoutFacilitator collectionViewWillPerformBatchUpdates]; - [super performBatchUpdates:^{ + [self _superPerformBatchUpdates:^{ for (dispatch_block_t block in _batchUpdateBlocks) { block(); } } completion:^(BOOL finished){ + // Flush any range changes that happened as part of the update animations ending. + [_rangeController updateIfNeeded]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdateBlocks]; if (completion) { completion(finished); } }]; + // Flush any range changes that happened as part of submitting the update. + [_rangeController updateIfNeeded]; }); [_batchUpdateBlocks removeAllObjects]; @@ -1091,6 +1140,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } else { [UIView performWithoutAnimation:^{ [super insertItemsAtIndexPaths:indexPaths]; + // Flush any range changes that happened as part of submitting the update. + [_rangeController updateIfNeeded]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; }]; } @@ -1111,6 +1162,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } else { [UIView performWithoutAnimation:^{ [super deleteItemsAtIndexPaths:indexPaths]; + // Flush any range changes that happened as part of submitting the update. + [_rangeController updateIfNeeded]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; }]; } @@ -1131,6 +1184,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } else { [UIView performWithoutAnimation:^{ [super insertSections:indexSet]; + // Flush any range changes that happened as part of submitting the update. + [_rangeController updateIfNeeded]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; }]; } @@ -1151,12 +1206,33 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } else { [UIView performWithoutAnimation:^{ [super deleteSections:indexSet]; + // Flush any range changes that happened as part of submitting the update. + [_rangeController updateIfNeeded]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; }]; } } #pragma mark - ASCellNodeDelegate +- (void)nodeSelectedStateDidChange:(ASCellNode *)node +{ + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath) { + if (node.isSelected) { + [self selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; + } else { + [self deselectItemAtIndexPath:indexPath animated:NO]; + } + } +} + +- (void)nodeHighlightedStateDidChange:(ASCellNode *)node +{ + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath) { + [self cellForItemAtIndexPath:indexPath].highlighted = node.isHighlighted; + } +} - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged { @@ -1166,57 +1242,36 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return; } - BOOL queued = (_queuedNodeSizeInvalidationContext != nil); - if (!queued) { - _queuedNodeSizeInvalidationContext = [[_ASCollectionViewNodeSizeInvalidationContext alloc] init]; - - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - __typeof__(self) strongSelf = weakSelf; - if (strongSelf) { - [strongSelf requeryNodeSizes]; - } - }); + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath == nil) { + return; } - - [_queuedNodeSizeInvalidationContext.invalidatedNodes addObject:node]; - // Check if this node or one of its subnodes can be animated. - // If the context is already non-animated, don't bother checking this node. - if (_queuedNodeSizeInvalidationContext.shouldAnimate) { - BOOL (^shouldNotAnimateBlock)(ASDisplayNode *) = ^BOOL(ASDisplayNode * _Nonnull node) { - return node.shouldAnimateSizeChanges == NO; - }; + [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[ indexPath ] batched:NO]; + + ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle; + if (invalidationStyle == ASCollectionViewInvalidationStyleNone) { + [self setNeedsLayout]; + invalidationStyle = ASCollectionViewInvalidationStyleWithAnimation; + } + + // If we think we're going to animate, check if this node will prevent it. + if (invalidationStyle == ASCollectionViewInvalidationStyleWithAnimation) { + // TODO: Incorporate `shouldAnimateSizeChanges` into ASEnvironmentState for performance benefit. + static dispatch_once_t onceToken; + static BOOL (^shouldNotAnimateBlock)(ASDisplayNode *); + dispatch_once(&onceToken, ^{ + shouldNotAnimateBlock = ^BOOL(ASDisplayNode * _Nonnull node) { + return (node.shouldAnimateSizeChanges == NO); + }; + }); if (ASDisplayNodeFindFirstNode(node, shouldNotAnimateBlock) != nil) { - // One single non-animated cell node causes the whole context to be non-animated - _queuedNodeSizeInvalidationContext.shouldAnimate = NO; - } - } -} - -// Cause UICollectionView to requery for the new size of all nodes -- (void)requeryNodeSizes -{ - ASDisplayNodeAssertMainThread(); - NSSet *nodes = _queuedNodeSizeInvalidationContext.invalidatedNodes; - NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodes.count]; - for (ASCellNode *node in nodes) { - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (indexPath != nil) { - [indexPaths addObject:indexPath]; + // One single non-animated node causes the whole layout update to be non-animated + invalidationStyle = ASCollectionViewInvalidationStyleWithoutAnimation; } } - if (indexPaths.count > 0) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; - - ASPerformBlockWithoutAnimation(!_queuedNodeSizeInvalidationContext.shouldAnimate, ^{ - // Perform an empty update transaction here to trigger UICollectionView to requery row sizes and layout its subviews again - [super performBatchUpdates:^{} completion:nil]; - }); - } - - _queuedNodeSizeInvalidationContext = nil; + _nextLayoutInvalidationStyle = invalidationStyle; } #pragma mark - Memory Management @@ -1254,7 +1309,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass if (![node supportsRangeManagedInterfaceState]) { - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + [_rangeController setNeedsUpdate]; + [_rangeController updateIfNeeded]; } } diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index a2be60e5bf..29b0b57da2 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -10,8 +10,6 @@ #import "ASControlNode.h" #import "ASControlNode+Subclasses.h" -#import "ASThread.h" -#import "ASDisplayNodeExtras.h" #import "ASImageNode.h" #import "AsyncDisplayKit+Debug.h" #import "ASInternalHelpers.h" @@ -288,7 +286,9 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { // Create the dispatch table for this event. eventDispatchTable = [NSMapTable weakToStrongObjectsMapTable]; - _controlEventDispatchTable[eventKey] = eventDispatchTable; + if (eventKey) { + [_controlEventDispatchTable setObject:eventDispatchTable forKey:eventKey]; + } } // Have we seen this target before for this event? @@ -332,7 +332,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v NSMutableSet *targets = [[NSMutableSet alloc] init]; // Look at each event... - for (NSMapTable *eventDispatchTable in [_controlEventDispatchTable allValues]) + for (NSMapTable *eventDispatchTable in [_controlEventDispatchTable objectEnumerator]) { // and each event's targets... for (id target in eventDispatchTable) @@ -444,9 +444,11 @@ id _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEv void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)) { + if (block == nil) { + return; + } // Start with our first event (touch down) and work our way up to the last event (touch cancel) - for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventTouchCancel; thisEvent <<= 1) - { + for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventTouchCancel; thisEvent <<= 1){ // If it's included in the mask, invoke the block. if ((mask & thisEvent) == thisEvent) block(thisEvent); diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index 428f95f34c..b44715c97e 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -9,6 +9,7 @@ // #import "ASContextTransitioning.h" +#import "ASLayoutRangeType.h" NS_ASSUME_NONNULL_BEGIN @@ -22,6 +23,20 @@ ASDISPLAYNODE_EXTERN_C_END + (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 + * on invalid updates rather than throwing exceptions. + * + * Note that even if AsyncDisplayKit's exception is suppressed, the app may still crash + * as it proceeds with an invalid update. + * + * This currently defaults to YES. In a future release it will default to NO and later + * be removed entirely. + */ ++ (BOOL)suppressesInvalidCollectionUpdateExceptions; ++ (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses; + /** @name Layout */ @@ -116,6 +131,15 @@ ASDISPLAYNODE_EXTERN_C_END */ - (void)hierarchyDisplayDidFinish; +/** + * Only ASLayoutRangeModeVisibleOnly or ASLayoutRangeModeLowMemory are recommended. Default is ASLayoutRangeModeVisibleOnly, + * because this is the only way to ensure an application will not have blank / flashing views as the user navigates back after + * a memory warning. Apps that wish to use the more effective / aggressive ASLayoutRangeModeLowMemory may need to take steps + * to mitigate this behavior, including: restoring a larger range mode to the next controller before the user navigates there, + * enabling .neverShowPlaceholders on ASCellNodes so that the navigation operation is blocked on redisplay completing, etc. + */ ++ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 967b82270d..9711355757 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -465,6 +465,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSString *)descriptionForRecursiveDescription; +/** + * @abstract Called when the node's ASTraitCollection changes + * + * @discussion Subclasses can override this method to react to a trait collection change. + */ +- (void)asyncTraitCollectionDidChange; + @end #define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index c0361d0ce6..005d9e2203 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -17,6 +17,8 @@ #import #import +#define ASDisplayNodeLoggingEnabled 0 + @class ASDisplayNode; /** @@ -163,7 +165,7 @@ NS_ASSUME_NONNULL_BEGIN /** * @abstract The name of this node, which will be displayed in `description`. The default value is nil. */ -@property (nullable, atomic, copy) NSString *name; +@property (nullable, nonatomic, copy) NSString *name; /** * @abstract Returns whether the node is synchronous. @@ -191,7 +193,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return YES if a view is loaded, or if layerBacked is YES and layer is not nil; NO otherwise. */ -@property (atomic, readonly, assign, getter=isNodeLoaded) BOOL nodeLoaded; +@property (nonatomic, readonly, assign, getter=isNodeLoaded) BOOL nodeLoaded; /** * @abstract Returns whether the node rely on a layer instead of a view. @@ -301,7 +303,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return The preferred frame size of this node */ -@property (atomic, assign, readwrite) CGSize preferredFrameSize; +@property (nonatomic, assign, readwrite) CGSize preferredFrameSize; /** @name Managing the nodes hierarchy */ @@ -644,27 +646,27 @@ NS_ASSUME_NONNULL_END */ - (void)setNeedsLayout; -@property (atomic, strong, nullable) id contents; // default=nil -@property (atomic, assign) BOOL clipsToBounds; // default==NO -@property (atomic, getter=isOpaque) BOOL opaque; // default==YES +@property (nonatomic, strong, nullable) id contents; // default=nil +@property (nonatomic, assign) BOOL clipsToBounds; // default==NO +@property (nonatomic, getter=isOpaque) BOOL opaque; // default==YES -@property (atomic, assign) BOOL allowsEdgeAntialiasing; -@property (atomic, assign) unsigned int edgeAntialiasingMask; // default==all values from CAEdgeAntialiasingMask +@property (nonatomic, assign) BOOL allowsEdgeAntialiasing; +@property (nonatomic, assign) unsigned int edgeAntialiasingMask; // default==all values from CAEdgeAntialiasingMask -@property (atomic, getter=isHidden) BOOL hidden; // default==NO -@property (atomic, assign) BOOL needsDisplayOnBoundsChange; // default==NO -@property (atomic, assign) BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes) -@property (atomic, assign) UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes) -@property (atomic, assign) CGFloat alpha; // default=1.0f -@property (atomic, assign) CGRect bounds; // default=CGRectZero -@property (atomic, assign) CGRect frame; // default=CGRectZero -@property (atomic, assign) CGPoint anchorPoint; // default={0.5, 0.5} -@property (atomic, assign) CGFloat zPosition; // default=0.0 -@property (atomic, assign) CGPoint position; // default=CGPointZero -@property (atomic, assign) CGFloat cornerRadius; // default=0.0 -@property (atomic, assign) CGFloat contentsScale; // default=1.0f. See @contentsScaleForDisplay for more info -@property (atomic, assign) CATransform3D transform; // default=CATransform3DIdentity -@property (atomic, assign) CATransform3D subnodeTransform; // default=CATransform3DIdentity +@property (nonatomic, getter=isHidden) BOOL hidden; // default==NO +@property (nonatomic, assign) BOOL needsDisplayOnBoundsChange; // default==NO +@property (nonatomic, assign) BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes) +@property (nonatomic, assign) UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes) +@property (nonatomic, assign) CGFloat alpha; // default=1.0f +@property (nonatomic, assign) CGRect bounds; // default=CGRectZero +@property (nonatomic, assign) CGRect frame; // default=CGRectZero +@property (nonatomic, assign) CGPoint anchorPoint; // default={0.5, 0.5} +@property (nonatomic, assign) CGFloat zPosition; // default=0.0 +@property (nonatomic, assign) CGPoint position; // default=CGPointZero +@property (nonatomic, assign) CGFloat cornerRadius; // default=0.0 +@property (nonatomic, assign) CGFloat contentsScale; // default=1.0f. See @contentsScaleForDisplay for more info +@property (nonatomic, assign) CATransform3D transform; // default=CATransform3DIdentity +@property (nonatomic, assign) CATransform3D subnodeTransform; // default=CATransform3DIdentity /** * @abstract The node view's background color. @@ -672,9 +674,9 @@ NS_ASSUME_NONNULL_END * @discussion In contrast to UIView, setting a transparent color will not set opaque = NO. * This only affects nodes that implement +drawRect like ASTextNode. */ -@property (atomic, strong, nullable) UIColor *backgroundColor; // default=nil +@property (nonatomic, strong, nullable) UIColor *backgroundColor; // default=nil -@property (atomic, strong, null_resettable) UIColor *tintColor; // default=Blue +@property (nonatomic, strong, null_resettable) UIColor *tintColor; // default=Blue - (void)tintColorDidChange; // Notifies the node when the tintColor has changed. /** @@ -685,18 +687,18 @@ NS_ASSUME_NONNULL_END * Thus, UIViewContentModeRedraw is not allowed; use needsDisplayOnBoundsChange = YES instead, and pick an appropriate * contentMode for your content while it's being re-rendered. */ -@property (atomic, assign) UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill +@property (nonatomic, assign) UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill -@property (atomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes) +@property (nonatomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes) #if TARGET_OS_IOS -@property (atomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO +@property (nonatomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO #endif -@property (atomic, assign, nullable) CGColorRef shadowColor; // default=opaque rgb black -@property (atomic, assign) CGFloat shadowOpacity; // default=0.0 -@property (atomic, assign) CGSize shadowOffset; // default=(0, -3) -@property (atomic, assign) CGFloat shadowRadius; // default=3 -@property (atomic, assign) CGFloat borderWidth; // default=0 -@property (atomic, assign, nullable) CGColorRef borderColor; // default=opaque rgb black +@property (nonatomic, assign, nullable) CGColorRef shadowColor; // default=opaque rgb black +@property (nonatomic, assign) CGFloat shadowOpacity; // default=0.0 +@property (nonatomic, assign) CGSize shadowOffset; // default=(0, -3) +@property (nonatomic, assign) CGFloat shadowRadius; // default=3 +@property (nonatomic, assign) CGFloat borderWidth; // default=0 +@property (nonatomic, assign, nullable) CGColorRef borderColor; // default=opaque rgb black // UIResponder methods // By default these fall through to the underlying view, but can be overridden. diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index b2ab531519..c57589025d 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -14,7 +14,6 @@ #import "ASDisplayNode+Beta.h" #import -#import #import "_ASAsyncTransaction.h" #import "_ASAsyncTransactionContainer+Private.h" @@ -22,7 +21,6 @@ #import "_ASDisplayView.h" #import "_ASScopeTimer.h" #import "_ASCoreAnimationExtras.h" -#import "ASLayoutTransition.h" #import "ASDisplayNodeExtras.h" #import "ASTraitCollection.h" #import "ASEqualityHelpers.h" @@ -39,6 +37,10 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; +// Forward declare CALayerDelegate protocol as the iOS 10 SDK moves CALayerDelegate from a formal delegate to a protocol. +// We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10 +@protocol CALayerDelegate; + @interface ASDisplayNode () /** @@ -51,8 +53,11 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS @end -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) +#if ASDisplayNodeLoggingEnabled + #define LOG(...) NSLog(__VA_ARGS__) +#else + #define LOG(...) +#endif // Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) #if TIME_DISPLAYNODE_OPS @@ -84,6 +89,18 @@ static BOOL usesImplicitHierarchyManagement = NO; usesImplicitHierarchyManagement = enabled; } +static BOOL suppressesInvalidCollectionUpdateExceptions = YES; + ++ (BOOL)suppressesInvalidCollectionUpdateExceptions +{ + return suppressesInvalidCollectionUpdateExceptions; +} + ++ (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses +{ + suppressesInvalidCollectionUpdateExceptions = suppresses; +} + BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) { return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); @@ -98,7 +115,7 @@ BOOL ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(ASDisplayNodeFlags flag _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) { - ASDN::MutexLocker l(node->_propertyLock); + ASDN::MutexLocker l(node->__instanceLock__); _ASPendingState *result = node->_pendingViewState; if (result == nil) { result = [[_ASPendingState alloc] init]; @@ -245,9 +262,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) dispatch_once(&onceToken, ^{ renderQueue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) { - CFAbsoluteTime timestamp = isQueueDrained ? CFAbsoluteTimeGetCurrent() : 0; [dequeuedItem _recursivelyTriggerDisplayAndBlock:NO]; if (isQueueDrained) { + CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent(); [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}]; @@ -396,7 +413,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASDisplayNodeAssertThreadAffinity(self); ASDisplayNodeAssert([self isNodeLoaded], @"Implementation shouldn't call __unloadNode if not loaded: %@", self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_flags.layerBacked) _pendingViewState = [_ASPendingState pendingViewStateFromLayer:_layer]; @@ -429,7 +446,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (UIView *)_viewToLoad { UIView *view; - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_viewBlock) { view = _viewBlock(); @@ -459,7 +476,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (CALayer *)_layerToLoad { CALayer *layer; - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(_flags.layerBacked, @"_layerToLoad is only for layer-backed nodes"); if (_layerBlock) { @@ -480,7 +497,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (self._isDeallocating) { return; @@ -493,7 +510,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (isLayerBacked) { TIME_SCOPED(_debugTimeToCreateView); _layer = [self _layerToLoad]; - _layer.delegate = (id)self; + // Surpress warning for Base SDK > 10.0 + _layer.delegate = (id)self; } else { TIME_SCOPED(_debugTimeToCreateView); _view = [self _viewToLoad]; @@ -553,7 +571,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Returns nil if the layer is not an _ASDisplayLayer; will not create the layer if nil. - (_ASDisplayLayer *)asyncLayer { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return [_layer isKindOfClass:[_ASDisplayLayer class]] ? (_ASDisplayLayer *)_layer : nil; } @@ -564,20 +582,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // where the state of this property can change. As an optimization, we can avoid locking. return (_view != nil || (_layer != nil && _flags.layerBacked)); } else { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return (_view != nil || (_layer != nil && _flags.layerBacked)); } } - (NSString *)name { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _name; } - (void)setName:(NSString *)name { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (!ASObjectIsEqual(_name, name)) { _name = [name copy]; } @@ -597,7 +615,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { if (![self.class layerBackedNodesEnabled]) return; - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(!_view && !_layer, @"Cannot change isLayerBacked after layer or view has loaded"); ASDisplayNodeAssert(!_viewBlock && !_layerBlock, @"Cannot change isLayerBacked when a layer or view block is provided"); ASDisplayNodeAssert(!_viewClass && !_layerClass, @"Cannot change isLayerBacked when a layer or view class is provided"); @@ -609,7 +627,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (BOOL)isLayerBacked { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _flags.layerBacked; } @@ -622,37 +640,33 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (! [self shouldMeasureWithSizeRange:constrainedSize]) { - return _layout; + ASDisplayNodeAssertNotNil(_calculatedLayout, @"-[ASDisplayNode measureWithSizeRange:] _layout should not be nil! %@", self); + return _calculatedLayout ? : [ASLayout layoutWithLayoutableObject:self constrainedSizeRange:constrainedSize size:CGSizeZero]; } - + [self cancelLayoutTransitionsInProgress]; - ASLayout *previousLayout = _layout; + ASLayout *previousLayout = _calculatedLayout; ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize]; - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { - _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:newLayout - previousLayout:previousLayout]; - } else { - ASLayoutTransition *layoutContext; - if (self.usesImplicitHierarchyManagement) { - layoutContext = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:newLayout - previousLayout:previousLayout]; - } - [self applyLayout:newLayout layoutContext:layoutContext]; - [self _completeLayoutCalculation]; + _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self + pendingLayout:newLayout + previousLayout:previousLayout]; + + if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) { + // Complete the pending layout transition immediately + [self _completePendingLayoutTransition]; } - + + ASDisplayNodeAssertNotNil(newLayout, @"-[ASDisplayNode measureWithSizeRange:] newLayout should not be nil! %@", self); return newLayout; } - (BOOL)shouldMeasureWithSizeRange:(ASSizeRange)constrainedSize { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (![self __shouldSize]) { return NO; } @@ -667,12 +681,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Only generate a new layout if: // - The current layout is dirty // - The passed constrained size is different than the layout's constrained size - return ([self _hasDirtyLayout] || !ASSizeRangeEqualToSizeRange(constrainedSize, _layout.constrainedSizeRange)); + return ([self _hasDirtyLayout] || !ASSizeRangeEqualToSizeRange(constrainedSize, _calculatedLayout.constrainedSizeRange)); } - (BOOL)_hasDirtyLayout { - return _layout == nil || _layout.isDirty; + return _calculatedLayout == nil || _calculatedLayout.isDirty; } - (ASLayoutableType)layoutableType @@ -680,14 +694,25 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return ASLayoutableTypeDisplayNode; } +- (BOOL)canLayoutAsynchronous +{ + return !self.isNodeLoaded; +} + #pragma mark - Layout Transition - (void)transitionLayoutWithAnimation:(BOOL)animated shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(void(^)())completion { + if (_calculatedLayout == nil) { + // constrainedSizeRange returns a struct and is invalid to call on nil. + // Defaulting to CGSizeZero can cause negative values in client layout code. + return; + } + [self invalidateCalculatedLayout]; - [self transitionLayoutWithSizeRange:_layout.constrainedSizeRange + [self transitionLayoutWithSizeRange:_calculatedLayout.constrainedSizeRange animated:animated shouldMeasureAsync:shouldMeasureAsync measurementCompletion:completion]; @@ -704,14 +729,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one."); } int32_t transitionID = [self _startNewTransition]; ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) { - ASDisplayNodeAssert([node _hasTransitionInProgress] == NO, @"Can't start a transition when one of the subnodes is performing one."); + ASDisplayNodeAssert([node _isTransitionInProgress] == NO, @"Can't start a transition when one of the subnodes is performing one."); node.hierarchyState |= ASHierarchyStateLayoutPending; node.pendingTransitionID = transitionID; }); @@ -725,7 +750,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASLayoutableSetCurrentContext(ASLayoutableContextMake(transitionID, NO)); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); BOOL disableImplicitHierarchyManagement = self.usesImplicitHierarchyManagement == NO; self.usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x newLayout = [self calculateLayoutThatFits:constrainedSize]; @@ -741,20 +766,19 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } ASPerformBlockOnMainThread(^{ - // Grab _propertyLock here to make sure this transition isn't invalidated + // Grab __instanceLock__ here to make sure this transition isn't invalidated // right after it passed the validation test and before it proceeds - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if ([self _shouldAbortTransitionWithID:transitionID]) { return; } - ASLayout *previousLayout = _layout; - [self applyLayout:newLayout layoutContext:nil]; + ASLayout *previousLayout = _calculatedLayout; + [self setCalculatedLayout:newLayout]; ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) { - [node applyPendingLayoutContext]; - [node _completeLayoutCalculation]; + [node _completePendingLayoutTransition]; node.hierarchyState &= (~ASHierarchyStateLayoutPending); }); @@ -764,56 +788,33 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) completion(); } - _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:newLayout - previousLayout:previousLayout]; + // 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; + + // Setup context for pending layout transition. we need to hold a strong reference to the context + _pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated + layoutDelegate:_pendingLayoutTransition + completionDelegate:self]; + + // Apply the subnode insertion immediately to be able to animate the nodes [_pendingLayoutTransition applySubnodeInsertions]; - - _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated - layoutDelegate:_pendingLayoutTransition - completionDelegate:self]; - [self animateLayoutTransition:_transitionContext]; + + // Kick off animating the layout transition + [self animateLayoutTransition:_pendingLayoutTransitionContext]; }); }; - if (shouldMeasureAsync) { - ASPerformBlockOnBackgroundThread(transitionBlock); - } else { - transitionBlock(); - } -} - -- (void)_completeLayoutCalculation -{ - ASDN::MutexLocker l(_propertyLock); - [self calculatedLayoutDidChange]; - - // We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go. - // This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously. - // First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync. - if (_placeholderEnabled && [self _displaysAsynchronously] && self.contents == nil) { - - // Zero-sized nodes do not require a placeholder. - CGSize layoutSize = (_layout ? _layout.size : CGSizeZero); - if (CGSizeEqualToSize(layoutSize, CGSizeZero)) { - return; - } - - if (!_placeholderImage) { - _placeholderImage = [self placeholderImage]; - } - } -} - -- (void)calculatedLayoutDidChange -{ - // subclass override + ASPerformBlockOnBackgroundThread(transitionBlock); } - (void)cancelLayoutTransitionsInProgress { - ASDN::MutexLocker l(_propertyLock); - if ([self _hasTransitionInProgress]) { + ASDN::MutexLocker l(__instanceLock__); + if ([self _isTransitionInProgress]) { // Cancel transition in progress [self _finishOrCancelTransition]; @@ -826,26 +827,26 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (BOOL)usesImplicitHierarchyManagement { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _usesImplicitHierarchyManagement ? : [[self class] usesImplicitHierarchyManagement]; } - (void)setUsesImplicitHierarchyManagement:(BOOL)value { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _usesImplicitHierarchyManagement = value; } -- (BOOL)_hasTransitionInProgress +- (BOOL)_isTransitionInProgress { - ASDN::MutexLocker l(_propertyLock); - return _transitionInProgress; + ASDN::MutexLocker l(__instanceLock__); + return _transitionInProgress; } /// Starts a new transition and returns the transition id - (int32_t)_startNewTransition { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _transitionInProgress = YES; _transitionID = OSAtomicAdd32(1, &_transitionID); return _transitionID; @@ -853,48 +854,131 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)_finishOrCancelTransition { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _transitionInProgress = NO; } - (BOOL)_shouldAbortTransitionWithID:(int32_t)transitionID { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return (!_transitionInProgress || _transitionID != transitionID); } +#pragma mark - Layout Transition API / ASDisplayNode (Beta) + +/* + * 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. + */ - (void)animateLayoutTransition:(id)context { [self __layoutSublayouts]; [context completeTransition:YES]; } +/* + * Hook for subclasses to clean up nodes after the transition happened. Furthermore this can be used from subclasses + * to manually perform deletions. + */ - (void)didCompleteLayoutTransition:(id)context { [_pendingLayoutTransition applySubnodeRemovals]; - [self _completeLayoutCalculation]; - _pendingLayoutTransition = nil; } #pragma mark - _ASTransitionContextCompletionDelegate +/* + * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this + * delegate method will be called that start the completion process of the + */ - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete { [self didCompleteLayoutTransition:context]; - _transitionContext = nil; + _pendingLayoutTransitionContext = nil; + + [self _pendingLayoutTransitionDidComplete]; +} + +#pragma mark - Layout + +/* + * Completes the pending layout transition immediately without going through the the Layout Transition Animation API + */ +- (void)_completePendingLayoutTransition +{ + ASDN::MutexLocker l(__instanceLock__); + if (_pendingLayoutTransition) { + [self setCalculatedLayout:_pendingLayoutTransition.pendingLayout]; + [self _completeLayoutTransition:_pendingLayoutTransition]; + } + [self _pendingLayoutTransitionDidComplete]; +} + +/* + * Can be directly called to commit the given layout transition immediately to complete without calling through to the + * Layout Transition Animation API + */ +- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition +{ + // Layout transition is not supported for non implicit hierarchy managed nodes yet + if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) { + return; + } + + // Trampoline to the main thread if necessary + if (ASDisplayNodeThreadIsMain() || layoutTransition.isSynchronous == NO) { + [layoutTransition commitTransition]; + } else { + // Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded + ASPerformBlockOnMainThread(^{ + [layoutTransition commitTransition]; + }); + } +} + +- (void)_pendingLayoutTransitionDidComplete +{ + ASDN::MutexLocker l(__instanceLock__); + + // Subclass hook + [self calculatedLayoutDidChange]; + + // We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go. + // This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously. + // First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync. + if (_placeholderEnabled && [self _displaysAsynchronously] && self.contents == nil) { + + // Zero-sized nodes do not require a placeholder. + CGSize layoutSize = (_calculatedLayout ? _calculatedLayout.size : CGSizeZero); + if (CGSizeEqualToSize(layoutSize, CGSizeZero)) { + return; + } + + if (!_placeholderImage) { + _placeholderImage = [self placeholderImage]; + } + } + + // Cleanup pending layout transition + _pendingLayoutTransition = nil; +} + +- (void)calculatedLayoutDidChange +{ + // subclass override } #pragma mark - Asynchronous display - (BOOL)displaysAsynchronously { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return [self _displaysAsynchronously]; } /** * Core implementation of -displaysAsynchronously. - * Must be called with _propertyLock held. + * Must be called with __instanceLock__ held. */ - (BOOL)_displaysAsynchronously { @@ -910,7 +994,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (_flags.synchronous) return; - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_flags.displaysAsynchronously == displaysAsynchronously) return; @@ -922,7 +1006,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (BOOL)shouldRasterizeDescendants { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(!((_hierarchyState & ASHierarchyStateRasterized) && _flags.shouldRasterizeDescendants), @"Subnode of a rasterized node should not have redundant shouldRasterizeDescendants enabled"); return _flags.shouldRasterizeDescendants; @@ -932,7 +1016,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASDisplayNodeAssertThreadAffinity(self); { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_flags.shouldRasterizeDescendants == shouldRasterize) return; @@ -981,7 +1065,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (CGFloat)contentsScaleForDisplay { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _contentsScaleForDisplay; } @@ -989,7 +1073,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)setContentsScaleForDisplay:(CGFloat)contentsScaleForDisplay { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_contentsScaleForDisplay == contentsScaleForDisplay) return; @@ -1000,7 +1084,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)applyPendingViewState { ASDisplayNodeAssertMainThread(); - ASDN::MutexLocker l(_propertyLock); + 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 @@ -1029,7 +1113,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)recursivelyDisplayImmediately { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); for (ASDisplayNode *child in _subnodes) { [child recursivelyDisplayImmediately]; @@ -1040,17 +1124,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)__setNeedsLayout { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); - if ([self _hasDirtyLayout]) { + __instanceLock__.lock(); + + if (_calculatedLayout == nil) { + // Can't proceed without a layout as no constrained size would be available + __instanceLock__.unlock(); return; } - + [self invalidateCalculatedLayout]; if (_supernode) { ASDisplayNode *supernode = _supernode; - ASDN::MutexUnlocker u(_propertyLock); + __instanceLock__.unlock(); // Cause supernode's layout to be invalidated // We need to release the lock to prevent a deadlock [supernode setNeedsLayout]; @@ -1058,11 +1145,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used. - [self measureWithSizeRange:_layout.constrainedSizeRange]; + [self measureWithSizeRange:_calculatedLayout.constrainedSizeRange]; CGRect oldBounds = self.bounds; CGSize oldSize = oldBounds.size; - CGSize newSize = _layout.size; + CGSize newSize = _calculatedLayout.size; if (! CGSizeEqualToSize(oldSize, newSize)) { self.bounds = (CGRect){ oldBounds.origin, newSize }; @@ -1075,6 +1162,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); } + + __instanceLock__.unlock(); } - (void)__setNeedsDisplay @@ -1092,21 +1181,55 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)__layout { ASDisplayNodeAssertMainThread(); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); CGRect bounds = self.bounds; + + [self measureNodeWithBoundsIfNecessary:bounds]; + if (CGRectEqualToRect(bounds, CGRectZero)) { // Performing layout on a zero-bounds view often results in frame calculations // with negative sizes after applying margins, which will cause // measureWithSizeRange: on subnodes to assert. return; } + + // Handle placeholder layer creation in case the size of the node changed after the initial placeholder layer + // was created + if ([self _shouldHavePlaceholderLayer]) { + [self _setupPlaceholderLayerIfNeeded]; + } _placeholderLayer.frame = bounds; + [self layout]; [self layoutDidFinish]; } +- (void)measureNodeWithBoundsIfNecessary:(CGRect)bounds +{ + BOOL supportsRangedManagedInterfaceState = NO; + BOOL hasDirtyLayout = NO; + BOOL hasSupernode = NO; + { + ASDN::MutexLocker l(__instanceLock__); + supportsRangedManagedInterfaceState = [self supportsRangeManagedInterfaceState]; + hasDirtyLayout = [self _hasDirtyLayout]; + hasSupernode = (self.supernode != nil); + } + + // Normally measure will be called before layout occurs. If this doesn't happen, nothing is going to call it at all. + // We simply call measureWithSizeRange: using a size range equal to whatever bounds were provided to that element + if (!hasSupernode && !supportsRangedManagedInterfaceState && hasDirtyLayout) { + if (CGRectEqualToRect(bounds, CGRectZero)) { + LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); + } else { + [self measureWithSizeRange:ASSizeRangeMake(bounds.size, bounds.size)]; + } + } +} + - (void)layoutDidFinish { + // Hook for subclasses } - (CATransform3D)_transformToAncestor:(ASDisplayNode *)ancestor @@ -1233,7 +1356,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo return (id)[NSNull null]; } -#pragma mark - +#pragma mark - Managing the Node Hierarchy static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASDisplayNode *to) { @@ -1247,11 +1370,13 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD - (void)addSubnode:(ASDisplayNode *)subnode { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); ASDisplayNode *oldParent = subnode.supernode; - if (!subnode || subnode == self || oldParent == self) + if (!subnode || subnode == self || oldParent == self) { return; + } // Disable appearance methods during move between supernodes, but make sure we restore their state after we do our thing BOOL isMovingEquivalentParents = disableNotificationsForMovingBetweenParents(oldParent, self); @@ -1260,8 +1385,9 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD } [subnode removeFromSupernode]; - if (!_subnodes) + if (!_subnodes) { _subnodes = [[NSMutableArray alloc] init]; + } [_subnodes addObject:subnode]; @@ -1287,7 +1413,7 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD /* Private helper function. - You must hold _propertyLock to call this. + You must hold __instanceLock__ to call this. @param subnode The subnode to insert @param subnodeIndex The index in _subnodes to insert it @@ -1297,8 +1423,14 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD */ - (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnodeIndex sublayerIndex:(NSInteger)sublayerIndex andRemoveSubnode:(ASDisplayNode *)oldSubnode { - if (subnodeIndex == NSNotFound) + if (subnodeIndex == NSNotFound) { return; + } + + ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); + if (!subnode) { + return; + } ASDisplayNode *oldParent = [subnode _deallocSafeSupernode]; // Disable appearance methods during move between supernodes, but make sure we restore their state after we do our thing @@ -1346,7 +1478,7 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD - (void)replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (!replacementSubnode || [oldSubnode _deallocSafeSupernode] != self) { ASDisplayNodeAssert(0, @"Bad use of api. Invalid subnode to replace async."); @@ -1376,15 +1508,17 @@ static NSInteger incrementIfFound(NSInteger i) { - (void)insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); - if (!subnode) + if (!subnode) { return; + } ASDisplayNodeAssert([below _deallocSafeSupernode] == self, @"Node to insert below must be a subnode"); - if ([below _deallocSafeSupernode] != self) + if ([below _deallocSafeSupernode] != self) { return; + } ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); @@ -1419,15 +1553,17 @@ static NSInteger incrementIfFound(NSInteger i) { - (void)insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); - if (!subnode) + if (!subnode) { return; + } ASDisplayNodeAssert([above _deallocSafeSupernode] == self, @"Node to insert above must be a subnode"); - if ([above _deallocSafeSupernode] != self) + if ([above _deallocSafeSupernode] != self) { return; + } ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); @@ -1465,13 +1601,18 @@ static NSInteger incrementIfFound(NSInteger i) { - (void)insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (idx > _subnodes.count || idx < 0) { NSString *reason = [NSString stringWithFormat:@"Cannot insert a subnode at index %zd. Count is %zd", idx, _subnodes.count]; @throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil]; } - + + ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); + if (!subnode) { + return; + } + NSInteger sublayerIndex = NSNotFound; // Account for potentially having other subviews @@ -1515,12 +1656,13 @@ static NSInteger incrementIfFound(NSInteger i) { - (void)_removeSubnode:(ASDisplayNode *)subnode { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); // Don't call self.supernode here because that will retain/autorelease the supernode. This method -_removeSupernode: is often called while tearing down a node hierarchy, and the supernode in question might be in the middle of its -dealloc. The supernode is never messaged, only compared by value, so this is safe. // The particular issue that triggers this edge case is when a node calls -removeFromSupernode on a subnode from within its own -dealloc method. - if (!subnode || [subnode _deallocSafeSupernode] != self) + if (!subnode || [subnode _deallocSafeSupernode] != self) { return; + } [_subnodes removeObjectIdenticalTo:subnode]; @@ -1531,32 +1673,25 @@ static NSInteger incrementIfFound(NSInteger i) { - (void)removeFromSupernode { ASDisplayNodeAssertThreadAffinity(self); - _propertyLock.lock(); + __instanceLock__.lock(); __weak ASDisplayNode *supernode = _supernode; __weak UIView *view = _view; __weak CALayer *layer = _layer; BOOL layerBacked = _flags.layerBacked; - _propertyLock.unlock(); - - if (supernode == nil) { - return; - } + BOOL isNodeLoaded = (layer != nil || view != nil); + __instanceLock__.unlock(); + // Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView + // will trigger us to clear our _supernode pointer in willMoveToSuperview:nil. + // This may result in removing the last strong reference, triggering deallocation after this method. [supernode _removeSubnode:self]; - if (self.nodeLoaded && supernode.nodeLoaded) { - // Check to ensure that our view or layer is actually inside of our supernode; otherwise, don't remove it. - // Though _ASDisplayView decouples the supernode if it is inserted inside another view hierarchy, this is - // more difficult to guarantee with _ASDisplayLayer because CoreAnimation doesn't have a -didMoveToSuperlayer. + if (isNodeLoaded && (supernode == nil || supernode.isNodeLoaded)) { ASPerformBlockOnMainThread(^{ if (layerBacked || supernode.layerBacked) { - if (layer.superlayer == supernode.layer) { - [layer removeFromSuperlayer]; - } + [layer removeFromSuperlayer]; } else { - if (view.superview == supernode.view) { - [view removeFromSuperview]; - } + [view removeFromSuperview]; } }); } @@ -1565,19 +1700,19 @@ static NSInteger incrementIfFound(NSInteger i) { - (BOOL)__visibilityNotificationsDisabled { // Currently, this method is only used by the testing infrastructure to verify this internal feature. - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _flags.visibilityNotificationsDisabled > 0; } - (BOOL)__selfOrParentHasVisibilityNotificationsDisabled { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return (_hierarchyState & ASHierarchyStateTransitioningSupernodes); } - (void)__incrementVisibilityNotificationsDisabled { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); const size_t maxVisibilityIncrement = (1ULL< 0, @"Can't decrement past 0"); if (_flags.visibilityNotificationsDisabled > 0) { _flags.visibilityNotificationsDisabled--; @@ -1612,7 +1747,7 @@ static NSInteger incrementIfFound(NSInteger i) { ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy"); // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { _flags.isEnteringHierarchy = YES; @@ -1650,7 +1785,7 @@ static NSInteger incrementIfFound(NSInteger i) { ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy"); // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { _flags.isExitingHierarchy = YES; @@ -1701,20 +1836,20 @@ static NSInteger incrementIfFound(NSInteger i) { - (NSArray *)subnodes { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return [_subnodes copy]; } - (ASDisplayNode *)supernode { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _supernode; } // This is a thread-method to return the supernode without causing it to be retained autoreleased. See -_removeSubnode: for details. - (ASDisplayNode *)_deallocSafeSupernode { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _supernode; } @@ -1723,7 +1858,7 @@ static NSInteger incrementIfFound(NSInteger i) { BOOL supernodeDidChange = NO; ASDisplayNode *oldSupernode = nil; { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_supernode != newSupernode) { oldSupernode = _supernode; // Access supernode properties outside of lock to avoid remote chance of deadlock, // in case supernode implementation must access one of our properties. @@ -1745,6 +1880,28 @@ static NSInteger incrementIfFound(NSInteger i) { } if (newSupernode) { [self enterHierarchyState:stateToEnterOrExit]; + + // 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 + // is in fly + if (ASHierarchyStateIncludesLayoutPending(stateToEnterOrExit)) { + int32_t pendingTransitionId = newSupernode.pendingTransitionID; + if (pendingTransitionId != ASLayoutableContextInvalidTransitionID) { + { + ASDN::MutexLocker l(__instanceLock__); + _pendingTransitionID = pendingTransitionId; + + // Propagate down the new pending transition id + ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) { + node.pendingTransitionID = _pendingTransitionID; + }); + } + } + } + + // Now that we have a supernode, propagate its traits to self. + ASEnvironmentStatePropagateDown(self, [newSupernode environmentTraitCollection]); } else { // If a node will be removed from the supernode it should go out from the layout pending state to remove all // layout pending state related properties on the node @@ -1752,9 +1909,6 @@ static NSInteger incrementIfFound(NSInteger i) { [self exitHierarchyState:stateToEnterOrExit]; } - - // now that we have a supernode, propagate its traits to self. - ASEnvironmentStatePropagateDown(self, [newSupernode environmentTraitCollection]); } } @@ -1929,9 +2083,12 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) { __ASDisplayNodeCheckForLayoutMethodOverrides; - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if ((_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) || _layoutSpecBlock != NULL) { ASLayoutSpec *layoutSpec = [self layoutSpecThatFits:constrainedSize]; + + ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec); + layoutSpec.parent = self; // This causes upward propogation of any non-default layoutable values. // manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection @@ -1939,6 +2096,8 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) layoutSpec.isMutable = NO; ASLayout *layout = [layoutSpec measureWithSizeRange:constrainedSize]; + ASDisplayNodeAssertNotNil(layout, @"[ASLayoutSpec measureWithSizeRange:] should never return nil! %@, %@", self, layoutSpec); + // Make sure layoutableObject of the root layout is `self`, so that the flattened layout will be structurally correct. BOOL isFinalLayoutable = (layout.layoutableObject != self); if (isFinalLayoutable) { @@ -1963,7 +2122,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) { __ASDisplayNodeCheckForLayoutMethodOverrides; - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _preferredFrameSize; } @@ -1971,31 +2130,43 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) { __ASDisplayNodeCheckForLayoutMethodOverrides; - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_layoutSpecBlock != NULL) { return _layoutSpecBlock(self, constrainedSize); } - return nil; + ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never fall through to return empty value"); + return [[ASLayoutSpec alloc] init]; } - (ASLayout *)calculatedLayout { - ASDN::MutexLocker l(_propertyLock); - return _layout; + ASDN::MutexLocker l(__instanceLock__); + return _calculatedLayout; +} + +- (void)setCalculatedLayout:(ASLayout *)calculatedLayout +{ + ASDN::MutexLocker l(__instanceLock__); + + ASDisplayNodeAssertTrue(calculatedLayout.layoutableObject == self); + ASDisplayNodeAssertTrue(calculatedLayout.size.width >= 0.0); + ASDisplayNodeAssertTrue(calculatedLayout.size.height >= 0.0); + + _calculatedLayout = calculatedLayout; } - (CGSize)calculatedSize { - ASDN::MutexLocker l(_propertyLock); - return _layout.size; + ASDN::MutexLocker l(__instanceLock__); + return _calculatedLayout.size; } - (ASSizeRange)constrainedSizeForCalculatedLayout { - ASDN::MutexLocker l(_propertyLock); - return _layout.constrainedSizeRange; + ASDN::MutexLocker l(__instanceLock__); + return _calculatedLayout.constrainedSizeRange; } - (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock @@ -2008,36 +2179,42 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)setPendingTransitionID:(int32_t)pendingTransitionID { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssertTrue(_pendingTransitionID < pendingTransitionID); _pendingTransitionID = pendingTransitionID; } + +- (int32_t)pendingTransitionID +{ + ASDN::MutexLocker l(__instanceLock__); + return _pendingTransitionID; +} - (void)setPreferredFrameSize:(CGSize)preferredFrameSize { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (! CGSizeEqualToSize(_preferredFrameSize, preferredFrameSize)) { _preferredFrameSize = preferredFrameSize; - self.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(_preferredFrameSize), ASRelativeSizeMakeWithCGSize(_preferredFrameSize)); + self.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(_preferredFrameSize); [self invalidateCalculatedLayout]; } } - (CGSize)preferredFrameSize { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _preferredFrameSize; } - (CGRect)threadSafeBounds { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _threadSafeBounds; } - (void)setThreadSafeBounds:(CGRect)newBounds { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _threadSafeBounds = newBounds; } @@ -2048,16 +2225,16 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)invalidateCalculatedLayout { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); // This will cause the next call to -measureWithSizeRange: to actually compute a new layout // instead of returning the current layout - _layout.dirty = YES; + _calculatedLayout.dirty = YES; } - (void)__didLoad { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_nodeLoadedBlock) { _nodeLoadedBlock(self); _nodeLoadedBlock = nil; @@ -2102,7 +2279,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) if (ASInterfaceStateIncludesVisible(_interfaceState)) { dispatch_async(dispatch_get_main_queue(), ^{ // This block intentionally retains self. - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (!_flags.isInHierarchy && ASInterfaceStateIncludesVisible(_interfaceState)) { self.interfaceState = (_interfaceState & ~ASInterfaceStateVisible); } @@ -2193,7 +2370,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (ASInterfaceState)interfaceState { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _interfaceState; } @@ -2203,7 +2380,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); ASInterfaceState oldState = ASInterfaceStateNone; { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_interfaceState == newState) { return; } @@ -2248,7 +2425,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [self setDisplaySuspended:YES]; //schedule clear contents on next runloop dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { [self clearContents]; } @@ -2266,7 +2443,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [[self asyncLayer] cancelAsyncDisplay]; //schedule clear contents on next runloop dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { [self clearContents]; } @@ -2317,30 +2494,30 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) }); } -- (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState +- (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState { - ASInterfaceState oldState = self.interfaceState; - ASInterfaceState newState = interfaceState; + // Instead of each node in the recursion assuming it needs to schedule itself for display, + // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). + // If our range manager intends for us to be displayed right now, and didn't before, get started! + BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState]; ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { - node.interfaceState = interfaceState; + node.interfaceState = newInterfaceState; }); - - if ([self supportsRangeManagedInterfaceState]) { - // Instead of each node in the recursion assuming it needs to schedule itself for display, - // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). - // If our range manager intends for us to be displayed right now, and didn't before, get started! - - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState); - BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState); - if (nowDisplay && (nowDisplay != wasDisplay)) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } + if (shouldScheduleDisplay) { + [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; } } +- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState +{ + BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState); + BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState); + return willDisplay && (willDisplay != nowDisplay); +} + - (ASHierarchyState)hierarchyState { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _hierarchyState; } @@ -2348,7 +2525,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) { ASHierarchyState oldState = ASHierarchyStateNormal; { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_hierarchyState == newState) { return; } @@ -2375,7 +2552,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } else { // Leaving layout pending state, reset related properties { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _pendingTransitionID = ASLayoutableContextInvalidTransitionID; _pendingLayoutTransition = nil; } @@ -2407,28 +2584,40 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) }); } -- (void)applyPendingLayoutContext +- (void)_applyPendingLayoutContext { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_pendingLayoutTransition) { - [self applyLayout:_pendingLayoutTransition.pendingLayout layoutContext:_pendingLayoutTransition]; + [self _applyLayout:_pendingLayoutTransition.pendingLayout layoutTransition:_pendingLayoutTransition]; _pendingLayoutTransition = nil; } } -- (void)applyLayout:(ASLayout *)layout layoutContext:(ASLayoutTransition *)layoutContext +- (void)_applyLayout:(ASLayout *)layout layoutTransition:(ASLayoutTransition *)layoutTransition { - ASDN::MutexLocker l(_propertyLock); - _layout = layout; + ASDN::MutexLocker l(__instanceLock__); + _calculatedLayout = layout; ASDisplayNodeAssertTrue(layout.layoutableObject == self); ASDisplayNodeAssertTrue(layout.size.width >= 0.0); ASDisplayNodeAssertTrue(layout.size.height >= 0.0); - if (self.usesImplicitHierarchyManagement && layoutContext != nil) { - [layoutContext applySubnodeInsertions]; - [layoutContext applySubnodeRemovals]; + if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) { + return; } + + // Trampoline to the main thread if necessary + if (ASDisplayNodeThreadIsMain() == NO && layoutTransition.isSynchronous == NO) { + + // Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded + ASPerformBlockOnMainThread(^{ + [layoutTransition commitTransition]; + }); + + return; + } + + [layoutTransition commitTransition]; } - (void)layout @@ -2444,11 +2633,13 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)__layoutSublayouts { - for (ASLayout *subnodeLayout in _layout.sublayouts) { + for (ASLayout *subnodeLayout in _calculatedLayout.sublayouts) { ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame]; } } +#pragma mark - Display + - (void)displayWillStart { ASDisplayNodeAssertMainThread(); @@ -2480,7 +2671,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)setNeedsDisplayAtScale:(CGFloat)contentsScale { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (contentsScale != self.contentsScaleForDisplay) { self.contentsScaleForDisplay = contentsScale; [self setNeedsDisplay]; @@ -2541,14 +2732,14 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)setHitTestSlop:(UIEdgeInsets)hitTestSlop { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _hitTestSlop = hitTestSlop; } - (UIEdgeInsets)hitTestSlop { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _hitTestSlop; } @@ -2574,7 +2765,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) // If no view/layer properties were set before the view/layer were created, _pendingViewState will be nil and the default values // for the view/layer are still valid. - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); [self applyPendingViewState]; @@ -2652,7 +2843,7 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, - (BOOL)displaySuspended { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _flags.displaySuspended; } @@ -2664,7 +2855,7 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, if (_flags.synchronous) return; - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_flags.displaySuspended == flag) return; @@ -2688,14 +2879,14 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, - (BOOL)shouldAnimateSizeChanges { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _flags.shouldAnimateSizeChanges; } --(void)setShouldAnimateSizeChanges:(BOOL)shouldAnimateSizeChanges +- (void)setShouldAnimateSizeChanges:(BOOL)shouldAnimateSizeChanges { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _flags.shouldAnimateSizeChanges = shouldAnimateSizeChanges; } @@ -2704,7 +2895,7 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; - (void)setDrawingPriority:(NSInteger)drawingPriority { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (drawingPriority == ASDefaultDrawingPriority) { _flags.hasCustomDrawingPriority = NO; objc_setAssociatedObject(self, ASDisplayNodeDrawingPriorityKey, nil, OBJC_ASSOCIATION_ASSIGN); @@ -2714,10 +2905,10 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; } } --(NSInteger)drawingPriority +- (NSInteger)drawingPriority { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (!_flags.hasCustomDrawingPriority) return ASDefaultDrawingPriority; else @@ -2728,7 +2919,7 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _flags.isInHierarchy; } @@ -2736,7 +2927,7 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _flags.isInHierarchy = inHierarchy; } @@ -2755,7 +2946,12 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; - (void)setEnvironmentState:(ASEnvironmentState)environmentState { + ASEnvironmentTraitCollection oldTraitCollection = _environmentState.environmentTraitCollection; _environmentState = environmentState; + + if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(oldTraitCollection, _environmentState.environmentTraitCollection) == NO) { + [self asyncTraitCollectionDidChange]; + } } - (ASDisplayNode *)parent @@ -2785,7 +2981,10 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; - (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection { - _environmentState.environmentTraitCollection = environmentTraitCollection; + if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(environmentTraitCollection, _environmentState.environmentTraitCollection) == NO) { + _environmentState.environmentTraitCollection = environmentTraitCollection; + [self asyncTraitCollectionDidChange]; + } } ASEnvironmentLayoutOptionsForwarding @@ -2793,10 +2992,15 @@ ASEnvironmentLayoutExtensibilityForwarding - (ASTraitCollection *)asyncTraitCollection { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return [ASTraitCollection traitCollectionWithASEnvironmentTraitCollection:self.environmentTraitCollection]; } +- (void)asyncTraitCollectionDidChange +{ + +} + #if TARGET_OS_TV #pragma mark - UIFocusEnvironment Protocol (tvOS) diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index 46eb46daaf..19f199bd5b 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -80,8 +80,9 @@ extern void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^b block(node); // Add all subnodes to process in next step - for (int i = 0; i < node.subnodes.count; i++) - queue.push(node.subnodes[i]); + for (ASDisplayNode *subnode in node.subnodes) { + queue.push(subnode); + } } } @@ -144,7 +145,7 @@ static void _ASDisplayNodeFindAllSubnodes(NSMutableArray *array, ASDisplayNode * for (ASDisplayNode *subnode in node.subnodes) { if (block(subnode)) { - [array addObject:node]; + [array addObject:subnode]; } _ASDisplayNodeFindAllSubnodes(array, subnode, block); diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index 92e720f211..3274bb94ce 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @abstract Implements a node that supports text editing. @discussion Does not support layer backing. */ -@interface ASEditableTextNode : ASDisplayNode +@interface ASEditableTextNode : ASDisplayNode /** * @abstract Initializes an editable text node using default TextKit components. @@ -93,9 +93,16 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readwrite) UIEdgeInsets textContainerInset; /** - @abstract The returnKeyType of the keyboard. This value defaults to UIReturnKeyDefault. + @abstract properties. */ -@property (nonatomic, readwrite) UIReturnKeyType returnKeyType; +@property(nonatomic, readwrite, assign) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences +@property(nonatomic, readwrite, assign) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault +@property(nonatomic, readwrite, assign) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault; +@property(nonatomic, readwrite, assign) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault +@property(nonatomic, readwrite, assign) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault +@property(nonatomic, readwrite, assign) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum) +@property(nonatomic, readwrite, assign) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents) +@property(nonatomic, readwrite, assign, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO /** @abstract Indicates whether the receiver's text view is the first responder, and thus has the keyboard visible and is prepared for editing by the user. @@ -120,6 +127,14 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ASEditableTextNode (Unavailable) + +- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; + +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; + +@end + #pragma mark - /** * The methods declared by the ASEditableTextNodeDelegate protocol allow the adopting delegate to diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index fda9fd6e52..573323fda6 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -15,7 +15,42 @@ #import "ASDisplayNode+Subclasses.h" #import "ASEqualityHelpers.h" #import "ASTextNodeWordKerner.h" -#import "ASThread.h" + +/** + @abstract Object to hold UITextView's pending UITextInputTraits +**/ +@interface _ASTextInputTraitsPendingState : NSObject + +@property (nonatomic, readwrite, assign) UITextAutocapitalizationType autocapitalizationType; +@property (nonatomic, readwrite, assign) UITextAutocorrectionType autocorrectionType; +@property (nonatomic, readwrite, assign) UITextSpellCheckingType spellCheckingType; +@property (nonatomic, readwrite, assign) UIKeyboardAppearance keyboardAppearance; +@property (nonatomic, readwrite, assign) UIKeyboardType keyboardType; +@property (nonatomic, readwrite, assign) UIReturnKeyType returnKeyType; +@property (nonatomic, readwrite, assign) BOOL enablesReturnKeyAutomatically; +@property (nonatomic, readwrite, assign, getter=isSecureTextEntry) BOOL secureTextEntry; + +@end + +@implementation _ASTextInputTraitsPendingState + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + // set default values, as defined in Apple's comments in UITextInputTraits.h + _autocapitalizationType = UITextAutocapitalizationTypeSentences; + _autocorrectionType = UITextAutocorrectionTypeDefault; + _spellCheckingType = UITextSpellCheckingTypeDefault; + _keyboardAppearance = UIKeyboardAppearanceDefault; + _keyboardType = UIKeyboardTypeDefault; + _returnKeyType = UIReturnKeyDefault; + + return self; +} + +@end /** @abstract As originally reported in rdar://14729288, when scrollEnabled = NO, @@ -85,6 +120,10 @@ ASTextKitComponents *_placeholderTextKitComponents; // Forwards NSLayoutManagerDelegate methods related to word kerning ASTextNodeWordKerner *_wordKerner; + + // UITextInputTraits + ASDN::RecursiveMutex _textInputTraitsLock; + _ASTextInputTraitsPendingState *_textInputTraits; // Misc. State. BOOL _displayingPlaceholder; // Defaults to YES. @@ -93,6 +132,8 @@ NSRange _previousSelectedRange; } +@property (nonatomic, strong, readonly) _ASTextInputTraitsPendingState *textInputTraits; + @end @implementation ASEditableTextNode @@ -117,28 +158,15 @@ _textKitComponents = textKitComponents; _textKitComponents.layoutManager.delegate = self; _wordKerner = [[ASTextNodeWordKerner alloc] init]; - _returnKeyType = UIReturnKeyDefault; _textContainerInset = UIEdgeInsetsZero; // Create the placeholder scaffolding. _placeholderTextKitComponents = placeholderTextKitComponents; _placeholderTextKitComponents.layoutManager.delegate = self; - + return self; } -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - - (void)dealloc { _textKitComponents.textView.delegate = nil; @@ -151,8 +179,6 @@ { [super didLoad]; - ASDN::MutexLocker l(_textKitLock); - void (^configureTextView)(UITextView *) = ^(UITextView *textView) { if (!_displayingPlaceholder || textView != _textKitComponents.textView) { // If showing the placeholder, don't propagate backgroundColor/opaque to the editable textView. It is positioned over the placeholder to accept taps to begin editing, and if it's opaque/colored then it'll obscure the placeholder. @@ -164,28 +190,48 @@ textView.opaque = NO; } textView.textContainerInset = self.textContainerInset; + + // Configure textView with UITextInputTraits + { + ASDN::MutexLocker l(_textInputTraitsLock); + if (_textInputTraits) { + textView.autocapitalizationType = _textInputTraits.autocapitalizationType; + textView.autocorrectionType = _textInputTraits.autocorrectionType; + textView.spellCheckingType = _textInputTraits.spellCheckingType; + textView.keyboardType = _textInputTraits.keyboardType; + textView.keyboardAppearance = _textInputTraits.keyboardAppearance; + textView.returnKeyType = _textInputTraits.returnKeyType; + textView.enablesReturnKeyAutomatically = _textInputTraits.enablesReturnKeyAutomatically; + textView.secureTextEntry = _textInputTraits.isSecureTextEntry; + } + } + + [self.view addSubview:textView]; }; + ASDN::MutexLocker l(_textKitLock); + // Create and configure the placeholder text view. _placeholderTextKitComponents.textView = [[UITextView alloc] initWithFrame:CGRectZero textContainer:_placeholderTextKitComponents.textContainer]; _placeholderTextKitComponents.textView.userInteractionEnabled = NO; _placeholderTextKitComponents.textView.accessibilityElementsHidden = YES; configureTextView(_placeholderTextKitComponents.textView); - [self.view addSubview:_placeholderTextKitComponents.textView]; // Create and configure our text view. - _textKitComponents.textView = self.textView; + _textKitComponents.textView = [[ASPanningOverriddenUITextView alloc] initWithFrame:CGRectZero textContainer:_textKitComponents.textContainer]; _textKitComponents.textView.scrollEnabled = _scrollEnabled; _textKitComponents.textView.delegate = self; #if TARGET_OS_IOS _textKitComponents.textView.editable = YES; #endif _textKitComponents.textView.typingAttributes = _typingAttributes; - _textKitComponents.textView.returnKeyType = _returnKeyType; _textKitComponents.textView.accessibilityHint = _placeholderTextKitComponents.textStorage.string; configureTextView(_textKitComponents.textView); - [self.view addSubview:_textKitComponents.textView]; + [self _updateDisplayingPlaceholder]; + + // once view is loaded, setters set directly on view + _textInputTraits = nil; } - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize @@ -261,9 +307,8 @@ - (UITextView *)textView { ASDisplayNodeAssertMainThread(); - if (!_textKitComponents.textView) { - _textKitComponents.textView = [[ASPanningOverriddenUITextView alloc] initWithFrame:CGRectZero textContainer:_textKitComponents.textContainer]; - } + [self view]; + ASDisplayNodeAssert(_textKitComponents.textView != nil, @"UITextView must be created in -[ASEditableTextNode didLoad]"); return _textKitComponents.textView; } @@ -425,13 +470,6 @@ return [_textKitComponents.textView textInputMode]; } -- (void)setReturnKeyType:(UIReturnKeyType)returnKeyType -{ - ASDN::MutexLocker l(_textKitLock); - _returnKeyType = returnKeyType; - [_textKitComponents.textView setReturnKeyType:_returnKeyType]; -} - - (BOOL)isFirstResponder { ASDN::MutexLocker l(_textKitLock); @@ -460,6 +498,176 @@ return [_textKitComponents.textView resignFirstResponder]; } +#pragma mark - UITextInputTraits + +- (_ASTextInputTraitsPendingState *)textInputTraits +{ + if (!_textInputTraits) { + _textInputTraits = [[_ASTextInputTraitsPendingState alloc] init]; + } + return _textInputTraits; +} + +- (void)setAutocapitalizationType:(UITextAutocapitalizationType)autocapitalizationType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + [self.textView setAutocapitalizationType:autocapitalizationType]; + } else { + [self.textInputTraits setAutocapitalizationType:autocapitalizationType]; + } +} + +- (UITextAutocapitalizationType)autocapitalizationType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + return [self.textView autocapitalizationType]; + } else { + return [self.textInputTraits autocapitalizationType]; + } +} + +- (void)setAutocorrectionType:(UITextAutocorrectionType)autocorrectionType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + [self.textView setAutocorrectionType:autocorrectionType]; + } else { + [self.textInputTraits setAutocorrectionType:autocorrectionType]; + } +} + +- (UITextAutocorrectionType)autocorrectionType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + return [self.textView autocorrectionType]; + } else { + return [self.textInputTraits autocorrectionType]; + } +} + +- (void)setSpellCheckingType:(UITextSpellCheckingType)spellCheckingType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + [self.textView setSpellCheckingType:spellCheckingType]; + } else { + [self.textInputTraits setSpellCheckingType:spellCheckingType]; + } +} + +- (UITextSpellCheckingType)spellCheckingType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + return [self.textView spellCheckingType]; + } else { + return [self.textInputTraits spellCheckingType]; + } +} + +- (void)setEnablesReturnKeyAutomatically:(BOOL)enablesReturnKeyAutomatically +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + [self.textView setEnablesReturnKeyAutomatically:enablesReturnKeyAutomatically]; + } else { + [self.textInputTraits setEnablesReturnKeyAutomatically:enablesReturnKeyAutomatically]; + } +} + +- (BOOL)enablesReturnKeyAutomatically +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + return [self.textView enablesReturnKeyAutomatically]; + } else { + return [self.textInputTraits enablesReturnKeyAutomatically]; + } +} + +- (void)setKeyboardAppearance:(UIKeyboardAppearance)setKeyboardAppearance +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + [self.textView setKeyboardAppearance:setKeyboardAppearance]; + } else { + [self.textInputTraits setKeyboardAppearance:setKeyboardAppearance]; + } +} + +- (UIKeyboardAppearance)keyboardAppearance +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + return [self.textView keyboardAppearance]; + } else { + return [self.textInputTraits keyboardAppearance]; + } +} + +- (void)setKeyboardType:(UIKeyboardType)keyboardType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + [self.textView setKeyboardType:keyboardType]; + } else { + [self.textInputTraits setKeyboardType:keyboardType]; + } +} + +- (UIKeyboardType)keyboardType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + return [self.textView keyboardType]; + } else { + return [self.textInputTraits keyboardType]; + } +} + +- (void)setReturnKeyType:(UIReturnKeyType)returnKeyType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + [self.textView setReturnKeyType:returnKeyType]; + } else { + [self.textInputTraits setReturnKeyType:returnKeyType]; + } +} + +- (UIReturnKeyType)returnKeyType +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + return [self.textView returnKeyType]; + } else { + return [self.textInputTraits returnKeyType]; + } +} + +- (void)setSecureTextEntry:(BOOL)secureTextEntry +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + [self.textView setSecureTextEntry:secureTextEntry]; + } else { + [self.textInputTraits setSecureTextEntry:secureTextEntry]; + } +} + +- (BOOL)isSecureTextEntry +{ + ASDN::MutexLocker l(_textInputTraitsLock); + if (self.isNodeLoaded) { + return [self.textView isSecureTextEntry]; + } else { + return [self.textInputTraits isSecureTextEntry]; + } +} + #pragma mark - UITextView Delegate - (void)textViewDidBeginEditing:(UITextView *)textView { diff --git a/AsyncDisplayKit/ASImageNode+AnimatedImage.mm b/AsyncDisplayKit/ASImageNode+AnimatedImage.mm index e51305a298..d64cddfb1b 100644 --- a/AsyncDisplayKit/ASImageNode+AnimatedImage.mm +++ b/AsyncDisplayKit/ASImageNode+AnimatedImage.mm @@ -13,11 +13,9 @@ #import "ASImageNode.h" #import "ASAssert.h" -#import "ASImageProtocols.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNodeExtras.h" #import "ASEqualityHelpers.h" -#import "ASDisplayNode+FrameworkPrivate.h" #import "ASImageNode+AnimatedImagePrivate.h" #import "ASInternalHelpers.h" #import "ASWeakProxy.h" @@ -233,7 +231,11 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes; return frameIndex; } -- (void)dealloc +@end + +@implementation ASImageNode(AnimatedImageInvalidation) + +- (void)invalidateAnimatedImage { ASDN::MutexLocker l(_displayLinkLock); #if ASAnimatedImageDebug diff --git a/AsyncDisplayKit/ASImageNode.h b/AsyncDisplayKit/ASImageNode.h index db581f6441..9f33119e6c 100644 --- a/AsyncDisplayKit/ASImageNode.h +++ b/AsyncDisplayKit/ASImageNode.h @@ -37,7 +37,7 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); * the layer's contentsCenter property. Non-stretchable images work too, of * course. */ -@property (nullable, atomic, strong) UIImage *image; +@property (nullable, nonatomic, strong) UIImage *image; /** @abstract The placeholder color. @@ -135,7 +135,7 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); * @discussion Set this to an object which conforms to ASAnimatedImageProtocol * to have the ASImageNode playback an animated image. */ -@property (nullable, atomic, strong) id animatedImage; +@property (nullable, nonatomic, strong) id animatedImage; /** * @abstract Pause the playback of an animated image. @@ -143,7 +143,7 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); * @discussion Set to YES to pause playback of an animated image and NO to resume * playback. */ -@property (atomic, assign) BOOL animatedImagePaused; +@property (nonatomic, assign) BOOL animatedImagePaused; /** * @abstract The runloop mode used to animate the image. @@ -152,10 +152,17 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); * Setting NSDefaultRunLoopMode will cause animation to pause while scrolling (if the ASImageNode is * in a scroll view), which may improve scroll performance in some use cases. */ -@property (atomic, strong) NSString *animatedImageRunLoopMode; +@property (nonatomic, strong) NSString *animatedImageRunLoopMode; @end +@interface ASImageNode (Unavailable) + +- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; + +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; + +@end ASDISPLAYNODE_EXTERN_C_BEGIN diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index bb7762d285..f4302be39a 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -10,21 +10,24 @@ #import "ASImageNode.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import +#import "_ASDisplayLayer.h" +#import "ASAssert.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNodeInternal.h" +#import "ASDisplayNodeExtras.h" +#import "ASDisplayNode+Beta.h" +#import "ASTextNode.h" +#import "ASImageNode+AnimatedImagePrivate.h" #import "ASImageNode+CGExtras.h" #import "AsyncDisplayKit+Debug.h" #import "ASInternalHelpers.h" #import "ASEqualityHelpers.h" +#import "ASEqualityHashHelpers.h" +#import "ASWeakMap.h" + +#include struct ASImageNodeDrawParameters { BOOL opaque; @@ -39,13 +42,83 @@ struct ASImageNodeDrawParameters { asimagenode_modification_block_t imageModificationBlock; }; +/** + * Contains all data that is needed to generate the content bitmap. + */ +@interface ASImageNodeContentsKey : NSObject {} + +@property (nonatomic, strong) UIImage *image; +@property CGSize backingSize; +@property CGRect imageDrawRect; +@property BOOL isOpaque; +@property (nonatomic, strong) UIColor *backgroundColor; +@property ASDisplayNodeContextModifier preContextBlock; +@property ASDisplayNodeContextModifier postContextBlock; +@property asimagenode_modification_block_t imageModificationBlock; + +@end + +@implementation ASImageNodeContentsKey + +- (BOOL)isEqual:(id)object +{ + if (self == object) { + return YES; + } + + // Optimization opportunity: The `isKindOfClass` call here could be avoided by not using the NSObject `isEqual:` + // convention and instead using a custom comparison function that assumes all items are heterogeneous. + // However, profiling shows that our entire `isKindOfClass` expression is only ~1/40th of the total + // overheard of our caching, so it's likely not high-impact. + if ([object isKindOfClass:[ASImageNodeContentsKey class]]) { + ASImageNodeContentsKey *other = (ASImageNodeContentsKey *)object; + return [_image isEqual:other.image] + && CGSizeEqualToSize(_backingSize, other.backingSize) + && CGRectEqualToRect(_imageDrawRect, other.imageDrawRect) + && _isOpaque == other.isOpaque + && [_backgroundColor isEqual:other.backgroundColor] + && _preContextBlock == other.preContextBlock + && _postContextBlock == other.postContextBlock + && _imageModificationBlock == other.imageModificationBlock; + } else { + return NO; + } +} + +- (NSUInteger)hash +{ + NSUInteger subhashes[] = { + // Profiling shows that the work done in UIImage's `hash` is on the order of 0.005ms on an A5 processor + // and isn't proportional to the size of the image. + [_image hash], + + // TODO: Hashing the floats in a CGRect or CGSize is tricky. Equality of floats is + // fuzzy, but it's a 100% requirement that two equal values must produce an identical hash value. + // Until there's a robust solution for hashing floats, leave all float values out of the hash. + // This may lead to a greater number of isEqual comparisons but does not comprimise correctness. + //AS::hash()(_backingSize), + //AS::hash()(_imageDrawRect), + + AS::hash()(_isOpaque), + [_backgroundColor hash], + AS::hash()((void*)_preContextBlock), + AS::hash()((void*)_postContextBlock), + AS::hash()((void*)_imageModificationBlock), + }; + return ASIntegerArrayHash(subhashes, sizeof(subhashes) / sizeof(subhashes[0])); +} + +@end + + @implementation ASImageNode { @private UIImage *_image; + ASWeakMapEntry *_weakCacheEntry; // Holds a reference that keeps our contents in cache. + void (^_displayCompletionBlock)(BOOL canceled); - ASDN::RecursiveMutex _imageLock; // Drawing ASImageNodeDrawParameters _drawParameter; @@ -98,23 +171,17 @@ struct ASImageNodeDrawParameters { return self; } -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock +- (void)dealloc { - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; + // Invalidate all components around animated images + [self invalidateAnimatedImage]; } #pragma mark - Layout and Sizing - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); // if a preferredFrameSize is set, call the superclass to return that instead of using the image size. if (CGSizeEqualToSize(self.preferredFrameSize, CGSizeZero) == NO) return [super calculateSizeThatFits:constrainedSize]; @@ -128,11 +195,9 @@ struct ASImageNodeDrawParameters { - (void)setImage:(UIImage *)image { - _imageLock.lock(); + ASDN::MutexLocker l(__instanceLock__); if (!ASObjectIsEqual(_image, image)) { _image = image; - - _imageLock.unlock(); [self invalidateCalculatedLayout]; if (image) { @@ -148,14 +213,12 @@ struct ASImageNodeDrawParameters { } else { self.contents = nil; } - } else { - _imageLock.unlock(); // We avoid using MutexUnlocker as it needlessly re-locks at the end of the scope. } } - (UIImage *)image { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); return _image; } @@ -171,7 +234,7 @@ struct ASImageNodeDrawParameters { - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); _drawParameter = { .bounds = self.bounds, @@ -215,8 +278,8 @@ struct ASImageNodeDrawParameters { CGRect cropRect = CGRectZero; asimagenode_modification_block_t imageModificationBlock; - ASDN::MutexLocker l(_imageLock); { + ASDN::MutexLocker l(__instanceLock__); ASImageNodeDrawParameters drawParameter = _drawParameter; drawParameterBounds = drawParameter.bounds; @@ -299,25 +362,85 @@ struct ASImageNodeDrawParameters { imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) { return nil; } - + + ASImageNodeContentsKey *contentsKey = [[ASImageNodeContentsKey alloc] init]; + contentsKey.image = image; + contentsKey.backingSize = backingSize; + contentsKey.imageDrawRect = imageDrawRect; + contentsKey.isOpaque = isOpaque; + contentsKey.backgroundColor = backgroundColor; + contentsKey.preContextBlock = preContextBlock; + contentsKey.postContextBlock = postContextBlock; + contentsKey.imageModificationBlock = imageModificationBlock; + + if (isCancelled()) { + return nil; + } + + ASWeakMapEntry *entry = [self.class contentsForkey:contentsKey isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled]; + if (entry == nil) { // If nil, we were cancelled. + return nil; + } + _weakCacheEntry = entry; // Retain so that the entry remains in the weak cache + return entry.value; +} + +static ASWeakMap *cache = nil; +static ASDN::Mutex cacheLock; + ++ (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled +{ + { + ASDN::MutexLocker l(cacheLock); + if (!cache) { + cache = [[ASWeakMap alloc] init]; + } + ASWeakMapEntry *entry = [cache entryForKey:key]; + if (entry != nil) { + // cache hit + return entry; + } + } + + // cache miss + UIImage *contents = [self createContentsForkey:key isCancelled:isCancelled]; + if (contents == nil) { // If nil, we were cancelled + return nil; + } + + { + ASDN::MutexLocker l(cacheLock); + return [cache setObject:contents forKey:key]; + } +} + ++ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled +{ + // The following `UIGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an + // A5 processor for a 400x800 backingSize. + // Check for cancellation before we call it. + if (isCancelled()) { + return nil; + } + // Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds // will do its rounding on pixel instead of point boundaries - UIGraphicsBeginImageContextWithOptions(backingSize, isOpaque, 1.0); + UIGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0); CGContextRef context = UIGraphicsGetCurrentContext(); - if (context && preContextBlock) { - preContextBlock(context); + if (context && key.preContextBlock) { + key.preContextBlock(context); } // if view is opaque, fill the context with background color - if (isOpaque && backgroundColor) { - [backgroundColor setFill]; - UIRectFill({ .size = backingSize }); + if (key.isOpaque && key.backgroundColor) { + [key.backgroundColor setFill]; + UIRectFill({ .size = key.backingSize }); } // iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on // multiple threads concurrently. In fact, instead of crashing, it appears to deadlock. - // The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier, + // The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premiere, // as well as iOS games, and a small number of ASDK apps that provide the same image reference // to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes // that may get the same pointer for a given UI asset image, etc. @@ -327,25 +450,27 @@ struct ASImageNodeDrawParameters { // Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer. // Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068 - @synchronized(image) { - [image drawInRect:imageDrawRect]; + @synchronized(key.image) { + [key.image drawInRect:key.imageDrawRect]; } - if (context && postContextBlock) { - postContextBlock(context); + if (context && key.postContextBlock) { + key.postContextBlock(context); } - + + // The following `UIGraphicsGetImageFromCurrentImageContext` call will commonly take more than 20ms on an + // A5 processor. Check for cancellation before we call it. if (isCancelled()) { UIGraphicsEndImageContext(); return nil; } - + UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - if (imageModificationBlock != NULL) { - result = imageModificationBlock(result); + if (key.imageModificationBlock != NULL) { + result = key.imageModificationBlock(result); } return result; @@ -355,19 +480,19 @@ struct ASImageNodeDrawParameters { { [super displayDidFinish]; - _imageLock.lock(); + __instanceLock__.lock(); void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock; UIImage *image = _image; - _imageLock.unlock(); + __instanceLock__.unlock(); // If we've got a block to perform after displaying, do it. if (image && displayCompletionBlock) { displayCompletionBlock(NO); - _imageLock.lock(); + __instanceLock__.lock(); _displayCompletionBlock = nil; - _imageLock.unlock(); + __instanceLock__.unlock(); } } @@ -380,7 +505,7 @@ struct ASImageNodeDrawParameters { } // Stash the block and call-site queue. We'll invoke it in -displayDidFinish. - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); if (_displayCompletionBlock != displayCompletionBlock) { _displayCompletionBlock = [displayCompletionBlock copy]; } @@ -388,11 +513,20 @@ struct ASImageNodeDrawParameters { [self setNeedsDisplay]; } +#pragma mark Interface State + +- (void)clearContents +{ + [super clearContents]; + + _weakCacheEntry = nil; // release contents from the cache. +} + #pragma mark - Cropping - (BOOL)isCropEnabled { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); return _cropEnabled; } @@ -403,7 +537,7 @@ struct ASImageNodeDrawParameters { - (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); if (_cropEnabled == cropEnabled) return; @@ -424,13 +558,13 @@ struct ASImageNodeDrawParameters { - (CGRect)cropRect { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); return _cropRect; } - (void)setCropRect:(CGRect)cropRect { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); if (CGRectEqualToRect(_cropRect, cropRect)) return; @@ -451,25 +585,25 @@ struct ASImageNodeDrawParameters { - (BOOL)forceUpscaling { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); return _forceUpscaling; } - (void)setForceUpscaling:(BOOL)forceUpscaling { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); _forceUpscaling = forceUpscaling; } - (asimagenode_modification_block_t)imageModificationBlock { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); return _imageModificationBlock; } - (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock { - ASDN::MutexLocker l(_imageLock); + ASDN::MutexLocker l(__instanceLock__); _imageModificationBlock = imageModificationBlock; } diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index f7b7c6c014..80d63d4bb0 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -14,6 +14,16 @@ NS_ASSUME_NONNULL_BEGIN +typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) +{ + /** The annotations' positions are ignored, use the region or options specified instead. */ + ASMapNodeShowAnnotationsOptionsIgnored = 0, + /** The annotations' positions are used to calculate the region to show in the map, equivalent to showAnnotations:animated. */ + ASMapNodeShowAnnotationsOptionsZoomed = 1 << 0, + /** This will only have an effect if combined with the Zoomed state with liveMap turned on.*/ + ASMapNodeShowAnnotationsOptionsAnimated = 1 << 1 +}; + @interface ASMapNode : ASImageNode /** @@ -54,6 +64,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, copy) NSArray> *annotations; +/** + * @abstract This property specifies how to show the annotations. + * @default Default value is ASMapNodeShowAnnotationsIgnored + */ +@property (nonatomic, assign) ASMapNodeShowAnnotationsOptions showAnnotationsOptions; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index c8c2599b38..c0d7fb9a25 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -10,21 +10,18 @@ #if TARGET_OS_IOS #import "ASMapNode.h" -#import -#import -#import -#import -#import -#import -#import +#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNodeExtras.h" +#import "ASInsetLayoutSpec.h" +#import "ASInternalHelpers.h" +#import "ASLayout.h" @interface ASMapNode() { - ASDN::RecursiveMutex _propertyLock; MKMapSnapshotter *_snapshotter; BOOL _snapshotAfterLayout; NSArray *_annotations; - CLLocationCoordinate2D _centerCoordinateOfMap; } @end @@ -34,6 +31,7 @@ @synthesize mapDelegate = _mapDelegate; @synthesize options = _options; @synthesize liveMap = _liveMap; +@synthesize showAnnotationsOptions = _showAnnotationsOptions; #pragma mark - Lifecycle - (instancetype)init @@ -43,11 +41,12 @@ } self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); self.clipsToBounds = YES; + self.userInteractionEnabled = YES; _needsMapReloadOnBoundsChange = YES; _liveMap = NO; - _centerCoordinateOfMap = kCLLocationCoordinate2DInvalid; _annotations = @[]; + _showAnnotationsOptions = ASMapNodeShowAnnotationsOptionsIgnored; return self; } @@ -55,7 +54,6 @@ { [super didLoad]; if (self.isLiveMap) { - self.userInteractionEnabled = YES; [self addLiveMap]; } } @@ -97,14 +95,14 @@ - (BOOL)isLiveMap { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _liveMap; } - (void)setLiveMap:(BOOL)liveMap { ASDisplayNodeAssert(!self.isLayerBacked, @"ASMapNode can not use the interactive map feature whilst .isLayerBacked = YES, set .layerBacked = NO to use the interactive map feature."); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (liveMap == _liveMap) { return; } @@ -116,19 +114,19 @@ - (BOOL)needsMapReloadOnBoundsChange { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _needsMapReloadOnBoundsChange; } - (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; } - (MKMapSnapshotOptions *)options { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (!_options) { _options = [[MKMapSnapshotOptions alloc] init]; _options.region = MKCoordinateRegionForMapRect(MKMapRectWorld); @@ -142,7 +140,7 @@ - (void)setOptions:(MKMapSnapshotOptions *)options { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (!_options || ![options isEqual:_options]) { _options = options; if (self.isLiveMap) { @@ -161,7 +159,9 @@ - (void)setRegion:(MKCoordinateRegion)region { - self.options.region = region; + MKMapSnapshotOptions * options = [self.options copy]; + options.region = region; + self.options = options; } #pragma mark - Snapshotter @@ -265,24 +265,24 @@ [_mapView addAnnotations:_annotations]; [weakSelf setNeedsLayout]; [weakSelf.view addSubview:_mapView]; - - if (CLLocationCoordinate2DIsValid(_centerCoordinateOfMap)) { - [_mapView setCenterCoordinate:_centerCoordinateOfMap]; + + ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; + if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { + BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; + [_mapView showAnnotations:_mapView.annotations animated:animated]; } } } - (void)removeLiveMap { - // FIXME: With MKCoordinateRegion, isn't the center coordinate fully specified? Do we need this? - _centerCoordinateOfMap = _mapView.centerCoordinate; [_mapView removeFromSuperview]; _mapView = nil; } - (NSArray *)annotations { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _annotations; } @@ -290,16 +290,60 @@ { annotations = [annotations copy] ? : @[]; - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _annotations = annotations; + ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; if (self.isLiveMap) { [_mapView removeAnnotations:_mapView.annotations]; [_mapView addAnnotations:annotations]; + + if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { + BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; + [_mapView showAnnotations:_mapView.annotations animated:animated]; + } } else { - [self takeSnapshot]; + if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { + self.region = [self regionToFitAnnotations:annotations]; + } + else { + [self takeSnapshot]; + } } } +-(MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotations +{ + if([annotations count] == 0) + return MKCoordinateRegionForMapRect(MKMapRectWorld); + + CLLocationCoordinate2D topLeftCoord = CLLocationCoordinate2DMake(-90, 180); + 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)); + } + + 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)); + + return region; +} + +-(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { + ASDN::MutexLocker l(__instanceLock__); + return _showAnnotationsOptions; +} + +-(void)setShowAnnotationsOptions:(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { + ASDN::MutexLocker l(__instanceLock__); + _showAnnotationsOptions = showAnnotationsOptions; +} + #pragma mark - Layout - (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize { diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index 19145359ba..6d1e2a6c86 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -13,11 +13,7 @@ #import "ASMultiplexImageNode.h" #import -#import -#import - #import "ASAvailability.h" -#import "ASBaseDefines.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASLog.h" @@ -88,7 +84,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent id _downloadIdentifier; // Properties - ASDN::RecursiveMutex _propertyLock; + ASDN::RecursiveMutex __instanceLock__; BOOL _shouldRenderProgressImages; //set on init only @@ -350,7 +346,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent - (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (shouldRenderProgressImages == _shouldRenderProgressImages) { return; } @@ -358,13 +354,13 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _shouldRenderProgressImages = shouldRenderProgressImages; - ASDN::MutexUnlocker u(_propertyLock); + ASDN::MutexUnlocker u(__instanceLock__); [self _updateProgressImageBlockOnDownloaderIfNeeded]; } - (BOOL)shouldRenderProgressImages { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _shouldRenderProgressImages; } diff --git a/AsyncDisplayKit/ASNavigationController.m b/AsyncDisplayKit/ASNavigationController.m index bf97b765f5..79ae3f4232 100644 --- a/AsyncDisplayKit/ASNavigationController.m +++ b/AsyncDisplayKit/ASNavigationController.m @@ -39,8 +39,11 @@ ASVisibilityDepthImplementation; - (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController { - NSUInteger viewControllerIndex = [self.viewControllers indexOfObject:childViewController]; - NSAssert(viewControllerIndex != NSNotFound, @"childViewController is not in the navigation stack."); + NSUInteger viewControllerIndex = [self.viewControllers indexOfObjectIdenticalTo:childViewController]; + if (viewControllerIndex == NSNotFound) { + //If childViewController is not actually a child, return NSNotFound which is also a really large number. + return NSNotFound; + } if (viewControllerIndex == self.viewControllers.count - 1) { //view controller is at the top, just return our own visibility depth. diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/AsyncDisplayKit/ASNetworkImageNode.h index 1998d984bb..c0d2a3e6d9 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/AsyncDisplayKit/ASNetworkImageNode.h @@ -47,19 +47,19 @@ NS_ASSUME_NONNULL_BEGIN /** * The delegate, which must conform to the protocol. */ -@property (nullable, atomic, weak, readwrite) id delegate; +@property (nullable, nonatomic, weak, readwrite) id delegate; /** * A placeholder image to display while the URL is loading. */ -@property (nullable, atomic, strong, readwrite) UIImage *defaultImage; +@property (nullable, nonatomic, strong, readwrite) UIImage *defaultImage; /** * The URL of a new image to download and display. * * @discussion Changing this property will reset the displayed image to a placeholder () while loading. */ -@property (nullable, atomic, strong, readwrite) NSURL *URL; +@property (nullable, nonatomic, strong, readwrite) NSURL *URL; /** * Download and display a new image. diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index fbfea44213..432c50b5ff 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -11,10 +11,10 @@ #import "ASNetworkImageNode.h" #import "ASBasicImageDownloader.h" +#import "ASDisplayNodeInternal.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASEqualityHelpers.h" -#import "ASThread.h" #import "ASInternalHelpers.h" #import "ASImageContainerProtocolCategories.h" #import "ASDisplayNodeExtras.h" @@ -27,11 +27,10 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; @interface ASNetworkImageNode () { - ASDN::RecursiveMutex _lock; __weak id _cache; __weak id _downloader; - // Only access any of these with _lock. + // Only access any of these with __instanceLock__. __weak id _delegate; NSURL *_URL; @@ -43,24 +42,28 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; BOOL _imageLoaded; CGFloat _currentImageQuality; CGFloat _renderedImageQuality; - - // TODO: Move this to flags - BOOL _delegateSupportsDidStartFetchingData; - BOOL _delegateSupportsDidFailWithError; - BOOL _delegateSupportsDidFinishDecoding; - BOOL _delegateSupportsDidLoadImage; - BOOL _shouldRenderProgressImages; - //set on init only - BOOL _downloaderSupportsNewProtocol; - BOOL _downloaderImplementsSetProgress; - BOOL _downloaderImplementsSetPriority; - BOOL _downloaderImplementsAnimatedImage; + struct { + unsigned int delegateDidStartFetchingData:1; + unsigned int delegateDidFailWithError:1; + unsigned int delegateDidFinishDecoding:1; + unsigned int delegateDidLoadImage:1; + } _delegateFlags; - BOOL _cacheSupportsNewProtocol; - BOOL _cacheSupportsClearing; - BOOL _cacheSupportsSynchronousFetch; + //set on init only + struct { + unsigned int downloaderSupportsNewProtocol:1; + unsigned int downloaderImplementsSetProgress:1; + unsigned int downloaderImplementsSetPriority:1; + unsigned int downloaderImplementsAnimatedImage:1; + } _downloaderFlags; + + struct { + unsigned int cacheSupportsNewProtocol:1; + unsigned int cacheSupportsClearing:1; + unsigned int cacheSupportsSynchronousFetch:1; + } _cacheFlags; } @end @@ -76,17 +79,17 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:."); - _downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)]; + _downloaderFlags.downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)]; ASDisplayNodeAssert(cache == nil || [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:"); - _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; - _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - _downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; + _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; + _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; + _downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; - _cacheSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)]; - _cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; - _cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; + _cacheFlags.cacheSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)]; + _cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; + _cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; _shouldCacheImage = YES; _shouldRenderProgressImages = YES; @@ -118,7 +121,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); if (ASObjectIsEqual(URL, _URL)) { return; @@ -147,16 +150,15 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (NSURL *)URL { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); return _URL; } - (void)setDefaultImage:(UIImage *)defaultImage { - _lock.lock(); + ASDN::MutexLocker l(__instanceLock__); if (ASObjectIsEqual(defaultImage, _defaultImage)) { - _lock.unlock(); return; } _defaultImage = defaultImage; @@ -169,66 +171,60 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; dispatch_async(dispatch_get_main_queue(), ^{ self.currentImageQuality = hasURL ? 0.0 : 1.0; }); - _lock.unlock(); - // Locking: it is important to release _lock before entering setImage:, as it needs to release the lock before -invalidateCalculatedLayout. - // If we continue to hold the lock here, it will still be locked until the next unlock() call, causing a possible deadlock with - // -[ASNetworkImageNode displayWillStart] (which is called on a different thread / main, at an unpredictable time due to ASMainRunloopQueue). self.image = defaultImage; - } else { - _lock.unlock(); } } - (UIImage *)defaultImage { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); return _defaultImage; } - (void)setCurrentImageQuality:(CGFloat)currentImageQuality { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); _currentImageQuality = currentImageQuality; } - (CGFloat)currentImageQuality { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); return _currentImageQuality; } - (void)setRenderedImageQuality:(CGFloat)renderedImageQuality { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); _renderedImageQuality = renderedImageQuality; } - (CGFloat)renderedImageQuality { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); return _renderedImageQuality; } - (void)setDelegate:(id)delegate { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); _delegate = delegate; - _delegateSupportsDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)]; - _delegateSupportsDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; - _delegateSupportsDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; - _delegateSupportsDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)]; + _delegateFlags.delegateDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)]; + _delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; + _delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; + _delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)]; } - (id)delegate { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); return _delegate; } - (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); if (shouldRenderProgressImages == _shouldRenderProgressImages) { return; } @@ -236,19 +232,19 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; _shouldRenderProgressImages = shouldRenderProgressImages; - ASDN::MutexUnlocker u(_lock); + ASDN::MutexUnlocker u(__instanceLock__); [self _updateProgressImageBlockOnDownloaderIfNeeded]; } - (BOOL)shouldRenderProgressImages { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); return _shouldRenderProgressImages; } - (BOOL)placeholderShouldPersist { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); return (self.image == nil && _URL != nil); } @@ -258,8 +254,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; { [super displayWillStart]; - if (_cacheSupportsSynchronousFetch) { - ASDN::MutexLocker l(_lock); + if (_cacheFlags.cacheSupportsSynchronousFetch) { + ASDN::MutexLocker l(__instanceLock__); if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; if (result) { @@ -275,8 +271,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; // TODO: Consider removing this; it predates ASInterfaceState, which now ensures that even non-range-managed nodes get a -fetchData call. [self fetchData]; - if (self.image == nil && _downloaderImplementsSetPriority) { - ASDN::MutexLocker l(_lock); + if (self.image == nil && _downloaderFlags.downloaderImplementsSetPriority) { + ASDN::MutexLocker l(__instanceLock__); if (_downloadIdentifier != nil) { [_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:_downloadIdentifier]; } @@ -289,8 +285,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; { [super visibleStateDidChange:isVisible]; - if (_downloaderImplementsSetPriority) { - _lock.lock(); + if (_downloaderFlags.downloaderImplementsSetPriority) { + ASDN::MutexLocker l(__instanceLock__); if (_downloadIdentifier != nil) { if (isVisible) { [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier]; @@ -298,10 +294,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier]; } } - _lock.unlock(); } - // This method has to be called without _lock held [self _updateProgressImageBlockOnDownloaderIfNeeded]; } @@ -310,11 +304,11 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; [super clearFetchedData]; { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); [self _cancelImageDownload]; [self _clearImage]; - if (_cacheSupportsClearing) { + if (_cacheFlags.cacheSupportsClearing) { [_cache clearFetchedImageFromCacheWithURL:_URL]; } } @@ -325,26 +319,21 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; [super fetchData]; { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); [self _lazilyLoadImageIfNecessary]; } } #pragma mark - Private methods -- only call with lock. -/** - @note: This should be called without _lock held. We will lock - super to read our interface state and it's best to avoid acquiring both locks. - */ - (void)_updateProgressImageBlockOnDownloaderIfNeeded { - BOOL shouldRenderProgressImages = self.shouldRenderProgressImages; + ASDN::MutexLocker l(__instanceLock__); - // Read our interface state before locking so that we don't lock super while holding our lock. + BOOL shouldRenderProgressImages = _shouldRenderProgressImages; ASInterfaceState interfaceState = self.interfaceState; - ASDN::MutexLocker l(_lock); - if (!_downloaderImplementsSetProgress || _downloadIdentifier == nil) { + if (!_downloaderFlags.downloaderImplementsSetProgress || _downloadIdentifier == nil) { return; } @@ -357,14 +346,15 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; return; } - ASDN::MutexLocker l(strongSelf->_lock); + ASDN::MutexLocker l(strongSelf->__instanceLock__); //Getting a result back for a different download identifier, download must not have been successfully canceled if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { return; } strongSelf.image = progressImage; dispatch_async(dispatch_get_main_queue(), ^{ - strongSelf->_currentImageQuality = progress; + // See comment in -displayDidFinish for why this must be dispatched to main + strongSelf.currentImageQuality = progress; }); }; } @@ -388,6 +378,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; self.animatedImage = nil; self.image = _defaultImage; _imageLoaded = NO; + // See comment in -displayDidFinish for why this must be dispatched to main dispatch_async(dispatch_get_main_queue(), ^{ self.currentImageQuality = 0.0; }); @@ -410,8 +401,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier))finished { ASPerformBlockOnBackgroundThread(^{ - _lock.lock(); - if (_downloaderSupportsNewProtocol) { + + ASDN::MutexLocker l(__instanceLock__); + if (_downloaderFlags.downloaderSupportsNewProtocol) { _downloadIdentifier = [_downloader downloadImageWithURL:_URL callbackQueue:dispatch_get_main_queue() downloadProgress:NULL @@ -433,9 +425,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; }]; #pragma clang diagnostic pop } - _lock.unlock(); - - // This method has to be called without _lock held + [self _updateProgressImageBlockOnDownloaderIfNeeded]; }); @@ -446,15 +436,15 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; // FIXME: We should revisit locking in this method (e.g. to access the instance variables at the top, and holding lock while calling delegate) if (!_imageLoaded && _URL != nil && _downloadIdentifier == nil) { { - ASDN::MutexLocker l(_lock); - if (_delegateSupportsDidStartFetchingData) { + ASDN::MutexLocker l(__instanceLock__); + if (_delegateFlags.delegateDidStartFetchingData) { [_delegate imageNodeDidStartFetchingData:self]; } } if (_URL.isFileURL) { { - ASDN::MutexLocker l(_lock); + ASDN::MutexLocker l(__instanceLock__); dispatch_async(dispatch_get_main_queue(), ^{ if (self.shouldCacheImage) { @@ -474,12 +464,14 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; // If the file may be an animated gif and then created an animated image. id animatedImage = nil; - if (_downloaderImplementsAnimatedImage) { + if (_downloaderFlags.downloaderImplementsAnimatedImage) { NSData *data = [NSData dataWithContentsOfURL:_URL]; - animatedImage = [_downloader animatedImageWithData:data]; + if (data != nil) { + animatedImage = [_downloader animatedImageWithData:data]; - if ([animatedImage respondsToSelector:@selector(isDataSupported:)] && [animatedImage isDataSupported:data] == NO) { - animatedImage = nil; + if ([animatedImage respondsToSelector:@selector(isDataSupported:)] && [animatedImage isDataSupported:data] == NO) { + animatedImage = nil; + } } } @@ -497,7 +489,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; dispatch_async(dispatch_get_main_queue(), ^{ self.currentImageQuality = 1.0; }); - if (_delegateSupportsDidLoadImage) { + if (_delegateFlags.delegateDidLoadImage) { [_delegate imageNode:self didLoadImage:self.image]; } }); @@ -510,7 +502,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; return; } - ASDN::MutexLocker l(strongSelf->_lock); + ASDN::MutexLocker l(strongSelf->__instanceLock__); //Getting a result back for a different download identifier, download must not have been successfully canceled if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { @@ -519,7 +511,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; if (imageContainer != nil) { strongSelf->_imageLoaded = YES; - if ([imageContainer asdk_animatedImageData] && _downloaderImplementsAnimatedImage) { + if ([imageContainer asdk_animatedImageData] && _downloaderFlags.downloaderImplementsAnimatedImage) { strongSelf.animatedImage = [_downloader animatedImageWithData:[imageContainer asdk_animatedImageData]]; } else { strongSelf.image = [imageContainer asdk_image]; @@ -534,11 +526,11 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; strongSelf->_cacheUUID = nil; if (imageContainer != nil) { - if (strongSelf->_delegateSupportsDidLoadImage) { + if (strongSelf->_delegateFlags.delegateDidLoadImage) { [strongSelf->_delegate imageNode:strongSelf didLoadImage:strongSelf.image]; } } - else if (error && strongSelf->_delegateSupportsDidFailWithError) { + else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) { [strongSelf->_delegate imageNode:strongSelf didFailWithError:error]; } }; @@ -560,7 +552,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; } }; - if (_cacheSupportsNewProtocol) { + if (_cacheFlags.cacheSupportsNewProtocol) { [_cache cachedImageWithURL:_URL callbackQueue:dispatch_get_main_queue() completion:cacheCompletion]; @@ -587,8 +579,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; { [super displayDidFinish]; - ASDN::MutexLocker l(_lock); - if (_delegateSupportsDidFinishDecoding && self.layer.contents != nil) { + ASDN::MutexLocker l(__instanceLock__); + if (_delegateFlags.delegateDidFinishDecoding && self.layer.contents != nil) { /* We store the image quality in _currentImageQuality whenever _image is set. On the following displayDidFinish, we'll know that _currentImageQuality is the quality of the image that has just finished rendering. In order for this to be accurate, we need to be sure we are on main thread when we set _currentImageQuality. Otherwise, it is possible for _currentImageQuality diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 2f9809da79..21b5d698e2 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -11,6 +11,7 @@ // #import +#import @class ASPagerNode; @class ASPagerFlowLayout; @@ -22,8 +23,6 @@ * This method replaces -collectionView:numberOfItemsInSection: * * @param pagerNode The sender. - * - * * @returns The total number of pages that can display in the pagerNode. */ - (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; @@ -34,9 +33,7 @@ * This method replaces -collectionView:nodeForItemAtIndexPath: * * @param pagerNode The sender. - * - * @param index The index of the requested node. - * + * @param index The index of the requested node. * @returns a node for display at this index. This will be called on the main thread and should * not implement reuse (it will be called once per row). Unlike UICollectionView's version, * this method is not called when the row is about to display. @@ -48,9 +45,7 @@ * This method takes precedence over pagerNode:nodeAtIndex: if implemented. * * @param pagerNode The sender. - * - * @param index The index of the requested node. - * + * @param index The index of the requested node. * @returns a block that creates the node for display at this index. * Must be thread-safe (can be called on the main thread or a background * queue) and should not implement reuse (it will be called once per row). @@ -61,9 +56,7 @@ * 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; @@ -76,27 +69,46 @@ @interface ASPagerNode : ASCollectionNode -/// Configures a default horizontal, paging flow layout with 0 inter-item spacing. +/** + * Configures a default horizontal, paging flow layout with 0 inter-item spacing. + */ - (instancetype)init; -/// Initializer with custom-configured flow layout properties. +/** + * Initializer with custom-configured flow layout properties. + */ - (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; -/// Data Source is required, and uses a different protocol from ASCollectionNode. +/** + * Data Source is required, and uses a different protocol from ASCollectionNode. + */ - (void)setDataSource:(id )dataSource; - (id )dataSource; -// Delegate is optional, and uses the same protocol as ASCollectionNode. -// This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... +/** + * Delegate is optional, and uses the same protocol as ASCollectionNode. + * This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... + */ @property (nonatomic, weak) id delegate; -/// The underlying ASCollectionView object. +/** + * The underlying ASCollectionView object. + */ @property (nonatomic, readonly) ASCollectionView *view; -/// Returns the current page index +/** + * Returns the current page index + */ @property (nonatomic, assign, readonly) NSInteger currentPageIndex; -/// Scroll the contents of the receiver to ensure that the page is visible +/** + * Scroll the contents of the receiver to ensure that the page is visible + */ - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; +/** + * Returns the node for the passed page index + */ +- (ASCellNode *)nodeForPageAtIndex:(NSInteger)index; + @end diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 2c6cd4b32e..b5050f4d5c 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -14,7 +14,6 @@ #import "ASDelegateProxy.h" #import "ASDisplayNode+Subclasses.h" #import "ASPagerFlowLayout.h" -#import "UICollectionViewLayout+ASConvenience.h" @interface ASPagerNode () { @@ -98,6 +97,11 @@ [self.view scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated]; } +- (ASCellNode *)nodeForPageAtIndex:(NSInteger)index +{ + return [self.view nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; +} + #pragma mark - ASCollectionViewDataSource - (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKit/ASRunLoopQueue.mm b/AsyncDisplayKit/ASRunLoopQueue.mm index 693f01dc75..44621b667d 100644 --- a/AsyncDisplayKit/ASRunLoopQueue.mm +++ b/AsyncDisplayKit/ASRunLoopQueue.mm @@ -59,13 +59,15 @@ static void runLoopSourceCallback(void *info) { // It is not guaranteed that the runloop will turn if it has no scheduled work, and this causes processing of // the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done CFRunLoopSourceContext *runLoopSourceContext = (CFRunLoopSourceContext *)calloc(1, sizeof(CFRunLoopSourceContext)); - runLoopSourceContext->perform = runLoopSourceCallback; + if (runLoopSourceContext) { + runLoopSourceContext->perform = runLoopSourceCallback; #if ASRunLoopQueueLoggingEnabled - runLoopSourceContext->info = (__bridge void *)self; + runLoopSourceContext->info = (__bridge void *)self; #endif - _runLoopSource = CFRunLoopSourceCreate(NULL, 0, runLoopSourceContext); - CFRunLoopAddSource(runloop, _runLoopSource, kCFRunLoopCommonModes); - free(runLoopSourceContext); + _runLoopSource = CFRunLoopSourceCreate(NULL, 0, runLoopSourceContext); + CFRunLoopAddSource(runloop, _runLoopSource, kCFRunLoopCommonModes); + free(runLoopSourceContext); + } #if ASRunLoopQueueLoggingEnabled _runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES]; diff --git a/AsyncDisplayKit/ASTabBarController.m b/AsyncDisplayKit/ASTabBarController.m index 65b31d8137..8a82ff11b1 100644 --- a/AsyncDisplayKit/ASTabBarController.m +++ b/AsyncDisplayKit/ASTabBarController.m @@ -39,6 +39,12 @@ ASVisibilityDepthImplementation; - (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController { + NSUInteger viewControllerIndex = [self.viewControllers indexOfObjectIdenticalTo:childViewController]; + if (viewControllerIndex == NSNotFound) { + //If childViewController is not actually a child, return NSNotFound which is also a really large number. + return NSNotFound; + } + if (self.selectedViewController == childViewController) { return [self visibilityDepth]; } diff --git a/AsyncDisplayKit/ASTableNode.h b/AsyncDisplayKit/ASTableNode.h index 795e2ef95a..eb446a6cf3 100644 --- a/AsyncDisplayKit/ASTableNode.h +++ b/AsyncDisplayKit/ASTableNode.h @@ -11,17 +11,23 @@ // #import +#import +#import + +@protocol ASTableDataSource; +@protocol ASTableDelegate; +@class ASTableView; /** * ASTableNode is a node based class that wraps an ASTableView. It can be used * as a subnode of another node, and provide room for many (great) features and improvements later on. */ -@interface ASTableNode : ASDisplayNode +@interface ASTableNode : ASDisplayNode - (instancetype)init; // UITableViewStylePlain - (instancetype)initWithStyle:(UITableViewStyle)style; -@property (nonatomic, readonly) ASTableView *view; +@property (strong, nonatomic, readonly) ASTableView *view; // These properties can be set without triggering the view to be created, so it's fine to set them in -init. @property (weak, nonatomic) id delegate; diff --git a/AsyncDisplayKit/ASTableNode.mm b/AsyncDisplayKit/ASTableNode.mm index e5b09fea11..2f4de2d0cf 100644 --- a/AsyncDisplayKit/ASTableNode.mm +++ b/AsyncDisplayKit/ASTableNode.mm @@ -10,21 +10,35 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import "ASTableNode.h" +#import "ASTableViewInternal.h" #import "ASEnvironmentInternal.h" #import "ASDisplayNode+Subclasses.h" -#import "ASFlowLayoutController.h" #import "ASInternalHelpers.h" -#import "ASRangeControllerUpdateRangeProtocol+Beta.h" -#import "ASTableViewInternal.h" +#import "ASCellNode+Internal.h" + +#pragma mark - _ASTablePendingState @interface _ASTablePendingState : NSObject @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; +@property (assign, nonatomic) ASLayoutRangeMode rangeMode; @end @implementation _ASTablePendingState +- (instancetype)init +{ + self = [super init]; + if (self) { + _rangeMode = ASLayoutRangeModeCount; + } + return self; +} + @end +#pragma mark - ASTableView + @interface ASTableNode () { ASDN::RecursiveMutex _environmentStateLock; @@ -39,6 +53,8 @@ @implementation ASTableNode +#pragma mark Lifecycle + - (instancetype)_initWithTableView:(ASTableView *)tableView { // Avoid a retain cycle. In this case, the ASTableView is creating us, and strongly retains us. @@ -72,6 +88,8 @@ return [self _initWithFrame:CGRectZero style:UITableViewStylePlain dataControllerClass:nil]; } +#pragma mark ASDisplayNode + - (void)didLoad { [super didLoad]; @@ -84,22 +102,43 @@ self.pendingState = nil; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; + if (pendingState.rangeMode != ASLayoutRangeModeCount) { + [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; + } } } -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode +- (ASTableView *)view { - if (!self.isNodeLoaded) { - return; - } - - [self.view.rangeController updateCurrentRangeWithMode:rangeMode]; + return (ASTableView *)[super view]; } +- (void)clearContents +{ + [super clearContents]; + [self.view clearContents]; +} + +- (void)clearFetchedData +{ + [super clearFetchedData]; + [self.view clearFetchedData]; +} + +#if ASRangeControllerLoggingEnabled +- (void)visibleStateDidChange:(BOOL)isVisible +{ + [super visibleStateDidChange:isVisible]; + NSLog(@"%@ - visible: %d", self, isVisible); +} +#endif + +#pragma mark Setter / Getter + - (_ASTablePendingState *)pendingState { if (!_pendingState && ![self isNodeLoaded]) { - self.pendingState = [[_ASTablePendingState alloc] init]; + _pendingState = [[_ASTablePendingState alloc] init]; } ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASTableNode should not have a pendingState once it is loaded"); return _pendingState; @@ -143,30 +182,19 @@ } } -- (ASTableView *)view +#pragma mark ASRangeControllerUpdateRangeProtocol + +- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode { - return (ASTableView *)[super view]; + if ([self pendingState]) { + _pendingState.rangeMode = rangeMode; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); + [self.view.rangeController updateCurrentRangeWithMode:rangeMode]; + } } -#if ASRangeControllerLoggingEnabled -- (void)visibleStateDidChange:(BOOL)isVisible -{ - [super visibleStateDidChange:isVisible]; - NSLog(@"%@ - visible: %d", self, isVisible); -} -#endif - -- (void)clearContents -{ - [super clearContents]; - [self.view clearContents]; -} - -- (void)clearFetchedData -{ - [super clearFetchedData]; - [self.view clearFetchedData]; -} +#pragma mark ASEnvironment ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock) diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 4ab4877121..06ce862606 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -44,18 +44,8 @@ NS_ASSUME_NONNULL_BEGIN * The frame of the table view changes as table cells are added and deleted. * * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - * - * @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op. - * - * @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and - * `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically - * from calling thread. - * Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for - * large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically, - * we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching. - * The application should not update the data source while the data source is locked, to keep data consistence. */ -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled; +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style; /** * Tuning parameters for a range type in full mode. @@ -363,8 +353,9 @@ NS_ASSUME_NONNULL_BEGIN * due to the data access in async mode. * * @param tableView The sender. + * @deprecated The data source is always accessed on the main thread, and this method will not be called. */ -- (void)tableViewLockDataSource:(ASTableView *)tableView; +- (void)tableViewLockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED; /** * Indicator to unlock the data source for data fetching in asyn mode. @@ -372,8 +363,9 @@ NS_ASSUME_NONNULL_BEGIN * due to the data access in async mode. * * @param tableView The sender. + * @deprecated The data source is always accessed on the main thread, and this method will not be called. */ -- (void)tableViewUnlockDataSource:(ASTableView *)tableView; +- (void)tableViewUnlockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED; @end @@ -441,6 +433,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView; +/** + * Provides the constrained size range for measuring the row at the index path. + * Note: the widths in the returned size range are ignored! + * + * @param tableView The sender. + * + * @param indexPath The index path of the node. + * + * @returns A constrained size range for layout the node at this index path. + */ +- (ASSizeRange)tableView:(ASTableView *)tableView constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath; + /** * Informs the delegate that the table view did remove the node which was previously * at the given index path from the view hierarchy. @@ -458,4 +462,10 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASTableViewDelegate @end +@interface ASTableView (Deprecated) + +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled ASDISPLAYNODE_DEPRECATED; + +@end + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 871589b49d..958530cca9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -11,6 +11,7 @@ #import "ASTableViewInternal.h" #import "ASAssert.h" +#import "ASAvailability.h" #import "ASBatchFetching.h" #import "ASCellNode+Internal.h" #import "ASChangeSetDataController.h" @@ -18,16 +19,13 @@ #import "ASDisplayNodeExtras.h" #import "ASDisplayNode+Beta.h" #import "ASDisplayNode+FrameworkPrivate.h" -#import "ASEnvironmentInternal.h" #import "ASInternalHelpers.h" #import "ASLayout.h" -#import "ASLayoutController.h" -#import "ASRangeController.h" -#import "ASRangeControllerUpdateRangeProtocol+Beta.h" #import "_ASDisplayLayer.h" +#import "ASTableNode.h" +#import "ASEqualityHelpers.h" -#import - +static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero}; static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; //#define LOG(...) NSLog(__VA_ARGS__) @@ -66,20 +64,27 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)setNode:(ASCellNode *)node { _node = node; - node.selected = self.selected; - node.highlighted = self.highlighted; + [node __setSelectedFromUIKit:self.selected]; + [node __setHighlightedFromUIKit:self.highlighted]; } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; - _node.selected = selected; + [_node __setSelectedFromUIKit:selected]; } - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { [super setHighlighted:highlighted animated:animated]; - _node.highlighted = highlighted; + [_node __setHighlightedFromUIKit:highlighted]; +} + +- (void)prepareForReuse +{ + // Need to clear node pointer before UIKit calls setSelected:NO / setHighlighted:NO on its cells + self.node = nil; + [super prepareForReuse]; } @end @@ -91,7 +96,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (instancetype)_initWithTableView:(ASTableView *)tableView; @end -@interface ASTableView () +@interface ASTableView () { ASTableViewProxy *_proxyDataSource; ASTableViewProxy *_proxyDelegate; @@ -100,8 +105,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ASRangeController *_rangeController; - BOOL _asyncDataFetchingEnabled; - ASBatchContext *_batchContext; NSIndexPath *_pendingVisibleIndexPath; @@ -110,11 +113,21 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; CGFloat _contentOffsetAdjustment; CGPoint _deceleratingVelocity; + + /** + * Our layer, retained. Under iOS < 9, when table views are removed from the hierarchy, + * their layers may be deallocated and become dangling pointers. This puts the table view + * into a very dangerous state where pretty much any call will crash it. So we manually retain our layer. + * + * You should never access this, and it will be nil under iOS >= 9. + */ + CALayer *_retainedLayer; CGFloat _nodesConstrainedWidth; BOOL _ignoreNodesConstrainedWidthChange; BOOL _queuedNodeHeightUpdate; BOOL _isDeallocating; + BOOL _performingBatchUpdates; NSMutableSet *_cellsForVisibilityUpdates; struct { @@ -127,18 +140,16 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; unsigned int asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset:1; unsigned int asyncDelegateTableViewWillBeginBatchFetchWithContext:1; unsigned int asyncDelegateShouldBatchFetchForTableView:1; + unsigned int asyncDelegateTableViewConstrainedSizeForRowAtIndexPath:1; } _asyncDelegateFlags; struct { unsigned int asyncDataSourceNumberOfSectionsInTableView:1; unsigned int asyncDataSourceTableViewNodeBlockForRowAtIndexPath:1; unsigned int asyncDataSourceTableViewNodeForRowAtIndexPath:1; - unsigned int asyncDataSourceTableViewLockDataSource:1; - unsigned int asyncDataSourceTableViewUnlockDataSource:1; } _asyncDataSourceFlags; } -@property (atomic, assign) BOOL asyncDataSourceLocked; @property (nonatomic, strong, readwrite) ASDataController *dataController; // Used only when ASTableView is created directly rather than through ASTableNode. @@ -150,6 +161,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Always set, whether ASCollectionView is created directly or via ASCollectionNode. @property (nonatomic, weak) ASTableNode *tableNode; +@property (nonatomic) BOOL test_enableSuperUpdateCallLogging; @end @implementation ASTableView @@ -177,16 +189,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _rangeController.dataSource = self; _rangeController.delegate = self; - _dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:NO]; - _dataController.dataSource = self; + _dataController = [[dataControllerClass alloc] initWithDataSource:self]; _dataController.delegate = _rangeController; _dataController.environmentDelegate = self; _layoutController.dataSource = _dataController; - _asyncDataFetchingEnabled = NO; - _asyncDataSourceLocked = NO; - _leadingScreensForBatching = 2.0; _batchContext = [[ASBatchContext alloc] init]; @@ -238,6 +246,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; self.strongTableNode = tableNode; } + if (!AS_AT_LEAST_IOS9) { + _retainedLayer = self.layer; + } + return self; } @@ -290,8 +302,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]; _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; - _asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]; - _asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]; // Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath: ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath); @@ -326,6 +336,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]; _asyncDelegateFlags.asyncDelegateScrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]; _asyncDelegateFlags.asyncDelegateScrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]; + _asyncDelegateFlags.asyncDelegateTableViewConstrainedSizeForRowAtIndexPath = [_asyncDelegate respondsToSelector:@selector(tableView:constrainedSizeForRowAtIndexPath:)]; + } super.delegate = (id)_proxyDelegate; @@ -367,22 +379,22 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { - [_layoutController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; + [_rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { - return [_layoutController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; + return [_rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - [_layoutController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; + [_rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; } - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - return [_layoutController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; + return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } - (NSArray *> *)completedNodes @@ -439,6 +451,30 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_dataController waitUntilAllUpdatesAreCommitted]; } +- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated +{ + ASDisplayNodeAssertMainThread(); + + [self waitUntilAllUpdatesAreCommitted]; + [super scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; +} + +- (void)scrollToNearestSelectedRowAtScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated +{ + ASDisplayNodeAssertMainThread(); + + [self waitUntilAllUpdatesAreCommitted]; + [super scrollToNearestSelectedRowAtScrollPosition:scrollPosition animated:animated]; +} + +- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition +{ + ASDisplayNodeAssertMainThread(); + + [self waitUntilAllUpdatesAreCommitted]; + [super selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; +} + - (void)layoutSubviews { if (_nodesConstrainedWidth != self.bounds.size.width) { @@ -457,6 +493,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // To ensure _nodesConstrainedWidth is up-to-date for every usage, this call to super must be done last [super layoutSubviews]; + [_rangeController updateIfNeeded]; } #pragma mark - @@ -465,18 +502,21 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); + if (sections.count == 0) { return; } [_dataController insertSections:sections withAnimationOptions:animation]; } - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); + if (sections.count == 0) { return; } [_dataController deleteSections:sections withAnimationOptions:animation]; } - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); + if (sections.count == 0) { return; } [_dataController reloadSections:sections withAnimationOptions:animation]; } @@ -489,18 +529,21 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); + if (indexPaths.count == 0) { return; } [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); + if (indexPaths.count == 0) { return; } [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; } - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); + if (indexPaths.count == 0) { return; } [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; } @@ -586,16 +629,18 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; cell.delegate = self; ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - [_rangeController configureContentView:cell.contentView forCellNode:node]; + if (node) { + [_rangeController configureContentView:cell.contentView forCellNode:node]; - cell.node = node; - cell.backgroundColor = node.backgroundColor; - cell.selectionStyle = node.selectionStyle; + cell.node = node; + cell.backgroundColor = node.backgroundColor; + cell.selectionStyle = node.selectionStyle; - // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) - // This is actually a workaround for a bug we are seeing in some rare cases (selected background view - // overlaps other cells if size of ASCellNode has changed.) - cell.clipsToBounds = node.clipsToBounds; + // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) + // This is actually a workaround for a bug we are seeing in some rare cases (selected background view + // overlaps other cells if size of ASCellNode has changed.) + cell.clipsToBounds = node.clipsToBounds; + } return cell; } @@ -627,35 +672,29 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; } - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; - - if (cellNode.neverShowPlaceholders) { - [cellNode recursivelyEnsureDisplaySynchronously:YES]; - } + [_rangeController setNeedsUpdate]; if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) { [_cellsForVisibilityUpdates addObject:cell]; } } -- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath +- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { - if ([_pendingVisibleIndexPath isEqual:indexPath]) { + if (ASObjectIsEqual(_pendingVisibleIndexPath, indexPath)) { _pendingVisibleIndexPath = nil; } ASCellNode *cellNode = [cell node]; - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; + [_rangeController setNeedsUpdate]; if (_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath) { ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); [_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath]; } - if ([_cellsForVisibilityUpdates containsObject:cell]) { - [_cellsForVisibilityUpdates removeObject:cell]; - } + [_cellsForVisibilityUpdates removeObject:cell]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -688,18 +727,19 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { + CGPoint contentOffset = scrollView.contentOffset; _deceleratingVelocity = CGPointMake( - scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), - scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) + contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), + contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) ); if (targetContentOffset != NULL) { ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; } - + if (_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; + [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:(targetContentOffset ? : &contentOffset)]; } } @@ -845,68 +885,40 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Calling indexPathsForVisibleRows will trigger UIKit to call reloadData if it never has, which can result // in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast. - if (CGRectEqualToRect(self.bounds, CGRectZero)) { + if (CGSizeEqualToSize(self.bounds.size, CGSizeZero)) { return @[]; } - // In this case we cannot use indexPathsForVisibleRows in this case to get all the visible index paths as apparently - // in a grouped UITableView it would return index paths for cells that are over the edge of the visible area. - // Unfortunatly this means we never get a call for -tableView:cellForRowAtIndexPath: for that cells, but we will mark - // mark them as visible in the range controller - NSMutableArray *visibleIndexPaths = [NSMutableArray array]; - for (id cell in self.visibleCells) { - [visibleIndexPaths addObject:[self indexPathForCell:cell]]; + // NOTE: A prior comment claimed that `indexPathsForVisibleRows` may return extra index paths for grouped-style + // tables. This is seen as an acceptable issue for the time being. + + NSIndexPath *pendingVisibleIndexPath = _pendingVisibleIndexPath; + if (pendingVisibleIndexPath == nil) { + return self.indexPathsForVisibleRows; } - if (_pendingVisibleIndexPath) { - NSMutableSet *indexPaths = [NSMutableSet setWithArray:visibleIndexPaths]; - - BOOL (^isAfter)(NSIndexPath *, NSIndexPath *) = ^BOOL(NSIndexPath *indexPath, NSIndexPath *anchor) { - if (!anchor || !indexPath) { - return NO; - } - if (indexPath.section == anchor.section) { - return (indexPath.row == anchor.row+1); // assumes that indexes are valid - - } else if (indexPath.section > anchor.section && indexPath.row == 0) { - if (anchor.row != [_dataController numberOfRowsInSection:anchor.section] -1) { - return NO; // anchor is not at the end of the section - } - - NSInteger nextSection = anchor.section+1; - while([_dataController numberOfRowsInSection:nextSection] == 0) { - ++nextSection; - } - - return indexPath.section == nextSection; - } - - return NO; - }; - - BOOL (^isBefore)(NSIndexPath *, NSIndexPath *) = ^BOOL(NSIndexPath *indexPath, NSIndexPath *anchor) { - return isAfter(anchor, indexPath); - }; - - if ([indexPaths containsObject:_pendingVisibleIndexPath]) { - _pendingVisibleIndexPath = nil; // once it has shown up in visibleIndexPaths, we can stop tracking it - } else if (!isBefore(_pendingVisibleIndexPath, visibleIndexPaths.firstObject) && - !isAfter(_pendingVisibleIndexPath, visibleIndexPaths.lastObject)) { - _pendingVisibleIndexPath = nil; // not contiguous, ignore. - } else { - [indexPaths addObject:_pendingVisibleIndexPath]; - - [visibleIndexPaths removeAllObjects]; - [visibleIndexPaths addObjectsFromArray:[indexPaths.allObjects sortedArrayUsingSelector:@selector(compare:)]]; - } - } + NSMutableArray *visibleIndexPaths = [self.indexPathsForVisibleRows mutableCopy]; + [visibleIndexPaths sortUsingSelector:@selector(compare:)]; + + BOOL isPendingIndexPathVisible = (NSNotFound != [visibleIndexPaths indexOfObject:pendingVisibleIndexPath inSortedRange:NSMakeRange(0, visibleIndexPaths.count) options:kNilOptions usingComparator:^(id _Nonnull obj1, id _Nonnull obj2) { + return [obj1 compare:obj2]; + }]); + if (isPendingIndexPathVisible) { + _pendingVisibleIndexPath = nil; // once it has shown up in visibleIndexPaths, we can stop tracking it + } else if ([self isIndexPath:visibleIndexPaths.firstObject immediateSuccessorOfIndexPath:pendingVisibleIndexPath]) { + [visibleIndexPaths insertObject:pendingVisibleIndexPath atIndex:0]; + } else if ([self isIndexPath:pendingVisibleIndexPath immediateSuccessorOfIndexPath:visibleIndexPaths.lastObject]) { + [visibleIndexPaths addObject:pendingVisibleIndexPath]; + } else { + _pendingVisibleIndexPath = nil; // not contiguous, ignore. + } return visibleIndexPaths; } -- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths +- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController { - return [_dataController nodesAtIndexPaths:indexPaths]; + return self.scrollDirection; } - (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath @@ -936,6 +948,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } + _performingBatchUpdates = YES; [super beginUpdates]; if (_automaticallyAdjustsContentOffset) { @@ -961,8 +974,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ASPerformBlockWithoutAnimation(!animated, ^{ [super endUpdates]; + [_rangeController updateIfNeeded]; }); + _performingBatchUpdates = NO; if (completion) { completion(YES); } @@ -984,7 +999,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super insertRowsAtIndexPaths]: %@", indexPaths); + } [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + if (!_performingBatchUpdates) { + [_rangeController updateIfNeeded]; + } [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; }); @@ -1004,7 +1025,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super deleteRowsAtIndexPaths]: %@", indexPaths); + } [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + if (!_performingBatchUpdates) { + [_rangeController updateIfNeeded]; + } [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; }); @@ -1025,7 +1052,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super insertSections]: %@", indexSet); + } [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + if (!_performingBatchUpdates) { + [_rangeController updateIfNeeded]; + } [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; }); } @@ -1041,7 +1074,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ + if (self.test_enableSuperUpdateCallLogging) { + NSLog(@"-[super deleteSections]: %@", indexSet); + } [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + if (!_performingBatchUpdates) { + [_rangeController updateIfNeeded]; + } [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; }); } @@ -1056,8 +1095,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return ^{ __typeof__(self) strongSelf = weakSelf; [node enterHierarchyState:ASHierarchyStateRangeManaged]; - if (node.layoutDelegate == nil) { - node.layoutDelegate = strongSelf; + if (node.interactionDelegate == nil) { + node.interactionDelegate = strongSelf; } return node; }; @@ -1069,8 +1108,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; __typeof__(self) strongSelf = weakSelf; ASCellNode *node = block(); [node enterHierarchyState:ASHierarchyStateRangeManaged]; - if (node.layoutDelegate == nil) { - node.layoutDelegate = strongSelf; + if (node.interactionDelegate == nil) { + node.interactionDelegate = strongSelf; } return node; }; @@ -1079,30 +1118,17 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - return ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, 0), - CGSizeMake(_nodesConstrainedWidth, FLT_MAX)); -} - -- (void)dataControllerLockDataSource -{ - ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); - - self.asyncDataSourceLocked = YES; - - if (_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource) { - [_asyncDataSource tableViewLockDataSource:self]; - } -} - -- (void)dataControllerUnlockDataSource -{ - ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked"); - - self.asyncDataSourceLocked = NO; - - if (_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource) { - [_asyncDataSource tableViewUnlockDataSource:self]; + ASSizeRange constrainedSize = kInvalidSizeRange; + if (_asyncDelegateFlags.asyncDelegateTableViewConstrainedSizeForRowAtIndexPath) { + ASSizeRange delegateConstrainedSize = [_asyncDelegate tableView:self constrainedSizeForRowAtIndexPath:indexPath]; + // ignore widths in the returned size range (for TableView) + constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.min.height), + CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.max.height)); + } else { + constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, 0), + CGSizeMake(_nodesConstrainedWidth, FLT_MAX)); } + return constrainedSize; } - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section @@ -1158,7 +1184,27 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -#pragma mark - ASCellNodeLayoutDelegate +#pragma mark - ASCellNodeDelegate + +- (void)nodeSelectedStateDidChange:(ASCellNode *)node +{ + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath) { + if (node.isSelected) { + [self selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; + } else { + [self deselectRowAtIndexPath:indexPath animated:NO]; + } + } +} + +- (void)nodeHighlightedStateDidChange:(ASCellNode *)node +{ + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath) { + [self cellForRowAtIndexPath:indexPath].highlighted = node.isHighlighted; + } +} - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged { @@ -1196,6 +1242,32 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_rangeController clearFetchedData]; } +#pragma mark - Helper Methods + +- (BOOL)isIndexPath:(NSIndexPath *)indexPath immediateSuccessorOfIndexPath:(NSIndexPath *)anchor +{ + if (!anchor || !indexPath) { + return NO; + } + if (indexPath.section == anchor.section) { + return (indexPath.row == anchor.row+1); // assumes that indexes are valid + + } else if (indexPath.section > anchor.section && indexPath.row == 0) { + if (anchor.row != [_dataController numberOfRowsInSection:anchor.section] -1) { + return NO; // anchor is not at the end of the section + } + + NSInteger nextSection = anchor.section+1; + while([_dataController numberOfRowsInSection:nextSection] == 0) { + ++nextSection; + } + + return indexPath.section == nextSection; + } + + return NO; +} + #pragma mark - _ASDisplayView behavior substitutions // Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. // Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. @@ -1219,7 +1291,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass if (![node supportsRangeManagedInterfaceState]) { - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; + [_rangeController setNeedsUpdate]; + [_rangeController updateIfNeeded]; } } diff --git a/AsyncDisplayKit/ASTableViewInternal.h b/AsyncDisplayKit/ASTableViewInternal.h index 93cdca0fcb..6c24652ae8 100644 --- a/AsyncDisplayKit/ASTableViewInternal.h +++ b/AsyncDisplayKit/ASTableViewInternal.h @@ -10,9 +10,11 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTableNode.h" +#import @class ASDataController; +@class ASTableNode; +@class ASRangeController; @interface ASTableView (Internal) @@ -34,4 +36,7 @@ */ - (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass ownedByNode:(BOOL)ownedByNode; +/// Set YES and we'll log every time we call [super insertRows…] etc +@property (nonatomic) BOOL test_enableSuperUpdateCallLogging; + @end diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index 02eb204a33..e59e11bd4f 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -108,10 +108,10 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { /** @abstract When you set these ASDisplayNode properties, they are composited into the bitmap instead of being applied by CA. - @property (atomic, assign) CGColorRef shadowColor; - @property (atomic, assign) CGFloat shadowOpacity; - @property (atomic, assign) CGSize shadowOffset; - @property (atomic, assign) CGFloat shadowRadius; + @property (nonatomic, assign) CGColorRef shadowColor; + @property (nonatomic, assign) CGFloat shadowOpacity; + @property (nonatomic, assign) CGSize shadowOffset; + @property (nonatomic, assign) CGFloat shadowRadius; */ /** @@ -272,6 +272,14 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { @end +@interface ASTextNode (Unavailable) + +- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; + +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; + +@end + /** * @abstract Text node deprecated properties */ diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 95d441452d..b564caa488 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -13,11 +13,11 @@ #include -#import -#import -#import -#import -#import +#import "_ASDisplayLayer.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNodeInternal.h" +#import "ASHighlightOverlayLayer.h" +#import "ASDisplayNodeExtras.h" #import "ASTextKitCoreTextAdditions.h" #import "ASTextKitRenderer+Positioning.h" @@ -57,8 +57,6 @@ struct ASTextNodeDrawParameter { NSRange _highlightRange; ASHighlightOverlayLayer *_activeHighlightLayer; - std::recursive_mutex _textLock; - CGSize _constrainedSize; ASTextKitRenderer *_renderer; @@ -124,18 +122,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; return self; } -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - - (void)dealloc { if (_shadowColor != NULL) { @@ -153,7 +139,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (NSString *)description { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); NSString *plainString = [[_attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; NSString *truncationString = [_composedTruncationText string]; @@ -222,7 +208,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (_renderer == nil) { CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size; @@ -234,7 +220,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (ASTextKitAttributes)_rendererAttributes { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return { .attributedString = _attributedText, @@ -242,7 +228,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .lineBreakMode = _truncationMode, .maximumNumberOfLines = _maximumNumberOfLines, .exclusionPaths = _exclusionPaths, - .pointSizeScaleFactors = _pointSizeScaleFactors, + // use the property getter so a subclass can provide these scale factors on demand if desired + .pointSizeScaleFactors = self.pointSizeScaleFactors, .layoutManagerCreationBlock = self.layoutManagerCreationBlock, .textStorageCreationBlock = self.textStorageCreationBlock, }; @@ -260,7 +247,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // so our previous layout information is invalid, and TextKit may draw at the // incorrect origin. { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); } [self _invalidateRenderer]; @@ -269,7 +256,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)_invalidateRenderer { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (_renderer) { // Destruction of the layout managers/containers/text storage is quite @@ -288,7 +275,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (_renderer == nil) { return YES; @@ -326,10 +313,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; ASLayout *layout = self.calculatedLayout; - std::lock_guard l(_textLock); if (layout != nil) { - _constrainedSize = layout.size; - _renderer.constrainedSize = layout.size; + ASDN::MutexLocker l(__instanceLock__); + if (CGSizeEqualToSize(_constrainedSize, layout.size) == NO) { + _constrainedSize = layout.size; + _renderer.constrainedSize = layout.size; + } } } @@ -338,7 +327,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width); ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height); - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); _constrainedSize = constrainedSize; @@ -350,9 +339,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; CGSize size = [self _renderer].size; if (_attributedText.length > 0) { - CGFloat screenScale = ASScreenScale(); - self.ascender = round([[_attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; - self.descender = round([[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale; + self.ascender = [[self class] ascenderWithAttributedString:_attributedText]; + self.descender = [[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender]; if (_renderer.currentScaleFactor > 0 && _renderer.currentScaleFactor < 1.0) { // while not perfect, this is a good estimate of what the ascender of the scaled font will be. self.ascender *= _renderer.currentScaleFactor; @@ -364,50 +352,69 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; #pragma mark - Modifying User Text +// Returns the ascender of the first character in attributedString by also including the line height if specified in paragraph style. ++ (CGFloat)ascenderWithAttributedString:(NSAttributedString *)attributedString +{ + UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL]; + NSParagraphStyle *paragraphStyle = [attributedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL]; + if (!paragraphStyle) { + return font.ascender; + } + CGFloat lineHeight = MAX(font.lineHeight, paragraphStyle.minimumLineHeight); + if (paragraphStyle.maximumLineHeight > 0) { + lineHeight = MIN(lineHeight, paragraphStyle.maximumLineHeight); + } + return lineHeight + font.descender; +} + - (void)setAttributedText:(NSAttributedString *)attributedText { - std::lock_guard l(_textLock); if (attributedText == nil) { attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil]; } - - if (ASObjectIsEqual(attributedText, _attributedText)) { - return; - } - - _attributedText = ASCleanseAttributedStringOfCoreTextAttributes(attributedText); - - if (_attributedText.length > 0) { - CGFloat screenScale = ASScreenScale(); - self.ascender = round([[_attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; - self.descender = round([[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale; - } - - // Sync the truncation string with attributes from the updated _attributedString - // Without this, the size calculation of the text with truncation applied will - // not take into account the attributes of attributedText in the last line - [self _updateComposedTruncationText]; - // We need an entirely new renderer - [self _invalidateRenderer]; + // Don't hold textLock for too long. + { + ASDN::MutexLocker l(__instanceLock__); + if (ASObjectIsEqual(attributedText, _attributedText)) { + return; + } + + _attributedText = ASCleanseAttributedStringOfCoreTextAttributes(attributedText); + + // Sync the truncation string with attributes from the updated _attributedString + // Without this, the size calculation of the text with truncation applied will + // not take into account the attributes of attributedText in the last line + [self _updateComposedTruncationText]; + + // We need an entirely new renderer + [self _invalidateRenderer]; + } + + NSUInteger length = attributedText.length; + if (length > 0) { + self.ascender = [[self class] ascenderWithAttributedString:attributedText]; + self.descender = [[attributedText attribute:NSFontAttributeName atIndex:attributedText.length - 1 effectiveRange:NULL] descender]; + } // Tell the display node superclasses that the cached layout is incorrect now [self invalidateCalculatedLayout]; + // Force display to create renderer with new size and redisplay with new string [self setNeedsDisplay]; // Accessiblity - self.accessibilityLabel = _attributedText.string; - self.isAccessibilityElement = (_attributedText.length != 0); // We're an accessibility element by default if there is a string. + self.accessibilityLabel = attributedText.string; + self.isAccessibilityElement = (length != 0); // We're an accessibility element by default if there is a string. } #pragma mark - Text Layout - (void)setExclusionPaths:(NSArray *)exclusionPaths { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (ASObjectIsEqual(exclusionPaths, _exclusionPaths)) { return; @@ -421,7 +428,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (NSArray *)exclusionPaths { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return _exclusionPaths; } @@ -430,7 +437,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); _drawParameter = { .backgroundColor = self.backgroundColor, @@ -442,7 +449,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)drawRect:(CGRect)bounds withParameters:(id )p isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); ASTextNodeDrawParameter drawParameter = _drawParameter; CGRect drawParameterBounds = drawParameter.bounds; @@ -495,7 +502,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; { ASDisplayNodeAssertMainThread(); - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); ASTextKitRenderer *renderer = [self _renderer]; NSRange visibleRange = renderer.firstVisibleRange; @@ -622,14 +629,14 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (ASTextNodeHighlightStyle)highlightStyle { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return _highlightStyle; } - (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); _highlightStyle = highlightStyle; } @@ -713,6 +720,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } if (highlightTargetLayer != nil) { + ASDN::MutexLocker l(__instanceLock__); + NSArray *highlightRects = [[self _renderer] rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock]; NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count]; for (NSValue *rectValue in highlightRects) { @@ -791,7 +800,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption]; NSMutableArray *adjustedRects = [NSMutableArray array]; @@ -809,7 +818,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (CGRect)trailingRect { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); CGRect rect = [[self _renderer] trailingRect]; return ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding); @@ -817,7 +826,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (CGRect)frameForTextRange:(NSRange)textRange { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); CGRect frame = [[self _renderer] frameForTextRange:textRange]; return ASTextNodeAdjustRenderRectForShadowPadding(frame, self.shadowPadding); @@ -827,7 +836,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (void)setPlaceholderColor:(UIColor *)placeholderColor { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); _placeholderColor = placeholderColor; @@ -844,7 +853,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI return nil; } - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); UIGraphicsBeginImageContext(size); [self.placeholderColor setFill]; @@ -906,8 +915,6 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI { ASDisplayNodeAssertMainThread(); - std::lock_guard l(_textLock); - [super touchesBegan:touches withEvent:event]; CGPoint point = [[touches anyObject] locationInView:self.view]; @@ -926,7 +933,11 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); if (inAdditionalTruncationMessage) { - NSRange visibleRange = [self _renderer].firstVisibleRange; + NSRange visibleRange = NSMakeRange(0, 0); + { + ASDN::MutexLocker l(__instanceLock__); + visibleRange = [self _renderer].firstVisibleRange; + } NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange]; [self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES]; } else if (range.length && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { @@ -1003,14 +1014,14 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (BOOL)_pendingLinkTap { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && _delegate != nil; } - (BOOL)_pendingTruncationTap { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return [_highlightedLinkAttributeName isEqualToString:ASTextNodeTruncationTokenAttributeName]; } @@ -1019,14 +1030,14 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (CGColorRef)shadowColor { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return _shadowColor; } - (void)setShadowColor:(CGColorRef)shadowColor { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (_shadowColor != shadowColor) { if (shadowColor != NULL) { @@ -1040,14 +1051,14 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (CGSize)shadowOffset { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return _shadowOffset; } - (void)setShadowOffset:(CGSize)shadowOffset { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) { _shadowOffset = shadowOffset; @@ -1058,14 +1069,14 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (CGFloat)shadowOpacity { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return _shadowOpacity; } - (void)setShadowOpacity:(CGFloat)shadowOpacity { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (_shadowOpacity != shadowOpacity) { _shadowOpacity = shadowOpacity; @@ -1076,14 +1087,14 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (CGFloat)shadowRadius { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return _shadowRadius; } - (void)setShadowRadius:(CGFloat)shadowRadius { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (_shadowRadius != shadowRadius) { _shadowRadius = shadowRadius; @@ -1099,7 +1110,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI - (UIEdgeInsets)shadowPaddingWithRenderer:(ASTextKitRenderer *)renderer { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return renderer.shadower.shadowPadding; } @@ -1118,7 +1129,7 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (ASObjectIsEqual(_truncationAttributedText, truncationAttributedText)) { return; @@ -1130,7 +1141,7 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (ASObjectIsEqual(_additionalTruncationMessage, additionalTruncationMessage)) { return; @@ -1142,7 +1153,7 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)setTruncationMode:(NSLineBreakMode)truncationMode { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (_truncationMode != truncationMode) { _truncationMode = truncationMode; @@ -1153,7 +1164,7 @@ static NSAttributedString *DefaultTruncationAttributedString() - (BOOL)isTruncated { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); ASTextKitRenderer *renderer = [self _renderer]; return renderer.firstVisibleRange.length < _attributedText.length; @@ -1161,7 +1172,7 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) { _pointSizeScaleFactors = pointSizeScaleFactors; @@ -1171,7 +1182,7 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); if (_maximumNumberOfLines != maximumNumberOfLines) { _maximumNumberOfLines = maximumNumberOfLines; @@ -1182,7 +1193,7 @@ static NSAttributedString *DefaultTruncationAttributedString() - (NSUInteger)lineCount { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); return [[self _renderer] lineCount]; } @@ -1191,7 +1202,7 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)_updateComposedTruncationText { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); _composedTruncationText = [self _prepareTruncationStringForDrawing:[self _composedTruncationText]]; } @@ -1209,7 +1220,7 @@ static NSAttributedString *DefaultTruncationAttributedString() */ - (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); // Check if we even have an additional truncation message. if (!_additionalTruncationMessage) { @@ -1232,7 +1243,7 @@ static NSAttributedString *DefaultTruncationAttributedString() */ - (NSAttributedString *)_composedTruncationText { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); //If we have neither return the default if (!_additionalTruncationMessage && !_truncationAttributedText) { @@ -1262,14 +1273,14 @@ static NSAttributedString *DefaultTruncationAttributedString() */ - (NSAttributedString *)_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString { - std::lock_guard l(_textLock); + ASDN::MutexLocker l(__instanceLock__); truncationString = ASCleanseAttributedStringOfCoreTextAttributes(truncationString); NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy]; // Grab the attributes from the full string if (_attributedText.length > 0) { - NSAttributedString *originalString = _truncationAttributedText; - NSInteger originalStringLength = _truncationAttributedText.length; + NSAttributedString *originalString = _attributedText; + NSInteger originalStringLength = _attributedText.length; // Add any of the original string's attributes to the truncation string, // but don't overwrite any of the truncation string's attributes NSDictionary *originalStringAttributes = [originalString attributesAtIndex:originalStringLength-1 effectiveRange:NULL]; diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 1194266ef6..039c21269a 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -12,7 +12,7 @@ #import #import -@class AVAsset, AVPlayer, AVPlayerItem; +@class AVAsset, AVPlayer, AVPlayerItem, AVVideoComposition, AVAudioMix; @protocol ASVideoNodeDelegate; typedef enum { @@ -40,10 +40,18 @@ NS_ASSUME_NONNULL_BEGIN - (void)pause; - (BOOL)isPlaying; -@property (nullable, atomic, strong, readwrite) AVAsset *asset; +@property (nullable, nonatomic, strong, readwrite) AVAsset *asset; +/** + ** @abstract The URL with which the asset was initialized. + ** @discussion Setting the URL will overwrite the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. + ** @return Current URL the asset was initialized or nil if no URL was given. + **/ +@property (nullable, nonatomic, strong, readwrite) NSURL *assetURL; +@property (nullable, nonatomic, strong, readwrite) AVVideoComposition *videoComposition; +@property (nullable, nonatomic, strong, readwrite) AVAudioMix *audioMix; -@property (nullable, atomic, strong, readonly) AVPlayer *player; -@property (nullable, atomic, strong, readonly) AVPlayerItem *currentItem; +@property (nullable, nonatomic, strong, readonly) AVPlayer *player; +@property (nullable, nonatomic, strong, readonly) AVPlayerItem *currentItem; /** @@ -61,9 +69,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) int32_t periodicTimeObserverTimescale; //! Defaults to AVLayerVideoGravityResizeAspect -@property (atomic) NSString *gravity; +@property (nonatomic) NSString *gravity; -@property (nullable, atomic, weak, readwrite) id delegate; +@property (nullable, nonatomic, weak, readwrite) id delegate; @end @@ -121,6 +129,12 @@ NS_ASSUME_NONNULL_BEGIN * @param videoNode The videoNode */ - (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode; +/** + * @abstract Delegate method invoked when the AVPlayerItem for the asset has been set up and can be accessed throught currentItem. + * @param videoNode The videoNode. + * @param currentItem The AVPlayerItem that was constructed from the asset. + */ +- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem; /** * @abstract Delegate method invoked when the video node has recovered from the stall * @param videoNode The videoNode @@ -133,5 +147,13 @@ NS_ASSUME_NONNULL_BEGIN - (void)videoNode:(ASVideoNode *)videoNode didPlayToSecond:(NSTimeInterval)second __deprecated; @end + +@interface ASVideoNode (Unavailable) + +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; + +@end + NS_ASSUME_NONNULL_END -#endif \ No newline at end of file +#endif + diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 379a70955b..e6f44993ea 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -8,8 +8,13 @@ // of patent rights can be found in the PATENTS file in the same directory. // #if TARGET_OS_IOS +#import +#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+Subclasses.h" #import "ASVideoNode.h" -#import "ASDefaultPlayButton.h" +#import "ASEqualityHelpers.h" +#import "ASInternalHelpers.h" +#import "ASDisplayNodeExtras.h" static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { return ASObjectIsEqual(asset1, asset2) @@ -35,8 +40,6 @@ static NSString * const kStatus = @"status"; @interface ASVideoNode () { - ASDN::RecursiveMutex _videoLock; - __weak id _delegate; struct { unsigned int delegateVideNodeShouldChangePlayerStateTo:1; @@ -46,6 +49,7 @@ static NSString * const kStatus = @"status"; unsigned int delegateVideoNodeDidPlayToTimeInterval:1; unsigned int delegateVideoNodeDidStartInitialLoading:1; unsigned int delegateVideoNodeDidFinishInitialLoading:1; + unsigned int delegateVideoNodeDidSetCurrentItem:1; unsigned int delegateVideoNodeDidStallAtTimeInterval:1; unsigned int delegateVideoNodeDidRecoverFromStall:1; @@ -65,6 +69,9 @@ static NSString * const kStatus = @"status"; ASVideoNodePlayerState _playerState; AVAsset *_asset; + NSURL *_assetURL; + AVVideoComposition *_videoComposition; + AVAudioMix *_audioMix; AVPlayerItem *_currentPlayerItem; AVPlayer *_player; @@ -98,12 +105,6 @@ static NSString * const kStatus = @"status"; return self; } -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - - (ASDisplayNode *)constructPlayerNode { ASVideoNode * __weak weakSelf = self; @@ -118,13 +119,19 @@ static NSString * const kStatus = @"status"; - (AVPlayerItem *)constructPlayerItem { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); - if (_asset != nil) { - return [[AVPlayerItem alloc] initWithAsset:_asset]; + AVPlayerItem *playerItem = nil; + if (_assetURL != nil) { + playerItem = [[AVPlayerItem alloc] initWithURL:_assetURL]; + _asset = [playerItem asset]; + } else { + playerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; } - return nil; + playerItem.videoComposition = _videoComposition; + playerItem.audioMix = _audioMix; + return playerItem; } - (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys @@ -137,7 +144,7 @@ static NSString * const kStatus = @"status"; } } - if (![asset isPlayable]) { + if ([asset isPlayable] == NO) { NSLog(@"Asset is not playable."); return; } @@ -150,8 +157,12 @@ static NSString * const kStatus = @"status"; } else { self.player = [AVPlayer playerWithPlayerItem:playerItem]; } - - if (self.image == nil) { + + if (_delegateFlags.delegateVideoNodeDidSetCurrentItem) { + [_delegate videoNode:self didSetCurrentItem:playerItem]; + } + + if (self.image == nil && self.URL == nil) { [self generatePlaceholderImage]; } @@ -164,6 +175,10 @@ static NSString * const kStatus = @"status"; - (void)addPlayerItemObservers:(AVPlayerItem *)playerItem { + if (playerItem == nil) { + return; + } + [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext]; [playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; [playerItem addObserver:self forKeyPath:kplaybackBufferEmpty options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; @@ -202,7 +217,7 @@ static NSString * const kStatus = @"status"; - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); CGSize calculatedSize = constrainedSize; // if a preferredFrameSize is set, call the superclass to return that instead of using the image size. @@ -265,7 +280,7 @@ static NSString * const kStatus = @"status"; - (void)setVideoPlaceholderImage:(UIImage *)image { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); if (image != nil) { self.contentMode = ASContentModeFromVideoGravity(_gravity); } @@ -274,7 +289,7 @@ static NSString * const kStatus = @"status"; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); if (object != _currentPlayerItem) { return; @@ -284,7 +299,7 @@ static NSString * const kStatus = @"status"; 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) { + if (self.image == nil && self.URL == nil) { [self generatePlaceholderImage]; } } @@ -327,7 +342,7 @@ static NSString * const kStatus = @"status"; { [super fetchData]; - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); AVAsset *asset = self.asset; // Return immediately if the asset is nil; if (asset == nil || self.playerState == ASVideoNodePlayerStateInitialLoading) { @@ -377,7 +392,7 @@ static NSString * const kStatus = @"status"; [super clearFetchedData]; { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); self.player = nil; self.currentItem = nil; @@ -388,7 +403,7 @@ static NSString * const kStatus = @"status"; { [super visibleStateDidChange:isVisible]; - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); if (isVisible) { if (_shouldBePlaying || _shouldAutoplay) { @@ -405,7 +420,7 @@ static NSString * const kStatus = @"status"; - (void)setPlayerState:(ASVideoNodePlayerState)playerState { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); ASVideoNodePlayerState oldState = _playerState; @@ -420,30 +435,82 @@ static NSString * const kStatus = @"status"; _playerState = playerState; } -- (void)setAsset:(AVAsset *)asset +- (void)setAssetURL:(NSURL *)assetURL { - ASDN::MutexLocker l(_videoLock); - - if (ASAssetIsEqual(asset, _asset)) { - return; + ASDN::MutexLocker l(__instanceLock__); + + if (ASObjectIsEqual(assetURL, self.assetURL) == NO) { + [self _setAndFetchAsset:[AVURLAsset assetWithURL:assetURL] url:assetURL]; + } +} + +- (NSURL *)assetURL +{ + ASDN::MutexLocker l(__instanceLock__); + + if (_assetURL != nil) { + return _assetURL; + } else if ([_asset isKindOfClass:AVURLAsset.class]) { + return ((AVURLAsset *)_asset).URL; } - [self clearFetchedData]; + return nil; +} - _asset = asset; - - [self setNeedsDataFetch]; +- (void)setAsset:(AVAsset *)asset +{ + ASDN::MutexLocker l(__instanceLock__); + + if (ASAssetIsEqual(asset, _asset) == NO) { + [self _setAndFetchAsset:asset url:nil]; + } } - (AVAsset *)asset { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); return _asset; } +- (void)_setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL +{ + [self clearFetchedData]; + _asset = asset; + _assetURL = assetURL; + [self setNeedsDataFetch]; +} + +- (void)setVideoComposition:(AVVideoComposition *)videoComposition +{ + ASDN::MutexLocker l(__instanceLock__); + + _videoComposition = videoComposition; + _currentPlayerItem.videoComposition = videoComposition; +} + +- (AVVideoComposition *)videoComposition +{ + ASDN::MutexLocker l(__instanceLock__); + return _videoComposition; +} + +- (void)setAudioMix:(AVAudioMix *)audioMix +{ + ASDN::MutexLocker l(__instanceLock__); + + _audioMix = audioMix; + _currentPlayerItem.audioMix = audioMix; +} + +- (AVAudioMix *)audioMix +{ + ASDN::MutexLocker l(__instanceLock__); + return _audioMix; +} + - (AVPlayer *)player { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); return _player; } @@ -466,6 +533,7 @@ static NSString * const kStatus = @"status"; _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [_delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)]; _delegateFlags.delegateVideoNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)]; _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)]; + _delegateFlags.delegateVideoNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)]; _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)]; _delegateFlags.delegateVideoNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)]; @@ -481,7 +549,7 @@ static NSString * const kStatus = @"status"; - (void)setGravity:(NSString *)gravity { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); if (_playerNode.isNodeLoaded) { ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; } @@ -491,19 +559,19 @@ static NSString * const kStatus = @"status"; - (NSString *)gravity { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); return _gravity; } - (BOOL)muted { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); return _muted; } - (void)setMuted:(BOOL)muted { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); _player.muted = muted; _muted = muted; @@ -513,7 +581,7 @@ static NSString * const kStatus = @"status"; - (void)play { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); if (![self isStateChangeValid:ASVideoNodePlayerStatePlaying]) { return; @@ -550,7 +618,7 @@ static NSString * const kStatus = @"status"; - (void)pause { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) { return; } @@ -561,7 +629,7 @@ static NSString * const kStatus = @"status"; - (BOOL)isPlaying { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); return (_player.rate > 0 && !_player.error); } @@ -591,9 +659,9 @@ static NSString * const kStatus = @"status"; [_delegate videoPlaybackDidFinish:self]; #pragma clang diagnostic pop } - [_player seekToTime:kCMTimeZero]; if (_shouldAutorepeat) { + [_player seekToTime:kCMTimeZero]; [self play]; } else { [self pause]; @@ -629,30 +697,32 @@ static NSString * const kStatus = @"status"; - (AVPlayerItem *)currentItem { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); return _currentPlayerItem; } - (void)setCurrentItem:(AVPlayerItem *)currentItem { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); [self removePlayerItemObservers:_currentPlayerItem]; _currentPlayerItem = currentItem; - [self addPlayerItemObservers:currentItem]; + if (currentItem != nil) { + [self addPlayerItemObservers:currentItem]; + } } - (ASDisplayNode *)playerNode { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); return _playerNode; } - (void)setPlayerNode:(ASDisplayNode *)playerNode { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); _playerNode = playerNode; [self setNeedsLayout]; @@ -660,7 +730,7 @@ static NSString * const kStatus = @"status"; - (void)setPlayer:(AVPlayer *)player { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); _player = player; player.muted = _muted; ((AVPlayerLayer *)_playerNode.layer).player = player; @@ -668,13 +738,13 @@ static NSString * const kStatus = @"status"; - (BOOL)shouldBePlaying { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); return _shouldBePlaying; } - (void)setShouldBePlaying:(BOOL)shouldBePlaying { - ASDN::MutexLocker l(_videoLock); + ASDN::MutexLocker l(__instanceLock__); _shouldBePlaying = shouldBePlaying; } @@ -688,4 +758,4 @@ static NSString * const kStatus = @"status"; } @end -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index de07dada8e..1e8aead6d9 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ASVideoPlayerNode : ASDisplayNode -@property (nullable, atomic, weak) id delegate; +@property (nullable, nonatomic, weak) id delegate; @property (nonatomic, assign, readonly) CMTime duration; @@ -49,16 +49,19 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, readwrite) BOOL muted; @property (nonatomic, assign, readonly) ASVideoNodePlayerState playerState; @property (nonatomic, assign, readwrite) BOOL shouldAggressivelyRecoverFromStall; +@property (nullable, nonatomic, strong, readwrite) NSURL *placeholderImageURL; //! Defaults to 100 @property (nonatomic, assign) int32_t periodicTimeObserverTimescale; //! Defaults to AVLayerVideoGravityResizeAspect -@property (atomic) NSString *gravity; +@property (nonatomic) NSString *gravity; - (instancetype)initWithUrl:(NSURL*)url; - (instancetype)initWithAsset:(AVAsset*)asset; +- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix; - (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; - (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; +- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; #pragma mark - Public API - (void)seekToTime:(CGFloat)percentComplete; @@ -116,6 +119,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Spinner delegate methods - (UIColor *)videoPlayerNodeSpinnerTint:(ASVideoPlayerNode *)videoPlayer; +- (UIActivityIndicatorViewStyle)videoPlayerNodeSpinnerStyle:(ASVideoPlayerNode *)videoPlayer; #pragma mark - Playback button delegate methods - (UIColor *)videoPlayerNodePlaybackButtonTint:(ASVideoPlayerNode *)videoPlayer; @@ -155,10 +159,43 @@ NS_ASSUME_NONNULL_BEGIN /** * @abstract Delegate method invoked when the ASVideoNode has played to its end time. - * @param videoPlayerNode The video node has played to its end time. + * @param videoPlayer The video node has played to its end time. */ - (void)videoPlayerNodeDidPlayToEnd:(ASVideoPlayerNode *)videoPlayer; +/** + * @abstract Delegate method invoked when the ASVideoNode has constructed its AVPlayerItem for the asset. + * @param videoPlayer The video player node. + * @param currentItem The AVPlayerItem that was constructed from the asset. + */ +- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didSetCurrentItem:(AVPlayerItem *)currentItem; + +/** + * @abstract Delegate method invoked when the ASVideoNode stalls. + * @param videoPlayer The video player node that has experienced the stall + * @param second Current playback time when the stall happens + */ +- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didStallAtTimeInterval:(NSTimeInterval)timeInterval; + +/** + * @abstract Delegate method invoked when the ASVideoNode starts the inital asset loading + * @param videoPlayer The videoPlayer + */ +- (void)videoPlayerNodeDidStartInitialLoading:(ASVideoPlayerNode *)videoPlayer; + +/** + * @abstract Delegate method invoked when the ASVideoNode is done loading the asset and can start the playback + * @param videoPlayer The videoPlayer + */ +- (void)videoPlayerNodeDidFinishInitialLoading:(ASVideoPlayerNode *)videoPlayer; + +/** + * @abstract Delegate method invoked when the ASVideoNode has recovered from the stall + * @param videoPlayer The videoplayer + */ +- (void)videoPlayerNodeDidRecoverFromStall:(ASVideoPlayerNode *)videoPlayer; + + @end NS_ASSUME_NONNULL_END #endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index cd880bcd4d..4976c59a84 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -12,19 +12,19 @@ #import "ASVideoPlayerNode.h" #import "ASDefaultPlaybackButton.h" +#import "ASDisplayNodeInternal.h" static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; @interface ASVideoPlayerNode() { - ASDN::RecursiveMutex _videoPlayerLock; - __weak id _delegate; struct { unsigned int delegateNeededDefaultControls:1; unsigned int delegateCustomControls:1; unsigned int delegateSpinnerTintColor:1; + unsigned int delegateSpinnerStyle:1; unsigned int delegatePlaybackButtonTint:1; unsigned int delegateScrubberMaximumTrackTintColor:1; unsigned int delegateScrubberMinimumTrackTintColor:1; @@ -38,10 +38,17 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; unsigned int delegateVideoNodeShouldChangeState:1; unsigned int delegateVideoNodePlaybackDidFinish:1; unsigned int delegateDidTapVideoPlayerNode:1; + unsigned int delegateVideoPlayerNodeDidSetCurrentItem:1; + unsigned int delegateVideoPlayerNodeDidStallAtTimeInterval:1; + unsigned int delegateVideoPlayerNodeDidStartInitialLoading:1; + unsigned int delegateVideoPlayerNodeDidFinishInitialLoading:1; + unsigned int delegateVideoPlayerNodeDidRecoverFromStall:1; } _delegateFlags; NSURL *_url; AVAsset *_asset; + AVVideoComposition *_videoComposition; + AVAudioMix *_audioMix; ASVideoNode *_videoNode; @@ -76,6 +83,9 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; @end @implementation ASVideoPlayerNode + +@dynamic placeholderImageURL; + - (instancetype)init { if (!(self = [super init])) { @@ -116,6 +126,22 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return self; } +-(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix +{ + if (!(self = [super init])) { + return nil; + } + + _asset = asset; + _videoComposition = videoComposition; + _audioMix = audioMix; + _loadAssetWhenNodeBecomesVisible = YES; + + [self _init]; + + return self; +} + - (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible { if (!(self = [super init])) { @@ -145,6 +171,22 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return self; } +-(instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible +{ + if (!(self = [super init])) { + return nil; + } + + _asset = asset; + _videoComposition = videoComposition; + _audioMix = audioMix; + _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible; + + [self _init]; + + return self; +} + - (void)_init { _defaultControlsColor = [UIColor whiteColor]; @@ -154,6 +196,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _videoNode.delegate = self; if (_loadAssetWhenNodeBecomesVisible == NO) { _videoNode.asset = _asset; + _videoNode.videoComposition = _videoComposition; + _videoNode.audioMix = _audioMix; } [self addSubnode:_videoNode]; } @@ -162,7 +206,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; { [super didLoad]; { - ASDN::MutexLocker l(_videoPlayerLock); + ASDN::MutexLocker l(__instanceLock__); [self createControls]; } } @@ -171,10 +215,18 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; { [super visibleStateDidChange:isVisible]; - ASDN::MutexLocker l(_videoPlayerLock); + ASDN::MutexLocker l(__instanceLock__); - if (isVisible && _loadAssetWhenNodeBecomesVisible && _asset != _videoNode.asset) { - _videoNode.asset = _asset; + if (isVisible && _loadAssetWhenNodeBecomesVisible) { + if (_asset != _videoNode.asset) { + _videoNode.asset = _asset; + } + if (_videoComposition != _videoNode.videoComposition) { + _videoNode.videoComposition = _videoComposition; + } + if (_audioMix != _videoNode.audioMix) { + _videoNode.audioMix = _audioMix; + } } } @@ -193,7 +245,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; #pragma mark - UI - (void)createControls { - ASDN::MutexLocker l(_videoPlayerLock); + ASDN::MutexLocker l(__instanceLock__); if (_controlsDisabled) { return; @@ -244,17 +296,16 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } ASPerformBlockOnMainThread(^{ - ASDN::MutexLocker l(_videoPlayerLock); + ASDN::MutexLocker l(__instanceLock__); [self setNeedsLayout]; }); } - (void)removeControls { - NSArray *controls = [_cachedControls allValues]; - [controls enumerateObjectsUsingBlock:^(ASDisplayNode *_Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { + for (ASDisplayNode *node in [_cachedControls objectEnumerator]) { [node removeFromSupernode]; - }]; + } [self cleanCachedControls]; } @@ -320,32 +371,35 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)createScrubber { if (_scrubberNode == nil) { - _scrubberNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ + __weak __typeof__(self) weakSelf = self; + _scrubberNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull { + __typeof__(self) strongSelf = weakSelf; + UISlider *slider = [[UISlider alloc] initWithFrame:CGRectZero]; slider.minimumValue = 0.0; slider.maximumValue = 1.0; if (_delegateFlags.delegateScrubberMinimumTrackTintColor) { - slider.minimumTrackTintColor = [_delegate videoPlayerNodeScrubberMinimumTrackTint:self]; + slider.minimumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMinimumTrackTint:strongSelf]; } if (_delegateFlags.delegateScrubberMaximumTrackTintColor) { - slider.maximumTrackTintColor = [_delegate videoPlayerNodeScrubberMaximumTrackTint:self]; + slider.maximumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMaximumTrackTint:strongSelf]; } if (_delegateFlags.delegateScrubberThumbTintColor) { - slider.thumbTintColor = [_delegate videoPlayerNodeScrubberThumbTint:self]; + slider.thumbTintColor = [strongSelf.delegate videoPlayerNodeScrubberThumbTint:strongSelf]; } if (_delegateFlags.delegateScrubberThumbImage) { - UIImage *thumbImage = [_delegate videoPlayerNodeScrubberThumbImage:self]; + UIImage *thumbImage = [strongSelf.delegate videoPlayerNodeScrubberThumbImage:strongSelf]; [slider setThumbImage:thumbImage forState:UIControlStateNormal]; } - [slider addTarget:self action:@selector(beginSeek) forControlEvents:UIControlEventTouchDown]; - [slider addTarget:self action:@selector(endSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; - [slider addTarget:self action:@selector(seekTimeDidChange:) forControlEvents:UIControlEventValueChanged]; + [slider addTarget:strongSelf action:@selector(beginSeek) forControlEvents:UIControlEventTouchDown]; + [slider addTarget:strongSelf action:@selector(endSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; + [slider addTarget:strongSelf action:@selector(seekTimeDidChange:) forControlEvents:UIControlEventValueChanged]; return slider; }]; @@ -475,6 +529,41 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } +- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem +{ + if (_delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem) { + [_delegate videoPlayerNode:self didSetCurrentItem:currentItem]; + } +} + +- (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval +{ + if (_delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval) { + [_delegate videoPlayerNode:self didStallAtTimeInterval:timeInterval]; + } +} + +- (void)videoNodeDidStartInitialLoading:(ASVideoNode *)videoNode +{ + if (_delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading) { + [_delegate videoPlayerNodeDidStartInitialLoading:self]; + } +} + +- (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode +{ + if (_delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading) { + [_delegate videoPlayerNodeDidFinishInitialLoading:self]; + } +} + +- (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode +{ + if (_delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall) { + [_delegate videoPlayerNodeDidRecoverFromStall:self]; + } +} + #pragma mark - Actions - (void)togglePlayPause { @@ -487,15 +576,26 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)showSpinner { - ASDN::MutexLocker l(_videoPlayerLock); + ASDN::MutexLocker l(__instanceLock__); if (!_spinnerNode) { + + __weak __typeof__(self) weakSelf = self; _spinnerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ + __typeof__(self) strongSelf = weakSelf; UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; - spinnnerView.color = _defaultControlsColor; + spinnnerView.backgroundColor = [UIColor clearColor]; + if (_delegateFlags.delegateSpinnerTintColor) { - spinnnerView.color = [_delegate videoPlayerNodeSpinnerTint:self]; + spinnnerView.color = [_delegate videoPlayerNodeSpinnerTint:strongSelf]; + } else { + spinnnerView.color = _defaultControlsColor; } + + if (_delegateFlags.delegateSpinnerStyle) { + spinnnerView.activityIndicatorViewStyle = [_delegate videoPlayerNodeSpinnerStyle:strongSelf]; + } + return spinnnerView; }]; _spinnerNode.preferredFrameSize = CGSizeMake(44.0, 44.0); @@ -508,7 +608,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)removeSpinner { - ASDN::MutexLocker l(_videoPlayerLock); + ASDN::MutexLocker l(__instanceLock__); if (!_spinnerNode) { return; @@ -677,6 +777,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _delegateFlags.delegateNeededDefaultControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededDefaultControls:)]; _delegateFlags.delegateCustomControls = [_delegate respondsToSelector:@selector(videoPlayerNodeCustomControls:)]; _delegateFlags.delegateSpinnerTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerTint:)]; + _delegateFlags.delegateSpinnerStyle = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerStyle:)]; _delegateFlags.delegateScrubberMaximumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMaximumTrackTint:)]; _delegateFlags.delegateScrubberMinimumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMinimumTrackTint:)]; _delegateFlags.delegateScrubberThumbTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbTint:)]; @@ -690,6 +791,11 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)]; _delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)]; + _delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoPlayerNode:didSetCurrentItem:)]; + _delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoPlayerNode:didStallAtTimeInterval:)]; + _delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidStartInitialLoading:)]; + _delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidFinishInitialLoading:)]; + _delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoPlayerNodeDidRecoverFromStall:)]; } } @@ -771,6 +877,16 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return _videoNode.shouldAggressivelyRecoverFromStall; } +- (void) setPlaceholderImageURL:(NSURL *)placeholderImageURL +{ + _videoNode.URL = placeholderImageURL; +} + +- (NSURL*) placeholderImageURL +{ + return _videoNode.URL; +} + - (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall { if (_shouldAggressivelyRecoverFromStall == shouldAggressivelyRecoverFromStall) { diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index 6222af8140..31ff3ed9d4 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -27,18 +27,6 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C @property (nonatomic, strong, readonly) DisplayNodeType node; -/** - * An optional context to pass along with an ASTraitCollection. - * This can be used to pass any internal state to all subnodes via the ASTraitCollection that is not - * included in UITraitCollection. This could range from more fine-tuned size classes to a class of - * constants that is based upon the new trait collection. - * - * Be aware that internally this context is held by a C struct which cannot retain the pointer. Therefore - * ASVC keeps a strong reference to the context to make sure that it stays alive. If you change this value - * it will propagate the change to the subnodes. - */ -@property (nonatomic, strong) id _Nullable traitCollectionContext; - /** * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given traitCollection. */ @@ -74,4 +62,24 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C @end -NS_ASSUME_NONNULL_END \ No newline at end of file +@interface ASViewController (ASRangeControllerUpdateRangeProtocol) + +/** + * Automatically adjust range mode based on view events. If you set this to YES, the view controller or its node + * must conform to the ASRangeControllerUpdateRangeProtocol. + * + * Default value is NO. + */ +@property (nonatomic, assign) BOOL automaticallyAdjustRangeModeBasedOnViewEvents; + +@end + +@interface ASViewController (Unavailable) + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); + +- (instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); + +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASViewController.mm b/AsyncDisplayKit/ASViewController.mm index 86417d08aa..a0e3681381 100644 --- a/AsyncDisplayKit/ASViewController.mm +++ b/AsyncDisplayKit/ASViewController.mm @@ -13,10 +13,8 @@ #import "ASViewController.h" #import "ASAssert.h" #import "ASAvailability.h" -#import "ASDimension.h" #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNode+Beta.h" #import "ASTraitCollection.h" #import "ASEnvironmentInternal.h" #import "ASRangeControllerUpdateRangeProtocol+Beta.h" @@ -29,6 +27,9 @@ BOOL _automaticallyAdjustRangeModeBasedOnViewEvents; BOOL _parentManagesVisibilityDepth; NSInteger _visibilityDepth; + BOOL _selfConformsToRangeModeProtocol; + BOOL _nodeConformsToRangeModeProtocol; + BOOL _didCheckRangeModeProtocolConformance; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil @@ -58,17 +59,6 @@ return self; } -- (void)dealloc -{ - if (_traitCollectionContext != nil) { - // The setter will iterate through the VC's subnodes and replace the traitCollectionContext in their ASEnvironmentTraitCollection with nil. - // Since the VC holds the only strong reference to this context and we are in the process of destroying - // the VC, all the references in the subnodes will be unsafe unless we nil them out. More than likely all the subnodes will be dealloc'ed - // as part of the VC being dealloc'ed, but this is just to make extra sure. - self.traitCollectionContext = nil; - } -} - - (void)loadView { ASDisplayNodeAssertTrue(!_node.layerBacked); @@ -93,6 +83,10 @@ if (AS_AT_LEAST_IOS8) { ASEnvironmentTraitCollection traitCollection = [self environmentTraitCollectionForUITraitCollection:self.traitCollection]; [self progagateNewEnvironmentTraitCollection:traitCollection]; + } else { + ASEnvironmentTraitCollection traitCollection = ASEnvironmentTraitCollectionMakeDefault(); + traitCollection.containerSize = self.view.bounds.size; + [self progagateNewEnvironmentTraitCollection:traitCollection]; } } @@ -100,6 +94,10 @@ { [super viewWillLayoutSubviews]; [_node measureWithSizeRange:[self nodeConstrainedSize]]; + + if (!AS_AT_LEAST_IOS9) { + [self _legacyHandleViewDidLayoutSubviews]; + } } - (void)viewDidLayoutSubviews @@ -176,20 +174,37 @@ ASVisibilityDepthImplementation; - (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode { if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { return; } - if (![_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]) { - return; + + if (!_didCheckRangeModeProtocolConformance) { + _selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; + _nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; + _didCheckRangeModeProtocolConformance = YES; + if (!_selfConformsToRangeModeProtocol && !_nodeConformsToRangeModeProtocol) { + NSLog(@"Warning: automaticallyAdjustRangeModeBasedOnViewEvents set to YES in %@, but range mode updating is not possible because neither view controller nor node %@ conform to ASRangeControllerUpdateRangeProtocol.", self, _node); + } + } + + if (_selfConformsToRangeModeProtocol) { + id rangeUpdater = (id)self; + [rangeUpdater updateCurrentRangeWithMode:rangeMode]; + } + + if (_nodeConformsToRangeModeProtocol) { + id rangeUpdater = (id)_node; + [rangeUpdater updateCurrentRangeWithMode:rangeMode]; } - - id updateRangeNode = (id)_node; - [updateRangeNode updateCurrentRangeWithMode:rangeMode]; } #pragma mark - Layout Helpers - (ASSizeRange)nodeConstrainedSize { - CGSize viewSize = self.view.bounds.size; - return ASSizeRangeMake(viewSize, viewSize); + if (AS_AT_LEAST_IOS9) { + CGSize viewSize = self.view.bounds.size; + return ASSizeRangeMake(viewSize, viewSize); + } else { + return [self _legacyConstrainedSize]; + } } - (ASInterfaceState)interfaceState @@ -197,29 +212,63 @@ ASVisibilityDepthImplementation; return _node.interfaceState; } -#pragma mark - ASEnvironmentTraitCollection +#pragma mark - Legacy Layout Handling -- (void)setTraitCollectionContext:(id)traitCollectionContext +- (BOOL)_shouldLayoutTheLegacyWay { - if (_traitCollectionContext != traitCollectionContext) { - // nil out the displayContext in the subnodes so they aren't hanging around with a dealloc'ed pointer don't set - // the new context yet as this will cause ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection to fail - ASEnvironmentTraitCollectionUpdateDisplayContext(self.node, nil); - - _traitCollectionContext = traitCollectionContext; + BOOL isModalViewController = (self.presentingViewController != nil && self.presentedViewController == nil); + BOOL hasNavigationController = (self.navigationController != nil); + BOOL hasParentViewController = (self.parentViewController != nil); + if (isModalViewController && !hasNavigationController && !hasParentViewController) { + return YES; + } + + // Check if the view controller is a root view controller + BOOL isRootViewController = self.view.window.rootViewController == self; + if (isRootViewController) { + return YES; + } + + return NO; +} + +- (ASSizeRange)_legacyConstrainedSize +{ + // In modal presentation the view does not have the right bounds in iOS7 and iOS8. As workaround using the superviews + // view bounds + UIView *view = self.view; + CGSize viewSize = view.bounds.size; + if ([self _shouldLayoutTheLegacyWay]) { + UIView *superview = view.superview; + if (superview != nil) { + viewSize = superview.bounds.size; + } + } + return ASSizeRangeMake(viewSize, viewSize); +} + +- (void)_legacyHandleViewDidLayoutSubviews +{ + // In modal presentation or as root viw controller the view does not automatic resize in iOS7 and iOS8. + // As workaround we adjust the frame of the view manually + if ([self _shouldLayoutTheLegacyWay]) { + CGSize maxConstrainedSize = [self nodeConstrainedSize].max; + _node.frame = (CGRect){.origin = CGPointZero, .size = maxConstrainedSize}; } } +#pragma mark - ASEnvironmentTraitCollection + - (ASEnvironmentTraitCollection)environmentTraitCollectionForUITraitCollection:(UITraitCollection *)traitCollection { if (self.overrideDisplayTraitsWithTraitCollection) { ASTraitCollection *asyncTraitCollection = self.overrideDisplayTraitsWithTraitCollection(traitCollection); - self.traitCollectionContext = asyncTraitCollection.traitCollectionContext; return [asyncTraitCollection environmentTraitCollection]; } + ASDisplayNodeAssertMainThread(); ASEnvironmentTraitCollection asyncTraitCollection = ASEnvironmentTraitCollectionFromUITraitCollection(traitCollection); - asyncTraitCollection.displayContext = self.traitCollectionContext; + asyncTraitCollection.containerSize = self.view.frame.size; return asyncTraitCollection; } @@ -227,10 +276,11 @@ ASVisibilityDepthImplementation; { if (self.overrideDisplayTraitsWithWindowSize) { ASTraitCollection *traitCollection = self.overrideDisplayTraitsWithWindowSize(windowSize); - self.traitCollectionContext = traitCollection.traitCollectionContext; return [traitCollection environmentTraitCollection]; } - return self.node.environmentTraitCollection; + ASEnvironmentTraitCollection traitCollection = self.node.environmentTraitCollection; + traitCollection.containerSize = windowSize; + return traitCollection; } - (void)progagateNewEnvironmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection @@ -255,6 +305,7 @@ ASVisibilityDepthImplementation; [super traitCollectionDidChange:previousTraitCollection]; ASEnvironmentTraitCollection environmentTraitCollection = [self environmentTraitCollectionForUITraitCollection:self.traitCollection]; + environmentTraitCollection.containerSize = self.view.bounds.size; [self progagateNewEnvironmentTraitCollection:environmentTraitCollection]; } @@ -262,8 +313,12 @@ ASVisibilityDepthImplementation; { [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator]; - ASEnvironmentTraitCollection environmentTraitCollection = [self environmentTraitCollectionForUITraitCollection:newCollection]; - [self progagateNewEnvironmentTraitCollection:environmentTraitCollection]; + // 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; } - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator @@ -274,4 +329,13 @@ ASVisibilityDepthImplementation; [self progagateNewEnvironmentTraitCollection:environmentTraitCollection]; } +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation +{ + [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; + + ASEnvironmentTraitCollection traitCollection = self.node.environmentTraitCollection; + traitCollection.containerSize = self.view.bounds.size; + [self progagateNewEnvironmentTraitCollection:traitCollection]; +} + @end diff --git a/AsyncDisplayKit/ASVisibilityProtocols.m b/AsyncDisplayKit/ASVisibilityProtocols.m index 1b59dde807..62f9879e14 100644 --- a/AsyncDisplayKit/ASVisibilityProtocols.m +++ b/AsyncDisplayKit/ASVisibilityProtocols.m @@ -10,8 +10,6 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import - #import "ASVisibilityProtocols.h" ASLayoutRangeMode ASLayoutRangeModeForVisibilityDepth(NSUInteger visibilityDepth) diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 0064993276..bdb769ec43 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -20,6 +20,7 @@ #import #import +#import #import #import #import @@ -28,6 +29,8 @@ #import #import #import +#import +#import #import #import @@ -75,15 +78,17 @@ #import #import #import -#import -#import -#import -#import #import #import #import #import +#import +#import +#import +#import +#import + #import #import diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.h b/AsyncDisplayKit/Details/ASAbstractLayoutController.h index 09f590399e..bcbbd89d01 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.h +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.h @@ -17,4 +17,10 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ASAbstractLayoutController (Unavailable) + +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType __unavailable; + +@end + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index 9112108317..b0ef11b350 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -91,15 +91,13 @@ extern BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningPar - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), - @"Requesting a range that is OOB for the configured tuning parameters"); + ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters"); return _tuningParameters[rangeMode][rangeType]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), - @"Setting a range that is OOB for the configured tuning parameters"); + ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters"); _tuningParameters[rangeMode][rangeType] = tuningParameters; } diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm index 37f32d554a..bf84475b88 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -12,8 +12,6 @@ #import -#import - #import "ASBasicImageDownloaderInternal.h" #import "ASThread.h" @@ -33,7 +31,7 @@ NSString * const kASBasicImageDownloaderContextCompletionBlock = @"kASBasicImage @interface ASBasicImageDownloaderContext () { BOOL _invalid; - ASDN::RecursiveMutex _propertyLock; + ASDN::RecursiveMutex __instanceLock__; } @property (nonatomic, strong) NSMutableArray *callbackDatas; @@ -78,7 +76,7 @@ static ASDN::RecursiveMutex currentRequestsLock; - (void)cancel { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); NSURLSessionTask *sessionTask = self.sessionTask; if (sessionTask) { @@ -92,19 +90,19 @@ static ASDN::RecursiveMutex currentRequestsLock; - (BOOL)isCancelled { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _invalid; } - (void)addCallbackData:(NSDictionary *)callbackData { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); [self.callbackDatas addObject:callbackData]; } - (void)performProgressBlocks:(CGFloat)progress { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); for (NSDictionary *callbackData in self.callbackDatas) { ASBasicImageDownloaderContextProgressBlock progressBlock = callbackData[kASBasicImageDownloaderContextProgressBlock]; dispatch_queue_t callbackQueue = callbackData[kASBasicImageDownloaderContextCallbackQueue]; @@ -119,7 +117,7 @@ static ASDN::RecursiveMutex currentRequestsLock; - (void)completeWithImage:(UIImage *)image error:(NSError *)error { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); for (NSDictionary *callbackData in self.callbackDatas) { ASBasicImageDownloaderContextCompletionBlock completionBlock = callbackData[kASBasicImageDownloaderContextCompletionBlock]; dispatch_queue_t callbackQueue = callbackData[kASBasicImageDownloaderContextCallbackQueue]; @@ -137,7 +135,7 @@ static ASDN::RecursiveMutex currentRequestsLock; - (NSURLSessionTask *)createSessionTaskIfNecessaryWithBlock:(NSURLSessionTask *(^)())creationBlock { { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (self.isCancelled) { return nil; @@ -151,7 +149,7 @@ static ASDN::RecursiveMutex currentRequestsLock; NSURLSessionTask *newTask = creationBlock(); { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (self.isCancelled) { return nil; diff --git a/AsyncDisplayKit/Details/ASBatchContext.mm b/AsyncDisplayKit/Details/ASBatchContext.mm index 7a5efa766b..94c148a804 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.mm +++ b/AsyncDisplayKit/Details/ASBatchContext.mm @@ -21,7 +21,7 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { @interface ASBatchContext () { ASBatchContextState _state; - ASDN::RecursiveMutex _propertyLock; + ASDN::RecursiveMutex __instanceLock__; } @end @@ -37,33 +37,33 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { - (BOOL)isFetching { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _state == ASBatchContextStateFetching; } - (BOOL)batchFetchingWasCancelled { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _state == ASBatchContextStateCancelled; } - (void)beginBatchFetching { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _state = ASBatchContextStateFetching; } - (void)completeBatchFetching:(BOOL)didComplete { if (didComplete) { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _state = ASBatchContextStateCompleted; } } - (void)cancelBatchFetching { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _state = ASBatchContextStateCancelled; } diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.mm similarity index 55% rename from AsyncDisplayKit/Details/ASChangeSetDataController.m rename to AsyncDisplayKit/Details/ASChangeSetDataController.mm index 21fe2b8842..b85aacf817 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.mm @@ -11,30 +11,13 @@ // #import "ASChangeSetDataController.h" -#import "ASInternalHelpers.h" #import "_ASHierarchyChangeSet.h" #import "ASAssert.h" - #import "ASDataController+Subclasses.h" -@interface ASChangeSetDataController () - -@property (nonatomic, assign) NSUInteger changeSetBatchUpdateCounter; -@property (nonatomic, strong) _ASHierarchyChangeSet *changeSet; - -@end - -@implementation ASChangeSetDataController - -- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled -{ - if (!(self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled])) { - return nil; - } - - _changeSetBatchUpdateCounter = 0; - - return self; +@implementation ASChangeSetDataController { + NSInteger _changeSetBatchUpdateCounter; + _ASHierarchyChangeSet *_changeSet; } #pragma mark - Batching (External API) @@ -42,8 +25,9 @@ - (void)beginUpdates { ASDisplayNodeAssertMainThread(); - if (_changeSetBatchUpdateCounter == 0) { - _changeSet = [_ASHierarchyChangeSet new]; + if (_changeSetBatchUpdateCounter <= 0) { + _changeSetBatchUpdateCounter = 0; + _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[self itemCountsFromDataSource]]; } _changeSetBatchUpdateCounter++; } @@ -53,11 +37,23 @@ ASDisplayNodeAssertMainThread(); _changeSetBatchUpdateCounter--; + // Prevent calling endUpdatesAnimated:completion: in an unbalanced way + NSAssert(_changeSetBatchUpdateCounter >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); + if (_changeSetBatchUpdateCounter == 0) { - [_changeSet markCompleted]; + if (!self.initialReloadDataHasBeenCalled) { + if (completion) { + completion(YES); + } + _changeSet = nil; + return; + } + + [self invalidateDataSourceItemCounts]; + [_changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; [super beginUpdates]; - + for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { [super deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; } @@ -65,15 +61,7 @@ for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { [super deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; } - - for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - + for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { [super insertSections:change.indexSet withAnimationOptions:change.animationOptions]; } @@ -101,42 +89,34 @@ - (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - if ([self batchUpdating]) { - [_changeSet insertSections:sections animationOptions:animationOptions]; - } else { - [super insertSections:sections withAnimationOptions:animationOptions]; - } + [self beginUpdates]; + [_changeSet insertSections:sections animationOptions:animationOptions]; + [self endUpdates]; } - (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - if ([self batchUpdating]) { - [_changeSet deleteSections:sections animationOptions:animationOptions]; - } else { - [super deleteSections:sections withAnimationOptions:animationOptions]; - } + [self beginUpdates]; + [_changeSet deleteSections:sections animationOptions:animationOptions]; + [self endUpdates]; } - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - if ([self batchUpdating]) { - [_changeSet reloadSections:sections animationOptions:animationOptions]; - } else { - [super reloadSections:sections withAnimationOptions:animationOptions]; - } + [self beginUpdates]; + [_changeSet reloadSections:sections animationOptions:animationOptions]; + [self endUpdates]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - if ([self batchUpdating]) { - [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; - [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; - } else { - [super moveSection:section toSection:newSection withAnimationOptions:animationOptions]; - } + [self beginUpdates]; + [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; + [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; + [self endUpdates]; } #pragma mark - Row Editing (External API) @@ -144,42 +124,34 @@ - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - if ([self batchUpdating]) { - [_changeSet insertItems:indexPaths animationOptions:animationOptions]; - } else { - [super insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - } + [self beginUpdates]; + [_changeSet insertItems:indexPaths animationOptions:animationOptions]; + [self endUpdates]; } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - if ([self batchUpdating]) { - [_changeSet deleteItems:indexPaths animationOptions:animationOptions]; - } else { - [super deleteRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - } + [self beginUpdates]; + [_changeSet deleteItems:indexPaths animationOptions:animationOptions]; + [self endUpdates]; } - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - if ([self batchUpdating]) { - [_changeSet reloadItems:indexPaths animationOptions:animationOptions]; - } else { - [super reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - } + [self beginUpdates]; + [_changeSet reloadItems:indexPaths animationOptions:animationOptions]; + [self endUpdates]; } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssertMainThread(); - if ([self batchUpdating]) { - [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; - [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; - } else { - [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions]; - } + [self beginUpdates]; + [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; + [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; + [self endUpdates]; } @end diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index c1534ea827..2f19e09757 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -40,6 +40,8 @@ @interface ASCollectionDataController : ASChangeSetDataController +- (instancetype)initWithDataSource:(id)dataSource NS_DESIGNATED_INITIALIZER; + - (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; @end \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index c29fef148f..8f5d505815 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -13,7 +13,6 @@ #import "ASAssert.h" #import "ASMultidimensionalArrayUtils.h" #import "ASCellNode.h" -#import "ASDisplayNodeInternal.h" #import "ASDataController+Subclasses.h" #import "ASIndexedNodeContext.h" @@ -32,11 +31,14 @@ NSMutableDictionary *> *_pendingContexts; } -- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled +- (instancetype)initWithDataSource:(id)dataSource { - self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled]; + self = [super initWithDataSource:dataSource]; if (self != nil) { _pendingContexts = [NSMutableDictionary dictionary]; + _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; + + ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); } return self; } @@ -53,15 +55,13 @@ - (void)willReloadData { - NSArray *keys = _pendingContexts.allKeys; - for (NSString *kind in keys) { - NSMutableArray *contexts = _pendingContexts[kind]; + [_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 NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; NSArray *editingNodes = [self editingNodesOfKind:kind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; + NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; // Insert each section @@ -72,11 +72,11 @@ } [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; - [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingContexts removeObjectForKey:kind]; - } + }]; + [_pendingContexts removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections @@ -91,20 +91,18 @@ - (void)willInsertSections:(NSIndexSet *)sections { - NSArray *keys = _pendingContexts.allKeys; - for (NSString *kind in keys) { - NSMutableArray *contexts = _pendingContexts[kind]; + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingContexts removeObjectForKey:kind]; - } + }]; + [_pendingContexts removeAllObjects]; } - (void)willDeleteSections:(NSIndexSet *)sections @@ -117,43 +115,22 @@ } } -- (void)prepareForReloadSections:(NSIndexSet *)sections -{ - for (NSString *kind in [self supplementaryKinds]) { - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; - _pendingContexts[kind] = contexts; - } -} - -- (void)willReloadSections:(NSIndexSet *)sections -{ - NSArray *keys = _pendingContexts.allKeys; - for (NSString *kind in keys) { - NSMutableArray *contexts = _pendingContexts[kind]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - // reinsert the elements - [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - [_pendingContexts removeObjectForKey:kind]; - } -} - - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { + NSIndexSet *sectionAsIndexSet = [NSIndexSet indexSetWithIndex:section]; for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); + NSMutableArray *editingNodes = [self editingNodesOfKind:kind]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingNodes, sectionAsIndexSet); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; - }]; + for (NSIndexPath *indexPath in indexPaths) { + NSUInteger newItem = [indexPath indexAtPosition:indexPath.length - 1]; + NSIndexPath *mappedIndexPath = [NSIndexPath indexPathForItem:newItem inSection:newSection]; + [updatedIndexPaths addObject:mappedIndexPath]; + } [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -170,26 +147,16 @@ - (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths { - NSArray *keys = _pendingContexts.allKeys; - for (NSString *kind in keys) { - NSMutableArray *contexts = _pendingContexts[kind]; - - [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { + [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingContexts removeObjectForKey:kind]; - } + }]; + + [_pendingContexts removeAllObjects]; } -- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - for (NSString *kind in [self supplementaryKinds]) { - NSArray *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths); - [self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil]; - } -} - -- (void)prepareForReloadRowsAtIndexPaths:(NSArray *)indexPaths +- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths { for (NSString *kind in [self supplementaryKinds]) { NSMutableArray *contexts = [NSMutableArray array]; @@ -198,19 +165,27 @@ } } -- (void)willReloadRowsAtIndexPaths:(NSArray *)indexPaths +- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths { - NSArray *keys = _pendingContexts.allKeys; - for (NSString *kind in keys) { - NSMutableArray *contexts = _pendingContexts[kind]; + for (NSString *kind in [self supplementaryKinds]) { + NSArray *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - // reinsert the elements - [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil]; + + // If any of the contexts remain after the deletion, re-insert them, e.g. + // UICollectionElementKindSectionHeader remains even if item 0 is deleted. + NSMutableArray *reinsertedContexts = [NSMutableArray array]; + for (ASIndexedNodeContext *context in _pendingContexts[kind]) { + if ([deletedIndexPaths containsObject:context.indexPath]) { + [reinsertedContexts addObject:context]; + } + } + + [self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingContexts removeObjectForKey:kind]; } + [_pendingContexts removeAllObjects]; } - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray *)contexts @@ -218,12 +193,12 @@ id environment = [self.environmentDelegate dataControllerEnvironment]; ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; - NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; + id source = self.collectionDataSource; + NSUInteger sectionCount = [source dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; for (NSUInteger i = 0; i < sectionCount; i++) { - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i]; - NSUInteger rowCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:i]; + NSUInteger rowCount = [source dataController:self supplementaryNodesOfKind:kind inSection:i]; for (NSUInteger j = 0; j < rowCount; j++) { - NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i]; [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection]; } } @@ -234,12 +209,13 @@ id environment = [self.environmentDelegate dataControllerEnvironment]; ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSUInteger rowNum = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:idx]; - NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; - for (NSUInteger i = 0; i < rowNum; i++) { - NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection]; + [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { + NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; + for (NSUInteger i = 0; i < itemCount; i++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; + [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection]; + } } }]; } @@ -254,32 +230,33 @@ [sections addIndex:indexPath.section]; } - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSUInteger rowNum = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:idx]; - NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; - for (NSUInteger i = 0; i < rowNum; i++) { - NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection]; + [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { + NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; + for (NSUInteger i = 0; i < itemCount; i++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; + [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection]; + } } }]; } - (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts environmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection { - ASCellNodeBlock supplementaryCellBlock; - if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { - supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; - } else { - ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; - supplementaryCellBlock = ^{ return supplementaryNode; }; - } - - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock - indexPath:indexPath - constrainedSize:constrainedSize - environmentTraitCollection:environmentTraitCollection]; - [contexts addObject:context]; + ASCellNodeBlock supplementaryCellBlock; + if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { + supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; + } else { + ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; + supplementaryCellBlock = ^{ return supplementaryNode; }; + } + + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock + indexPath:indexPath + constrainedSize:constrainedSize + environmentTraitCollection:environmentTraitCollection]; + [contexts addObject:context]; } #pragma mark - Sizing query @@ -307,8 +284,7 @@ return nodesOfKindInSection[itemIndex]; } } - ASDisplayNodeAssert(NO, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self.collectionDataSource); - return [[ASCellNode alloc] init]; + return nil; } #pragma mark - Private Helpers @@ -323,12 +299,4 @@ return (id)self.dataSource; } -- (void)setDataSource:(id)dataSource -{ - [super setDataSource:dataSource]; - _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; - - ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); -} - @end diff --git a/AsyncDisplayKit/Details/ASCollectionInternal.h b/AsyncDisplayKit/Details/ASCollectionInternal.h index d110b85634..703409b075 100644 --- a/AsyncDisplayKit/Details/ASCollectionInternal.h +++ b/AsyncDisplayKit/Details/ASCollectionInternal.h @@ -10,10 +10,12 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASCollectionView.h" -#import "ASCollectionNode.h" -#import "ASDataController.h" -#import "ASRangeController.h" +#import + +@protocol ASCollectionViewLayoutFacilitatorProtocol; +@class ASCollectionNode; +@class ASDataController; +@class ASRangeController; @interface ASCollectionView () - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator ownedByNode:(BOOL)ownedByNode; diff --git a/AsyncDisplayKit/Details/ASCollectionInternal.m b/AsyncDisplayKit/Details/ASCollectionInternal.m index e7f68bd978..fd39342a48 100644 --- a/AsyncDisplayKit/Details/ASCollectionInternal.m +++ b/AsyncDisplayKit/Details/ASCollectionInternal.m @@ -10,4 +10,3 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASCollectionInternal.h" diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index 661f140628..423c8302a7 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -14,15 +14,20 @@ #import @class ASCollectionView; +@protocol ASCollectionDataSource; @protocol ASCollectionDelegate; +NS_ASSUME_NONNULL_BEGIN + @protocol ASCollectionViewLayoutInspecting /** - * Provides the size range needed to measure the collection view's item. + * Asks the inspector to provide a constarained size range for the given collection view node. */ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; +@optional + /** * Asks the inspector to provide a constrained size range for the given supplementary node. */ @@ -38,21 +43,44 @@ */ - (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; -@optional - /** * Allow the inspector to respond to delegate changes. * * @discussion A great time to update perform selector caches! */ -- (void)didChangeCollectionViewDelegate:(id)delegate; +- (void)didChangeCollectionViewDelegate:(nullable id)delegate; + +/** + * Allow the inspector to respond to dataSource changes. + * + * @discussion A great time to update perform selector caches! + */ +- (void)didChangeCollectionViewDataSource:(nullable id)dataSource; @end +/** + * A layout inspector for non-flow layouts that returns a constrained size to let the cells layout itself as + * far as possible based on the scrollable direction of the collection view. It throws exceptions for delegate + * methods that are related to supplementary node's management. + */ +@interface ASCollectionViewLayoutInspector : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView NS_DESIGNATED_INITIALIZER; + +@end + +/** + * A layout inspector implementation specific for the sizing behavior of UICollectionViewFlowLayouts + */ @interface ASCollectionViewFlowLayoutInspector : NSObject -@property (nonatomic, weak) UICollectionViewFlowLayout *layout; +@property (nonatomic, weak, readonly) UICollectionViewFlowLayout *layout; -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout NS_DESIGNATED_INITIALIZER; @end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index f217571193..25ba06b71d 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -8,52 +8,155 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import - #import "ASCollectionViewFlowLayoutInspector.h" #import "ASCollectionView.h" #import "ASAssert.h" #import "ASEqualityHelpers.h" -@implementation ASCollectionViewFlowLayoutInspector { - BOOL _delegateImplementsReferenceSizeForHeader; - BOOL _delegateImplementsReferenceSizeForFooter; +#define kDefaultItemSize CGSizeMake(50, 50) + +#pragma mark - Helper Functions + +// Returns a constrained size to let the cells layout itself as far as possible based on the scrollable direction +// of the collection view +static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView) { + CGSize maxSize = collectionView.bounds.size; + if (ASScrollDirectionContainsHorizontalDirection(collectionView.scrollableDirections)) { + maxSize.width = FLT_MAX; + } else { + maxSize.height = FLT_MAX; + } + return ASSizeRangeMake(CGSizeZero, maxSize); } -#pragma mark - Accessors +#pragma mark - ASCollectionViewLayoutInspector + +@implementation ASCollectionViewLayoutInspector { + struct { + unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; + } _dataSourceFlags; +} + +#pragma mark Lifecycle + +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView +{ + self = [super init]; + if (self != nil) { + [self didChangeCollectionViewDataSource:collectionView.asyncDataSource]; + } + return self; +} + +#pragma mark ASCollectionViewLayoutInspecting + +- (void)didChangeCollectionViewDataSource:(id)dataSource +{ + if (dataSource == nil) { + memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags)); + } else { + _dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath = [dataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; + } +} + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +{ + if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { + return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; + } + + return NodeConstrainedSizeForScrollDirection(collectionView); +} + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); + 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.)"); + return 0; +} + +@end + + +#pragma mark - ASCollectionViewFlowLayoutInspector + +@interface ASCollectionViewFlowLayoutInspector () +@property (nonatomic, weak) UICollectionViewFlowLayout *layout; +@end + +@implementation ASCollectionViewFlowLayoutInspector { + struct { + unsigned int implementsReferenceSizeForHeader:1; + unsigned int implementsReferenceSizeForFooter:1; + } _delegateFlags; + + struct { + unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; + unsigned int implementsNumberOfSectionsInCollectionView:1; + } _dataSourceFlags; +} + +#pragma mark Lifecycle - (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; { + NSParameterAssert(collectionView); + NSParameterAssert(flowLayout); + self = [super init]; - - if (flowLayout == nil) { - ASDisplayNodeAssert(NO, @"Should never create a layout inspector without a layout"); - } - if (self != nil) { + [self didChangeCollectionViewDataSource:collectionView.asyncDataSource]; [self didChangeCollectionViewDelegate:collectionView.asyncDelegate]; _layout = flowLayout; } return self; } +#pragma mark ASCollectionViewLayoutInspecting + - (void)didChangeCollectionViewDelegate:(id)delegate; { if (delegate == nil) { - _delegateImplementsReferenceSizeForHeader = NO; - _delegateImplementsReferenceSizeForFooter = NO; + memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { - _delegateImplementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; - _delegateImplementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; + _delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; + _delegateFlags.implementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; } } -#pragma mark - ASCollectionViewLayoutInspecting +- (void)didChangeCollectionViewDataSource:(id)dataSource +{ + 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 { - // TODO: Provide constrained size for flow layout item nodes - return ASSizeRangeMake(CGSizeZero, CGSizeZero); + if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { + return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; + } + + CGSize itemSize = _layout.itemSize; + if (CGSizeEqualToSize(itemSize, kDefaultItemSize) == NO) { + return ASSizeRangeMake(itemSize, itemSize); + } + + return NodeConstrainedSizeForScrollDirection(collectionView); } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath @@ -61,16 +164,16 @@ CGSize constrainedSize; CGSize supplementarySize = [self sizeForSupplementaryViewOfKind:kind inSection:indexPath.section collectionView:collectionView]; if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) { - constrainedSize = CGSizeMake(collectionView.bounds.size.width, supplementarySize.height); + constrainedSize = CGSizeMake(CGRectGetWidth(collectionView.bounds), supplementarySize.height); } else { - constrainedSize = CGSizeMake(supplementarySize.height, collectionView.bounds.size.height); + constrainedSize = CGSizeMake(supplementarySize.width, CGRectGetHeight(collectionView.bounds)); } return ASSizeRangeMake(CGSizeZero, constrainedSize); } - (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind { - if ([collectionView.asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) { + if (_dataSourceFlags.implementsNumberOfSectionsInCollectionView) { return [collectionView.asyncDataSource numberOfSectionsInCollectionView:collectionView]; } else { return 1; @@ -87,13 +190,13 @@ - (CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView { if (ASObjectIsEqual(kind, UICollectionElementKindSectionHeader)) { - if (_delegateImplementsReferenceSizeForHeader) { + if (_delegateFlags.implementsReferenceSizeForHeader) { return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:section]; } else { return [self.layout headerReferenceSize]; } } else if (ASObjectIsEqual(kind, UICollectionElementKindSectionFooter)) { - if (_delegateImplementsReferenceSizeForFooter) { + if (_delegateFlags.implementsReferenceSizeForFooter) { return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:section]; } else { return [self.layout footerReferenceSize]; diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm index f7e63f3d6e..6c31beaa82 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -10,13 +10,10 @@ #import "ASCollectionViewLayoutController.h" -#include - #import "ASAssert.h" #import "ASCollectionView.h" #import "CGRect+ASConvenience.h" #import "UICollectionViewLayout+ASConvenience.h" -#import "ASDisplayNodeExtras.h" struct ASRangeGeometry { CGRect rangeBounds; diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 74ed8f0edb..7d748663dd 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -57,17 +57,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController; -/** - Lock the data source for data fetching. - */ -- (void)dataControllerLockDataSource; - -/** - Unlock the data source after data fetching. - */ -- (void)dataControllerUnlockDataSource; - - @end @protocol ASDataControllerEnvironmentDelegate @@ -120,10 +109,12 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; @protocol ASFlowLayoutControllerDataSource; @interface ASDataController : ASDealloc2MainObject +- (instancetype)initWithDataSource:(id)dataSource NS_DESIGNATED_INITIALIZER; + /** Data source for fetching data info. */ -@property (nonatomic, weak) id dataSource; +@property (nonatomic, weak, readonly) id dataSource; /** Delegate to notify when data is updated. @@ -136,18 +127,13 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; @property (nonatomic, weak) id environmentDelegate; /** - * Designated initializer. + * Returns YES if reloadData has been called at least once. Before this point it is + * important to ignore/suppress some operations. For example, inserting a section + * before the initial data load should have no effect. * - * @param asyncDataFetchingEnabled Enable the data fetching in async mode. - * - * @discussion If enabled, we will fetch data through `dataController:nodeAtIndexPath:` and `dataController:rowsInSection:` in background thread. - * Otherwise, the methods will be invoked synchronically in calling thread. Enabling data fetching in async mode could avoid blocking main thread - * while allocating cell on main thread, which is frequently reported issue for handling large scale data. On another hand, the application code - * will take the responsibility to avoid data inconsistency. Specifically, we will lock the data source through `dataControllerLockDataSource`, - * and unlock it by `dataControllerUnlockDataSource` after the data fetching. The application should not update the data source while - * the data source is locked. + * This must be called on the main thread. */ -- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; +@property (nonatomic, readonly) BOOL initialReloadDataHasBeenCalled; /** @name Data Updating */ @@ -197,8 +183,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; -- (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths; - /** * Direct access to the nodes that have completed calculation and layout */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 296f67b81d..f738b55e01 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -10,14 +10,9 @@ #import "ASDataController.h" -#import - #import "ASAssert.h" #import "ASCellNode.h" -#import "ASDisplayNode.h" #import "ASEnvironmentInternal.h" -#import "ASFlowLayoutController.h" -#import "ASInternalHelpers.h" #import "ASLayout.h" #import "ASMainSerialQueue.h" #import "ASMultidimensionalArrayUtils.h" @@ -28,23 +23,36 @@ //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) +#define AS_MEASURE_AVOIDED_DATACONTROLLER_WORK 0 + +#define RETURN_IF_NO_DATASOURCE(val) if (_dataSource == nil) { return val; } +#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) + const static NSUInteger kASDataControllerSizingCountPerProcessor = 5; +const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey"; +const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext"; NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; -static void *kASSizingQueueContext = &kASSizingQueueContext; +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK +@interface ASDataController (AvoidedWorkMeasuring) ++ (void)_didLayoutNode; ++ (void)_expectToInsertNodes:(NSUInteger)count; +@end +#endif @interface ASDataController () { NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propagated to _completedNodes. + BOOL _itemCountsFromDataSourceAreValid; // Main thread only. + std::vector _itemCountsFromDataSource; // Main thread only. ASMainSerialQueue *_mainSerialQueue; NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. - NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. - - BOOL _asyncDataFetchingEnabled; + 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. BOOL _initialReloadDataHasBeenCalled; @@ -54,7 +62,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; BOOL _delegateDidDeleteSections; } -@property (atomic, assign) NSUInteger batchUpdateCounter; +@property (nonatomic, assign) NSUInteger batchUpdateCounter; @end @@ -62,11 +70,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Lifecycle -- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled +- (instancetype)initWithDataSource:(id)dataSource { if (!(self = [super init])) { return nil; } + ASDisplayNodeAssert(![self isMemberOfClass:[ASDataController class]], @"ASDataController is an abstract class and should not be instantiated. Instantiate a subclass instead."); + + _dataSource = dataSource; _completedNodes = [NSMutableDictionary dictionary]; _editingNodes = [NSMutableDictionary dictionary]; @@ -78,16 +89,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; _pendingEditCommandBlocks = [NSMutableArray array]; - _editingTransactionQueue = [[NSOperationQueue alloc] init]; - _editingTransactionQueue.maxConcurrentOperationCount = 1; // Serial queue - _editingTransactionQueue.name = @"org.AsyncDisplayKit.ASDataController.editingTransactionQueue"; + 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; - _asyncDataFetchingEnabled = asyncDataFetchingEnabled; return self; } +- (instancetype)init +{ + ASDisplayNodeFailAssert(@"Failed to call designated initializer."); + id fakeDataSource = nil; + return [self initWithDataSource:fakeDataSource]; +} + - (void)setDelegate:(id)delegate { if (_delegate == delegate) { @@ -117,8 +135,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock +- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler { + ASSERT_ON_EDITING_QUEUE; +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + [ASDataController _expectToInsertNodes:contexts.count]; +#endif + NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; NSUInteger count = contexts.count; @@ -126,141 +149,77 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger i = 0; i < count; i += blockSize) { NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize)); NSArray *batchedContexts = [contexts subarrayWithRange:batchedRange]; - [self _layoutNodesFromContexts:batchedContexts ofKind:kind completion:completionBlock]; + NSArray *nodes = [self _layoutNodesFromContexts:batchedContexts]; + NSArray *indexPaths = [ASIndexedNodeContext indexPathsFromContexts:batchedContexts]; + batchCompletionHandler(nodes, indexPaths); } } -- (void)layoutLoadedNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts ofKind:(NSString *)kind -{ - NSAssert(ASDisplayNodeThreadIsMain(), @"Layout of loaded nodes must happen on the main thread."); - ASDisplayNodeAssertTrue(nodes.count == contexts.count); - - [self _layoutNodes:nodes fromContexts:contexts atIndexesOfRange:NSMakeRange(0, nodes.count) ofKind:kind]; -} - /** * Measure and layout the given node with the constrained size range. */ - (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize { - [node measureWithSizeRange:constrainedSize]; - node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); + CGRect frame = CGRectZero; + frame.size = [node measureWithSizeRange:constrainedSize].size; + node.frame = frame; } /** * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. */ -- (void)_batchLayoutNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)_batchLayoutAndInsertNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(NSArray *nodes, NSArray *indexPaths) { + ASSERT_ON_EDITING_QUEUE; + + [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { // Insert finished nodes into data storage [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; } -/** - * Perform measurement and layout of loaded or unloaded nodes based if they will be layed out on main thread or not - */ -- (void)_layoutNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts atIndexesOfRange:(NSRange)range ofKind:(NSString *)kind +- (NSArray *)_layoutNodesFromContexts:(NSArray *)contexts { - if (_dataSource == nil) { - return; - } + ASSERT_ON_EDITING_QUEUE; - // For any given layout pass that occurs, this method will be called at least twice, once on the main thread and - // the background, to result in complete coverage of both loaded and unloaded nodes - BOOL isMainThread = ASDisplayNodeThreadIsMain(); - for (NSUInteger k = range.location; k < NSMaxRange(range); k++) { - ASCellNode *node = nodes[k]; - // Only nodes that are loaded should be layout on the main thread - if (node.isNodeLoaded == isMainThread) { - ASIndexedNodeContext *context = contexts[k]; - [self _layoutNode:node withConstrainedSize:context.constrainedSize]; - } - } -} - -- (void)_layoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock -{ - if (!contexts.count || _dataSource == nil) { - return; - } - NSUInteger nodeCount = contexts.count; - NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount]; - dispatch_group_t layoutGroup = dispatch_group_create(); - - for (NSUInteger j = 0; j < nodeCount; j += kASDataControllerSizingCountPerProcessor) { - NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, nodeCount - j); - - // Allocate nodes concurrently. - __block NSArray *subarrayOfContexts; - __block NSArray *subarrayOfNodes; - dispatch_block_t allocationBlock = ^{ - __strong ASIndexedNodeContext **allocatedContextBuffer = (__strong ASIndexedNodeContext **)calloc(batchCount, sizeof(ASIndexedNodeContext *)); - __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(batchCount, sizeof(ASCellNode *)); - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_apply(batchCount, queue, ^(size_t i) { - unsigned long k = j + i; - ASIndexedNodeContext *context = contexts[k]; - ASCellNode *node = [context allocateNode]; - if (node == nil) { - ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); - node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. - } - allocatedNodeBuffer[i] = node; - allocatedContextBuffer[i] = context; - }); - subarrayOfNodes = [NSArray arrayWithObjects:allocatedNodeBuffer count:batchCount]; - subarrayOfContexts = [NSArray arrayWithObjects:allocatedContextBuffer count:batchCount]; - // Nil out buffer indexes to allow arc to free the stored cells. - for (int i = 0; i < batchCount; i++) { - allocatedContextBuffer[i] = nil; - allocatedNodeBuffer[i] = nil; - } - free(allocatedContextBuffer); - free(allocatedNodeBuffer); - }; - - // Run the allocation block to concurrently create the cell nodes. Then, handle layout for nodes that are already loaded - // (e.g. the dataSource may have provided cells that have been used before), which must do layout on the main thread. - NSRange batchRange = NSMakeRange(0, batchCount); - if (ASDisplayNodeThreadIsMain()) { - dispatch_semaphore_t sema = dispatch_semaphore_create(0); - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - allocationBlock(); - dispatch_semaphore_signal(sema); - }); - dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - - [self _layoutNodes:subarrayOfNodes fromContexts:subarrayOfContexts atIndexesOfRange:batchRange ofKind:kind]; - } else { - allocationBlock(); - [_mainSerialQueue performBlockOnMainThread:^{ - [self _layoutNodes:subarrayOfNodes fromContexts:subarrayOfContexts atIndexesOfRange:batchRange ofKind:kind]; - }]; - } - - [allocatedNodes addObjectsFromArray:subarrayOfNodes]; - - dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // We should already have measured loaded nodes before we left the main thread. Layout the remaining ones on a background thread. - NSRange asyncBatchRange = NSMakeRange(j, batchCount); - [self _layoutNodes:allocatedNodes fromContexts:contexts atIndexesOfRange:asyncBatchRange ofKind:kind]; - }); + if (!nodeCount || _dataSource == nil) { + return nil; } - // Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated. - dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); + __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *)); + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_apply(nodeCount, queue, ^(size_t i) { + RETURN_IF_NO_DATASOURCE(); + + // Allocate the node. + ASIndexedNodeContext *context = contexts[i]; + ASCellNode *node = [context allocateNode]; + if (node == nil) { + ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); + node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. + } + + [self _layoutNode:node withConstrainedSize:context.constrainedSize]; +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + [ASDataController _didLayoutNode]; +#endif + allocatedNodeBuffer[i] = node; + }); + + BOOL canceled = _dataSource == nil; + + // Create nodes array + NSArray *nodes = canceled ? nil : [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount]; - if (completionBlock) { - NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeCount]; - for (ASIndexedNodeContext *context in contexts) { - [indexPaths addObject:context.indexPath]; - } - - completionBlock(allocatedNodes, indexPaths); + // Nil out buffer indexes to allow arc to free the stored cells. + for (int i = 0; i < nodeCount; i++) { + allocatedNodeBuffer[i] = nil; } + free(allocatedNodeBuffer); + + return nodes; } - (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath @@ -272,13 +231,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock { + ASSERT_ON_EDITING_QUEUE; if (!indexPaths.count || _dataSource == nil) { return; } NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); - _editingNodes[kind] = editingNodes; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes); @@ -298,13 +257,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForTwoDimensionalArray(_editingNodes[kind])); - NSMutableArray *editingNodes = _editingNodes[kind]; - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); - _editingNodes[kind] = editingNodes; + ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); [_mainSerialQueue performBlockOnMainThread:^{ - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); + NSMutableArray *allNodes = _completedNodes[kind]; + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths); + ASDeleteElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths); if (completionBlock) { completionBlock(nodes, indexPaths); } @@ -359,7 +317,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASSERT_ON_EDITING_QUEUE; + [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + ASDisplayNodeAssertMainThread(); + if (_delegateDidInsertNodes) [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; @@ -373,7 +335,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASSERT_ON_EDITING_QUEUE; + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + ASDisplayNodeAssertMainThread(); + if (_delegateDidDeleteNodes) [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; @@ -387,7 +353,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASSERT_ON_EDITING_QUEUE; + [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { + ASDisplayNodeAssertMainThread(); + if (_delegateDidInsertSections) [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; }]; @@ -401,7 +371,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASSERT_ON_EDITING_QUEUE; + [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) { + ASDisplayNodeAssertMainThread(); + if (_delegateDidDeleteSections) [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }]; @@ -424,51 +398,47 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; _initialReloadDataHasBeenCalled = YES; [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - [self accessDataSourceSynchronously:synchronously withBlock:^{ - NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; - - // Allow subclasses to perform setup before going into the edit transaction - [self prepareForReloadData]; + [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 prepareForReloadData]; + + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + LOG(@"Edit Transaction - reloadData"); - void (^transactionBlock)() = ^{ - 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) { - NSMutableIndexSet *indexSet = [[NSMutableIndexSet 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 _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - - if (completion) { - dispatch_async(dispatch_get_main_queue(), completion); - } - }; + // Remove everything that existed before the reload, now that we're ready to insert replacements + NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; + NSUInteger editingNodesSectionCount = editingNodes.count; - if (synchronously) { - transactionBlock(); - } else { - [_editingTransactionQueue addOperationWithBlock:transactionBlock]; + 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]; + } }]; } @@ -480,73 +450,73 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // This should never be called in a batch update, return immediately therefore if (_batchUpdateCounter > 0) { return; } - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); // Schedule block in main serial queue to wait until all operations are finished that are // where scheduled while waiting for the _editingTransactionQueue to finish - [_mainSerialQueue performBlockOnMainThread:^{ - ASDisplayNodeAssert(_editingTransactionQueue.operationCount == 0, @"No operation should be in the _editingTransactionQueue anymore"); - }]; + [_mainSerialQueue performBlockOnMainThread:^{ }]; } #pragma mark - Data Source Access (Calling _dataSource) -/** - * Safely locks access to the data source and executes the given block, unlocking once complete. - * - * @discussion When `asyncDataFetching` is enabled, the block is executed on a background thread. - */ -- (void)accessDataSourceWithBlock:(dispatch_block_t)block -{ - [self accessDataSourceSynchronously:NO withBlock:block]; -} - -- (void)accessDataSourceSynchronously:(BOOL)synchronously withBlock:(dispatch_block_t)block -{ - if (!synchronously && _asyncDataFetchingEnabled) { - [_dataSource dataControllerLockDataSource]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - block(); - [_dataSource dataControllerUnlockDataSource]; - }); - } else { - [_dataSource dataControllerLockDataSource]; - block(); - [_dataSource dataControllerUnlockDataSource]; - } -} - /** * Fetches row contexts for the provided sections from the data source. */ - (NSArray *)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet { + ASDisplayNodeAssertMainThread(); + id environment = [self.environmentDelegate dataControllerEnvironment]; ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; + std::vector counts = [self itemCountsFromDataSource]; NSMutableArray *contexts = [NSMutableArray array]; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx]; - NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; - for (NSUInteger i = 0; i < rowNum; i++) { - NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; - 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]]; + [indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { + NSUInteger itemCount = counts[sectionIndex]; + for (NSUInteger i = 0; i < itemCount; i++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex]; + 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]]; + } } }]; return contexts; } +- (void)invalidateDataSourceItemCounts +{ + ASDisplayNodeAssertMainThread(); + _itemCountsFromDataSourceAreValid = NO; +} + +- (std::vector)itemCountsFromDataSource +{ + ASDisplayNodeAssertMainThread(); + if (NO == _itemCountsFromDataSourceAreValid) { + id source = self.dataSource; + NSInteger sectionCount = [source numberOfSectionsInDataController:self]; + std::vector newCounts; + newCounts.reserve(sectionCount); + for (NSInteger i = 0; i < sectionCount; i++) { + newCounts.push_back([source dataController:self rowsInSection:i]); + } + _itemCountsFromDataSource = newCounts; + _itemCountsFromDataSourceAreValid = YES; + } + return _itemCountsFromDataSource; +} + #pragma mark - Batching (External API) - (void)beginUpdates { - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + 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++; @@ -564,7 +534,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (_batchUpdateCounter == 0) { LOG(@"endUpdatesWithCompletion - beginning"); - [_editingTransactionQueue addOperationWithBlock:^{ + 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. @@ -573,18 +543,19 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; 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); - [_pendingEditCommandBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) { - LOG(@"endUpdatesWithCompletion - running block #%zd", idx); + NSUInteger i = 0; + for (dispatch_block_t block in _pendingEditCommandBlocks) { + LOG(@"endUpdatesWithCompletion - running block #%zd", i); block(); - }]; + i += 1; + } [_pendingEditCommandBlocks removeAllObjects]; - - [_editingTransactionQueue addOperationWithBlock:^{ + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ [_mainSerialQueue performBlockOnMainThread:^{ // Now that the transaction is done, _completedNodes can be accessed externally again. _externalCompletedNodes = nil; @@ -592,7 +563,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"endUpdatesWithCompletion - calling delegate end"); [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; }]; - }]; + }); } } @@ -610,6 +581,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; 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) { @@ -625,28 +600,27 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); + LOG(@"Edit Command - insertSections: %@", sections); - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - [self accessDataSourceWithBlock:^{ - NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections]; + NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections]; - [self prepareForInsertSections: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]; - [_editingTransactionQueue addOperationWithBlock:^{ - [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 _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }]; - }]; + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; + }); }]; } @@ -655,9 +629,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); LOG(@"Edit Command - deleteSections: %@", sections); - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - - [_editingTransactionQueue addOperationWithBlock:^{ + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ [self willDeleteSections:sections]; // remove elements @@ -666,37 +639,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }]; + }); }]; } - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - reloadSections: %@", sections); - - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - - [self accessDataSourceWithBlock:^{ - NSArray *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections]; - - [self prepareForReloadSections:sections]; - - [_editingTransactionQueue addOperationWithBlock:^{ - [self willReloadSections:sections]; - - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // reinsert the elements - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }]; - }]; - }]; + ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController the reload will be broken into delete & insert.", NSStringFromSelector(_cmd)); } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions @@ -705,29 +654,28 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; ASDisplayNodeAssertMainThread(); LOG(@"Edit Command - moveSection"); - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - - [_editingTransactionQueue addOperationWithBlock:^{ + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ [self willMoveSection:section toSection:newSection]; // remove elements LOG(@"Edit Transaction - moveSection"); - - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); + NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; for (NSIndexPath *indexPath in indexPaths) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; + 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]; - }]; + }); }]; } @@ -759,16 +707,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Optional template hook for subclasses (See ASDataController+Subclasses.h) } -- (void)prepareForReloadSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willReloadSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { // Optional template hook for subclasses (See ASDataController+Subclasses.h) @@ -794,16 +732,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Optional template hook for subclasses (See ASDataController+Subclasses.h) } -- (void)prepareForReloadRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willReloadRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - #pragma mark - Row Editing (External API) - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions @@ -811,35 +739,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); LOG(@"Edit Command - insertRows: %@", indexPaths); - - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); // 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 accessDataSourceWithBlock:^{ - 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]]; - } + 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]]; + } - [self prepareForInsertRowsAtIndexPaths:indexPaths]; + [self prepareForInsertRowsAtIndexPaths:indexPaths]; - [_editingTransactionQueue addOperationWithBlock:^{ - [self willInsertRowsAtIndexPaths:indexPaths]; + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [self willInsertRowsAtIndexPaths:indexPaths]; - LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }]; - }]; + LOG(@"Edit Transaction - insertRows: %@", indexPaths); + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; + }); }]; } @@ -849,7 +774,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; ASDisplayNodeAssertMainThread(); LOG(@"Edit Command - deleteRows: %@", indexPaths); - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + 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? @@ -857,54 +782,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; - [_editingTransactionQueue addOperationWithBlock:^{ + 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 { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - reloadRows: %@", indexPaths); - - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - - // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. - [self accessDataSourceWithBlock:^{ - NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - - // Sort indexPath to avoid messing up the index when deleting - // FIXME: Shouldn't deletes be sorted in descending order? - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - - 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]]; - } - - [self prepareForReloadRowsAtIndexPaths:indexPaths]; - - [_editingTransactionQueue addOperationWithBlock:^{ - [self willReloadRowsAtIndexPaths:indexPaths]; - - LOG(@"Edit Transaction - reloadRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }]; - }]; - }]; + ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController and the reload will be broken into delete & insert.", NSStringFromSelector(_cmd)); } - (void)relayoutAllNodes @@ -912,18 +801,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); LOG(@"Edit Command - relayoutRows"); - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + 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:). - [_editingTransactionQueue addOperationWithBlock:^{ + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ [_mainSerialQueue performBlockOnMainThread:^{ for (NSString *kind in _completedNodes) { [self _relayoutNodesOfKind:kind]; } }]; - }]; + }); }]; } @@ -935,16 +824,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return; } - [self accessDataSourceWithBlock:^{ - [nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) { - [section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - ASLayout *layout = [node measureWithSizeRange:constrainedSize]; - node.frame = CGRectMake(0.0f, 0.0f, layout.size.width, layout.size.height); - }]; - }]; - }]; + NSUInteger sectionIndex = 0; + for (NSMutableArray *section in nodes) { + NSUInteger rowIndex = 0; + for (ASCellNode *node in section) { + RETURN_IF_NO_DATASOURCE(); + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + [self _layoutNode:node withConstrainedSize:constrainedSize]; + rowIndex += 1; + } + sectionIndex += 1; + } } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions @@ -952,18 +843,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath); - [_editingTransactionQueue waitUntilAllOperationsAreFinished]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - [_editingTransactionQueue addOperationWithBlock:^{ + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], @[indexPath]); 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]; - }]; + }); }]; } @@ -971,12 +862,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind { - return _editingNodes[kind] != nil ? ASIndexPathsForTwoDimensionalArray(_editingNodes[kind]) : nil; + NSArray *nodes = _editingNodes[kind]; + return nodes != nil ? ASIndexPathsForTwoDimensionalArray(nodes) : nil; } - (NSMutableArray *)editingNodesOfKind:(NSString *)kind { - return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array]; + return _editingNodes[kind] ? : [NSMutableArray array]; } - (NSMutableArray *)completedNodesOfKind:(NSString *)kind @@ -1021,28 +913,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; { ASDisplayNodeAssertMainThread(); - - NSArray *nodes = [self completedNodes]; - NSUInteger numberOfNodes = nodes.count; + NSInteger section = 0; // Loop through each section to look for the cellNode - for (NSUInteger i = 0; i < numberOfNodes; i++) { - NSArray *sectionNodes = nodes[i]; - NSUInteger cellIndex = [sectionNodes indexOfObjectIdenticalTo:cellNode]; - if (cellIndex != NSNotFound) { - return [NSIndexPath indexPathForRow:cellIndex inSection:i]; + for (NSArray *sectionNodes in [self completedNodes]) { + NSUInteger item = [sectionNodes indexOfObjectIdenticalTo:cellNode]; + if (item != NSNotFound) { + return [NSIndexPath indexPathForItem:item inSection:section]; } + section += 1; } return nil; } -- (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); -} - /// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. - (NSArray *)completedNodes { @@ -1055,7 +939,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)dealloc { ASDisplayNodeAssertMainThread(); - [_completedNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *sections, BOOL *stop) { + for (NSMutableArray *sections in [_completedNodes objectEnumerator]) { for (NSArray *section in sections) { for (ASCellNode *node in section) { if (node.isNodeLoaded) { @@ -1067,7 +951,31 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } } } - }]; + } } @end + +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + +static volatile int64_t _totalExpectedItems = 0; +static volatile int64_t _totalMeasuredNodes = 0; + +@implementation ASDataController (WorkMeasuring) + ++ (void)_didLayoutNode +{ + int64_t measured = OSAtomicIncrement64(&_totalMeasuredNodes); + int64_t expected = _totalExpectedItems; + if (measured % 20 == 0 || measured == expected) { + NSLog(@"Data controller avoided work (underestimated): %lld / %lld", measured, expected); + } +} + ++ (void)_expectToInsertNodes:(NSUInteger)count +{ + OSAtomicAdd64((int64_t)count, &_totalExpectedItems); +} + +@end +#endif diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index b1f3310736..13c0d211c9 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -118,6 +118,15 @@ return self; } +- (BOOL)conformsToProtocol:(Protocol *)aProtocol +{ + if (_target) { + return [_target conformsToProtocol:aProtocol]; + } else { + return [super conformsToProtocol:aProtocol]; + } +} + - (BOOL)respondsToSelector:(SEL)aSelector { if ([self interceptsSelector:aSelector]) { diff --git a/AsyncDisplayKit/Details/ASEnvironment.h b/AsyncDisplayKit/Details/ASEnvironment.h index 6c395aa18e..4dad9fde9f 100644 --- a/AsyncDisplayKit/Details/ASEnvironment.h +++ b/AsyncDisplayKit/Details/ASEnvironment.h @@ -10,9 +10,9 @@ #import -#import "ASDimension.h" -#import "ASStackLayoutDefines.h" -#import "ASRelativeSize.h" +#import +#import +#import @protocol ASEnvironment; @class UITraitCollection; @@ -50,6 +50,7 @@ typedef struct ASEnvironmentLayoutOptionsState { struct ASEnvironmentStateExtensions _extensions; } ASEnvironmentLayoutOptionsState; +extern ASEnvironmentLayoutOptionsState ASEnvironmentLayoutOptionsStateMakeDefault(); #pragma mark - ASEnvironmentHierarchyState @@ -60,6 +61,7 @@ typedef struct ASEnvironmentHierarchyState { unsigned transitioningSupernodes:1; // = NO unsigned layoutPending:1; // = NO } ASEnvironmentHierarchyState; +extern ASEnvironmentHierarchyState ASEnvironmentHierarchyStateMakeDefault(); #pragma mark - ASEnvironmentDisplayTraits @@ -69,25 +71,10 @@ typedef struct ASEnvironmentTraitCollection { UIUserInterfaceIdiom userInterfaceIdiom; UIUserInterfaceSizeClass verticalSizeClass; UIForceTouchCapability forceTouchCapability; - - // WARNING: - // This pointer is in a C struct and therefore not managed by ARC. It is - // an unsafe unretained pointer, so when you dereference it you better be - // sure that it is valid. - // - // Use displayContext when you wish to pass view context specific data along with the - // display traits to subnodes. This should be a piece of data owned by an - // ASViewController, which will ensure that the data is still valid when laying out - // its subviews. When the VC is dealloc'ed, the displayContext it created will also - // be dealloced but any subnodes that are hanging around (why would they be?) will now - // have a displayContext that points to a bad pointer. - // - // As an added precaution ASDisplayTraitsClearDisplayContext is called from ASVC's desctructor - // which will propagate a nil displayContext to its subnodes. - id __unsafe_unretained displayContext; -} ASEnvironmentTraitCollection; -extern void ASEnvironmentTraitCollectionUpdateDisplayContext(id rootEnvironment, id _Nullable context); + CGSize containerSize; +} ASEnvironmentTraitCollection; +extern ASEnvironmentTraitCollection ASEnvironmentTraitCollectionMakeDefault(); extern ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection); extern BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs); @@ -147,6 +134,9 @@ ASDISPLAYNODE_EXTERN_C_END // // If there is any new downward propagating state, it should be added to this define. // +// If the only change in a trait collection is that its dislplayContext has gone from non-nil to nil, +// assume that we are clearing the context as part of a ASVC dealloc and do not trigger a layout. +// // This logic is used in both ASCollectionNode and ASTableNode #define ASEnvironmentCollectionTableSetEnvironmentState(lock) \ - (void)setEnvironmentState:(ASEnvironmentState)environmentState\ @@ -154,14 +144,18 @@ ASDISPLAYNODE_EXTERN_C_END ASDN::MutexLocker l(lock);\ ASEnvironmentTraitCollection oldTraits = self.environmentState.environmentTraitCollection;\ [super setEnvironmentState:environmentState];\ +\ + /* Extra Trait Collection Handling */\ + /* If the node is not loaded yet don't do anything as otherwise the access of the view will trigger a load*/\ + if (!self.isNodeLoaded) { return; } \ ASEnvironmentTraitCollection currentTraits = environmentState.environmentTraitCollection;\ if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(currentTraits, oldTraits) == NO) {\ + /* Must dispatch to main for self.view && [self.view.dataController completedNodes]*/ \ ASPerformBlockOnMainThread(^{\ NSArray *> *completedNodes = [self.view.dataController completedNodes];\ for (NSArray *sectionArray in completedNodes) {\ for (ASCellNode *cellNode in sectionArray) {\ ASEnvironmentStatePropagateDown(cellNode, currentTraits);\ - [cellNode setNeedsLayout];\ }\ }\ });\ diff --git a/AsyncDisplayKit/Details/ASEnvironment.mm b/AsyncDisplayKit/Details/ASEnvironment.mm index 77ffca7a0f..68aca910da 100644 --- a/AsyncDisplayKit/Details/ASEnvironment.mm +++ b/AsyncDisplayKit/Details/ASEnvironment.mm @@ -8,41 +8,29 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASEnvironment.h" #import "ASEnvironmentInternal.h" -#import +#import "ASAvailability.h" -ASEnvironmentLayoutOptionsState _ASEnvironmentLayoutOptionsStateMakeDefault() +ASEnvironmentLayoutOptionsState ASEnvironmentLayoutOptionsStateMakeDefault() { return (ASEnvironmentLayoutOptionsState) { // Default values can be defined in here }; } -ASEnvironmentHierarchyState _ASEnvironmentHierarchyStateMakeDefault() +ASEnvironmentHierarchyState ASEnvironmentHierarchyStateMakeDefault() { return (ASEnvironmentHierarchyState) { // Default values can be defined in here }; } -extern void ASEnvironmentTraitCollectionUpdateDisplayContext(id rootEnvironment, id context) -{ - ASEnvironmentState envState = [rootEnvironment environmentState]; - ASEnvironmentTraitCollection environmentTraitCollection = envState.environmentTraitCollection; - environmentTraitCollection.displayContext = context; - envState.environmentTraitCollection = environmentTraitCollection; - [rootEnvironment setEnvironmentState:envState]; - - for (id child in [rootEnvironment children]) { - ASEnvironmentStatePropagateDown(child, environmentTraitCollection); - } -} - -ASEnvironmentTraitCollection _ASEnvironmentTraitCollectionMakeDefault() +ASEnvironmentTraitCollection ASEnvironmentTraitCollectionMakeDefault() { return (ASEnvironmentTraitCollection) { // Default values can be defined in here + .userInterfaceIdiom = UIUserInterfaceIdiomUnspecified, + .containerSize = CGSizeZero, }; } @@ -69,15 +57,15 @@ BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnviron lhs.displayScale == rhs.displayScale && lhs.userInterfaceIdiom == rhs.userInterfaceIdiom && lhs.forceTouchCapability == rhs.forceTouchCapability && - lhs.displayContext == rhs.displayContext; + CGSizeEqualToSize(lhs.containerSize, rhs.containerSize); } ASEnvironmentState ASEnvironmentStateMakeDefault() { return (ASEnvironmentState) { - .layoutOptionsState = _ASEnvironmentLayoutOptionsStateMakeDefault(), - .hierarchyState = _ASEnvironmentHierarchyStateMakeDefault(), - .environmentTraitCollection = _ASEnvironmentTraitCollectionMakeDefault() + .layoutOptionsState = ASEnvironmentLayoutOptionsStateMakeDefault(), + .hierarchyState = ASEnvironmentHierarchyStateMakeDefault(), + .environmentTraitCollection = ASEnvironmentTraitCollectionMakeDefault() }; } diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/AsyncDisplayKit/Details/ASFlowLayoutController.mm index cb754bb9bf..c89e187c8f 100644 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ b/AsyncDisplayKit/Details/ASFlowLayoutController.mm @@ -16,7 +16,6 @@ #include #include -#include @interface ASFlowLayoutController() { @@ -92,12 +91,12 @@ currPath.row++; // Once we reach the end of the section, advance to the next one. Keep advancing if the next section is zero-sized. - while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < completedNodes.count - 1) { + while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < endPath.section) { currPath.row = 0; currPath.section++; - ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath"); } } + ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath"); [indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:endPath]]; @@ -114,11 +113,11 @@ range.start = currentIndexPath; range.end = currentIndexPath; - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { + for (NSIndexPath *indexPath in indexPaths) { currentIndexPath = [indexPath ASIndexPathValue]; range.start = ASIndexPathMinimum(range.start, currentIndexPath); range.end = ASIndexPathMaximum(range.end, currentIndexPath); - }]; + } return range; } diff --git a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h index 6f80b2996b..34d31979ec 100644 --- a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h +++ b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.h @@ -31,8 +31,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithRects:(NSArray *)rects; -@property (nullable, atomic, strong) __attribute__((NSObject)) CGColorRef highlightColor; -@property (atomic, weak) CALayer *targetLayer; +@property (nullable, nonatomic, strong) __attribute__((NSObject)) CGColorRef highlightColor; +@property (nonatomic, weak) CALayer *targetLayer; @end diff --git a/AsyncDisplayKit/Details/ASImageProtocols.h b/AsyncDisplayKit/Details/ASImageProtocols.h index 676ef4196c..e9074e723b 100644 --- a/AsyncDisplayKit/Details/ASImageProtocols.h +++ b/AsyncDisplayKit/Details/ASImageProtocols.h @@ -17,8 +17,8 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASImageContainerProtocol -- (UIImage *)asdk_image; -- (NSData *)asdk_animatedImageData; +- (nullable UIImage *)asdk_image; +- (nullable NSData *)asdk_animatedImageData; @end diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.h b/AsyncDisplayKit/Details/ASIndexedNodeContext.h index b970d3e997..cdb907bcaf 100644 --- a/AsyncDisplayKit/Details/ASIndexedNodeContext.h +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.h @@ -29,4 +29,6 @@ */ - (ASCellNode *)allocateNode; ++ (NSArray *)indexPathsFromContexts:(NSArray *)contexts; + @end diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.mm b/AsyncDisplayKit/Details/ASIndexedNodeContext.mm index a009d2cbcc..daf9c850a5 100644 --- a/AsyncDisplayKit/Details/ASIndexedNodeContext.mm +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.mm @@ -48,4 +48,13 @@ return node; } ++ (NSArray *)indexPathsFromContexts:(NSArray *)contexts +{ + NSMutableArray *result = [NSMutableArray arrayWithCapacity:contexts.count]; + for (ASIndexedNodeContext *ctx in contexts) { + [result addObject:ctx.indexPath]; + } + return result; +} + @end diff --git a/AsyncDisplayKit/Details/ASLayoutController.h b/AsyncDisplayKit/Details/ASLayoutController.h index 759c230e20..d159cc0384 100644 --- a/AsyncDisplayKit/Details/ASLayoutController.h +++ b/AsyncDisplayKit/Details/ASLayoutController.h @@ -37,14 +37,6 @@ FOUNDATION_EXPORT BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRan @optional -- (void)insertNodesAtIndexPaths:(NSArray *)indexPaths withSizes:(NSArray *)nodeSizes; - -- (void)deleteNodesAtIndexPaths:(NSArray *)indexPaths; - -- (void)insertSections:(NSArray*> *)sections atIndexSet:(NSIndexSet *)indexSet; - -- (void)deleteSectionsAtIndexSet:(NSIndexSet *)indexSet; - - (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths; - (void)setViewportSize:(CGSize)viewportSize; diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index 99fad4ecc4..70e3b194da 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -30,6 +30,7 @@ #import #if PIN_ANIMATED_AVAILABLE + @interface ASPINRemoteImageDownloader () @end @@ -68,6 +69,19 @@ @end #endif +@interface ASPINRemoteImageManager : PINRemoteImageManager +@end + +@implementation ASPINRemoteImageManager + +//Share image cache with sharedImageManager image cache. +- (PINCache *)defaultImageCache +{ + return [[PINRemoteImageManager sharedImageManager] cache]; +} + +@end + @implementation ASPINRemoteImageDownloader + (instancetype)sharedDownloader @@ -82,7 +96,7 @@ - (PINRemoteImageManager *)sharedPINRemoteImageManager { - static PINRemoteImageManager *sharedPINRemoteImageManager = nil; + static ASPINRemoteImageManager *sharedPINRemoteImageManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -102,9 +116,9 @@ userInfo:nil]; @throw e; } - sharedPINRemoteImageManager = [[PINRemoteImageManager alloc] initWithSessionConfiguration:nil alternativeRepresentationProvider:self]; + sharedPINRemoteImageManager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:nil alternativeRepresentationProvider:self]; #else - sharedPINRemoteImageManager = [[PINRemoteImageManager alloc] initWithSessionConfiguration:nil]; + sharedPINRemoteImageManager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:nil]; #endif }); return sharedPINRemoteImageManager; @@ -164,10 +178,10 @@ /// If we're targeting the main queue and we're on the main thread, call immediately. if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { - downloadProgress(totalBytes / (CGFloat)completedBytes); + downloadProgress(completedBytes / (CGFloat)totalBytes); } else { dispatch_async(callbackQueue, ^{ - downloadProgress(totalBytes / (CGFloat)completedBytes); + downloadProgress(completedBytes / (CGFloat)totalBytes); }); } } completion:^(PINRemoteImageManagerResult * _Nonnull result) { @@ -200,6 +214,10 @@ - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier { + if (!downloadIdentifier) { + return; + } + ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier]; } diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index c79124a961..d682e07b9d 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -9,11 +9,11 @@ // #import - -#import +#import #import #import #import +#import #define ASRangeControllerLoggingEnabled 0 @@ -40,12 +40,18 @@ NS_ASSUME_NONNULL_BEGIN /** * Notify the range controller that the visible range has been updated. * This is the primary input call that drives updating the working ranges, and triggering their actions. - * - * @param scrollDirection The current scroll direction of the scroll view. + * The ranges will be updated in the next turn of the main loop, or when -updateIfNeeded is called. * * @see [ASRangeControllerDelegate rangeControllerVisibleNodeIndexPaths:] */ -- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection; +- (void)setNeedsUpdate; + +/** + * Update the ranges immediately, if -setNeedsUpdate has been called since the last update. + * This is useful because the ranges must be updated immediately after a cell is added + * into a table/collection to satisfy interface state API guarantees. + */ +- (void)updateIfNeeded; /** * Add the sized node for `indexPath` as a subview of `contentView`. @@ -101,6 +107,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController; +/** + * @param rangeController Sender. + * + * @returns the current scroll direction of the view using this range controller. + */ +- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController; + /** * @param rangeController Sender. * @@ -117,8 +130,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController; -- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths; - - (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath; - (NSArray *> *)completedNodes; @@ -203,4 +214,23 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +@interface ASRangeController (ASRangeControllerUpdateRangeProtocol) + +/** + * Update the range mode for a range controller to a explicitly set mode until the node that contains the range + * controller becomes visible again + * + * Logic for the automatic range mode: + * 1. If there are no visible node paths available nothing is to be done and no range update will happen + * 2. The initial range update if the range controller is visible always will be ASLayoutRangeModeCount + * (ASLayoutRangeModeMinimum) as it's the initial fetch + * 3. The range mode set explicitly via updateCurrentRangeWithMode: will last at least one range update. After that it + the range controller will use the explicit set range mode until it becomes visible and a new range update was + triggered or a new range mode via updateCurrentRangeWithMode: is set + * 4. If range mode is not explicitly set the range mode is variying based if the range controller is visible or not + */ +- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 6c84935540..d82073779d 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -14,21 +14,29 @@ #import "ASWeakSet.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" -#import "ASMultiDimensionalArrayUtils.h" +#import "ASMultidimensionalArrayUtils.h" #import "ASInternalHelpers.h" #import "ASDisplayNode+FrameworkPrivate.h" +#import "ASCellNode.h" + +#define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0 @interface ASRangeController () { BOOL _rangeIsValid; - BOOL _queuedRangeUpdate; + BOOL _needsRangeUpdate; BOOL _layoutControllerImplementsSetVisibleIndexPaths; - ASScrollDirection _scrollDirection; + BOOL _layoutControllerImplementsSetViewportSize; NSSet *_allPreviousIndexPaths; ASLayoutRangeMode _currentRangeMode; BOOL _didUpdateCurrentRange; - BOOL _didRegisterForNotifications; + BOOL _didRegisterForNodeDisplayNotifications; CFAbsoluteTime _pendingDisplayNodesTimestamp; + +#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ + NSUInteger _updateCountThisFrame; + CADisplayLink *_displayLink; +#endif } @end @@ -51,12 +59,21 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; [[[self class] allRangeControllersWeakSet] addObject:self]; +#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_updateCountDisplayLinkDidFire)]; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; +#endif + return self; } - (void)dealloc { - if (_didRegisterForNotifications) { +#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ + [_displayLink invalidate]; +#endif + + if (_didRegisterForNodeDisplayNotifications) { [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; } } @@ -93,12 +110,25 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; return selfInterfaceState; } -- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection +- (void)setNeedsUpdate { - _scrollDirection = scrollDirection; + if (!_needsRangeUpdate) { + _needsRangeUpdate = YES; + + __weak __typeof__(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf updateIfNeeded]; + }); + } +} - // Perform update immediately, so that cells receive a visibleStateDidChange: call before their first pixel is visible. - [self scheduleRangeUpdate]; +- (void)updateIfNeeded +{ + if (_needsRangeUpdate) { + _needsRangeUpdate = NO; + + [self _updateVisibleNodeIndexPaths]; + } } - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode @@ -106,71 +136,56 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; if (_currentRangeMode != rangeMode) { _currentRangeMode = rangeMode; _didUpdateCurrentRange = YES; - - [self scheduleRangeUpdate]; - } -} -- (void)scheduleRangeUpdate -{ - if (_queuedRangeUpdate) { - return; + [self setNeedsUpdate]; } - - // coalesce these events -- handling them multiple times per runloop is noisy and expensive - _queuedRangeUpdate = YES; - - dispatch_async(dispatch_get_main_queue(), ^{ - [self performRangeUpdate]; - }); -} - -- (void)performRangeUpdate -{ - // Call this version if you want the update to occur immediately, such as on app suspend, as another runloop may not occur. - ASDisplayNodeAssertMainThread(); - _queuedRangeUpdate = YES; // For now, set this flag as _update... expects it and clears it. - [self _updateVisibleNodeIndexPaths]; } - (void)setLayoutController:(id)layoutController { _layoutController = layoutController; - _layoutControllerImplementsSetVisibleIndexPaths = [_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]; - if (_layoutController && _queuedRangeUpdate) { - [self performRangeUpdate]; + _layoutControllerImplementsSetVisibleIndexPaths = [layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]; + _layoutControllerImplementsSetViewportSize = [layoutController respondsToSelector:@selector(setViewportSize:)]; + if (layoutController && _dataSource) { + [self updateIfNeeded]; } } - (void)setDataSource:(id)dataSource { _dataSource = dataSource; - if (_dataSource && _queuedRangeUpdate) { - [self performRangeUpdate]; + if (dataSource && _layoutController) { + [self updateIfNeeded]; } } - (void)_updateVisibleNodeIndexPaths { ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController"); - if (!_queuedRangeUpdate || !_layoutController || !_dataSource) { + if (!_layoutController || !_dataSource) { return; } +#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ + _updateCountThisFrame += 1; +#endif + // allNodes is a 2D array: it contains arrays for each section, each containing nodes. NSArray *allNodes = [_dataSource completedNodes]; NSUInteger numberOfSections = [allNodes count]; // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges - // Example: ... = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; + // Example: ... = [_layoutController indexPathsForScrolling:scrollDirection rangeType:ASLayoutRangeTypeVisible]; NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... - _queuedRangeUpdate = NO; return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later } - [_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]]; + ASScrollDirection scrollDirection = [_dataSource scrollDirectionForRangeController:self]; + if (_layoutControllerImplementsSetViewportSize) { + [_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]]; + } // the layout controller needs to know what the current visible indices are to calculate range offsets if (_layoutControllerImplementsSetVisibleIndexPaths) { @@ -202,7 +217,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersFetchData, ASRangeTuningParametersZero)) { fetchDataIndexPaths = visibleIndexPaths; } else { - fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection + fetchDataIndexPaths = [_layoutController indexPathsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeFetchData]; } @@ -216,7 +231,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; } else if (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersFetchData)) { displayIndexPaths = fetchDataIndexPaths; } else { - displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection + displayIndexPaths = [_layoutController indexPathsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay]; } @@ -242,10 +257,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; [allIndexPaths addObjectsFromArray:ASIndexPathsForTwoDimensionalArray(allNodes)]; } - // TODO Don't register for notifications if this range update doesn't cause any node to enter rendering pipeline. - // This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings - [self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; - #if ASRangeControllerLoggingEnabled ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]); NSMutableArray *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil); @@ -309,18 +320,22 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; #if ASRangeControllerLoggingEnabled [modifiedIndexPaths addObject:indexPath]; #endif + + BOOL nodeShouldScheduleDisplay = [node shouldScheduleDisplayWithNewInterfaceState:interfaceState]; [node recursivelySetInterfaceState:interfaceState]; + + if (nodeShouldScheduleDisplay) { + [self registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; + if (_didRegisterForNodeDisplayNotifications) { + _pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent(); + } + } } } } } - if (_didRegisterForNotifications) { - _pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent(); - } - _rangeIsValid = YES; - _queuedRangeUpdate = NO; #if ASRangeControllerLoggingEnabled // NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; @@ -338,9 +353,9 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; #pragma mark - Notification observers -- (void)registerForNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState +- (void)registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState { - if (!_didRegisterForNotifications) { + if (!_didRegisterForNodeDisplayNotifications) { ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState currentRangeMode:_currentRangeMode]; if (_currentRangeMode != nextRangeMode) { @@ -348,7 +363,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; selector:@selector(scheduledNodesDidDisplay:) name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; - _didRegisterForNotifications = YES; + _didRegisterForNodeDisplayNotifications = YES; } } } @@ -359,9 +374,9 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; if (_pendingDisplayNodesTimestamp < notificationTimestamp) { // The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; - _didRegisterForNotifications = NO; + _didRegisterForNodeDisplayNotifications = NO; - [self scheduleRangeUpdate]; + [self setNeedsUpdate]; } } @@ -400,50 +415,44 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; - (void)dataControllerBeginUpdates:(ASDataController *)dataController { - ASPerformBlockOnMainThread(^{ - [_delegate didBeginUpdatesInRangeController:self]; - }); + ASDisplayNodeAssertMainThread(); + [_delegate didBeginUpdatesInRangeController:self]; } - (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - ASPerformBlockOnMainThread(^{ - [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; - }); + ASDisplayNodeAssertMainThread(); + [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; } - (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssertMainThread(); + _rangeIsValid = NO; + [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; } - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssertMainThread(); + _rangeIsValid = NO; + [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; } - (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssertMainThread(); + _rangeIsValid = NO; + [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; } - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssertMainThread(); + _rangeIsValid = NO; + [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; } #pragma mark - Memory Management @@ -507,7 +516,8 @@ static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeVisible for (ASRangeController *rangeController in allRangeControllers) { BOOL isDisplay = ASInterfaceStateIncludesDisplay([rangeController interfaceState]); [rangeController updateCurrentRangeWithMode:isDisplay ? ASLayoutRangeModeMinimum : __rangeModeForMemoryWarnings]; - [rangeController performRangeUpdate]; + [rangeController setNeedsUpdate]; + [rangeController updateIfNeeded]; } #if ASRangeControllerLoggingEnabled @@ -529,7 +539,8 @@ static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeVisible __ApplicationState = UIApplicationStateBackground; for (ASRangeController *rangeController in allRangeControllers) { // Trigger a range update immediately, as we may not be allowed by the system to run the update block scheduled by changing range mode. - [rangeController performRangeUpdate]; + [rangeController setNeedsUpdate]; + [rangeController updateIfNeeded]; } #if ASRangeControllerLoggingEnabled @@ -544,7 +555,8 @@ static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeVisible for (ASRangeController *rangeController in allRangeControllers) { BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]); [rangeController updateCurrentRangeWithMode:isVisible ? ASLayoutRangeModeMinimum : ASLayoutRangeModeVisibleOnly]; - [rangeController performRangeUpdate]; + [rangeController setNeedsUpdate]; + [rangeController updateIfNeeded]; } #if ASRangeControllerLoggingEnabled @@ -554,6 +566,16 @@ static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeVisible #pragma mark - Debugging +#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ +- (void)_updateCountDisplayLinkDidFire +{ + if (_updateCountThisFrame > 1) { + NSLog(@"ASRangeController %p updated %lu times this frame.", self, (unsigned long)_updateCountThisFrame); + } + _updateCountThisFrame = 0; +} +#endif + - (NSString *)descriptionWithIndexPaths:(NSArray *)indexPaths { NSMutableString *description = [NSMutableString stringWithFormat:@"%@ %@", [super description], @" allPreviousIndexPaths:\n"]; @@ -575,3 +597,12 @@ static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeVisible } @end + +@implementation ASDisplayNode (RangeModeConfiguring) + ++ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode +{ + [ASRangeController setRangeModeForMemoryWarnings:rangeMode]; +} + +@end diff --git a/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h b/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h index 09b10cd478..13f03b3e12 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h +++ b/AsyncDisplayKit/Details/ASRangeControllerUpdateRangeProtocol+Beta.h @@ -8,65 +8,14 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASLayoutRangeType.h" -#import "ASViewController.h" -#import "ASRangeController.h" -#import "ASCollectionNode.h" -#import "ASTableNode.h" - +#import @protocol ASRangeControllerUpdateRangeProtocol /** - * Updates the current range mode of the range controller for at least the next range update. - */ -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; - -/** - * Only ASLayoutRangeModeVisibleOnly or ASLayoutRangeModeLowMemory are recommended. Default is ASLayoutRangeModeVisibleOnly, - * because this is the only way to ensure an application will not have blank / flashing views as the user navigates back after - * a memory warning. Apps that wish to use the more effective / aggressive ASLayoutRangeModeLowMemory may need to take steps - * to mitigate this behavior, including: restoring a larger range mode to the next controller before the user navigates there, - * enabling .neverShowPlaceholders on ASCellNodes so that the navigation operation is blocked on redisplay completing, etc. - */ -+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode; - -@end - - -@interface ASRangeController (ASRangeControllerUpdateRangeProtocol) - -/** - * Update the range mode for a range controller to a explicitly set mode until the node that contains the range - * controller becomes visible again - * - * Logic for the automatic range mode: - * 1. If there are no visible node paths available nothing is to be done and no range update will happen - * 2. The initial range update if the range controller is visible always will be ASLayoutRangeModeCount - * (ASLayoutRangeModeMinimum) as it's the initial fetch - * 3. The range mode set explicitly via updateCurrentRangeWithMode: will last at least one range update. After that it - the range controller will use the explicit set range mode until it becomes visible and a new range update was - triggered or a new range mode via updateCurrentRangeWithMode: is set - * 4. If range mode is not explicitly set the range mode is variying based if the range controller is visible or not + * Updates the current range mode of the range controller for at least the next range update + * and, if the new mode is different from the previous mode, enqueues a range update. */ - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; @end - - -@interface ASCollectionNode (ASRangeControllerUpdateRangeProtocol) - -@end - - -@interface ASTableNode (ASRangeControllerUpdateRangeProtocol) - -@end - - -@interface ASViewController (ASRangeControllerUpdateRangeProtocol) - -/// Automatically adjust range mode based on view events if the containing node confirms to the ASRangeControllerUpdateRangeProtocol -@property (nonatomic, assign) BOOL automaticallyAdjustRangeModeBasedOnViewEvents; - -@end diff --git a/AsyncDisplayKit/Details/ASTraitCollection.h b/AsyncDisplayKit/Details/ASTraitCollection.h index 9524886d0f..19e23131cc 100644 --- a/AsyncDisplayKit/Details/ASTraitCollection.h +++ b/AsyncDisplayKit/Details/ASTraitCollection.h @@ -18,27 +18,13 @@ @property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom; @property (nonatomic, assign, readonly) UIUserInterfaceSizeClass verticalSizeClass; @property (nonatomic, assign, readonly) UIForceTouchCapability forceTouchCapability; - -/** - * An optional context to pass along with an ASTraitCollection. - * This can be used to pass any internal state to all subnodes via the ASTraitCollection that is not - * included in UITraitCollection. This could range from more fine-tuned size classes to a class of - * constants that is based upon the new trait collection. - * - * Be aware that internally this context is held by a C struct which cannot retain the pointer. - * ASTraitCollection is generally a very short-lived class, existing only to provide a non-struct API - * to trait collections. When an ASTraitCollection is returned via one of ASViewController's 2 - * custom trait collection creation blocks, traitCollectionContext is assigned to the VC's traitCollectionContext. - * This makes sure that the VC is the owner of the context and ASEnvironmentTraitCollections will not - * have a reference to a dangling pointer. - */ -@property (nonatomic, strong, readonly) id traitCollectionContext; +@property (nonatomic, assign, readonly) CGSize containerSize; + (ASTraitCollection *)traitCollectionWithASEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traits; + (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection - traitCollectionContext:(id)traitCollectionContext; + containerSize:(CGSize)windowSize; + (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale @@ -46,7 +32,7 @@ horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - traitCollectionContext:(id)traitCollectionContext; + containerSize:(CGSize)windowSize; - (ASEnvironmentTraitCollection)environmentTraitCollection; diff --git a/AsyncDisplayKit/Details/ASTraitCollection.m b/AsyncDisplayKit/Details/ASTraitCollection.m index 49fb94c780..c3b83dd8ee 100644 --- a/AsyncDisplayKit/Details/ASTraitCollection.m +++ b/AsyncDisplayKit/Details/ASTraitCollection.m @@ -11,7 +11,6 @@ // #import "ASTraitCollection.h" -#import #import @implementation ASTraitCollection @@ -21,7 +20,7 @@ horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - traitCollectionContext:(id)traitCollectionContext + containerSize:(CGSize)windowSize { self = [super init]; if (self) { @@ -30,7 +29,7 @@ _horizontalSizeClass = horizontalSizeClass; _verticalSizeClass = verticalSizeClass; _forceTouchCapability = forceTouchCapability; - _traitCollectionContext = traitCollectionContext; + _containerSize = windowSize; } return self; } @@ -40,29 +39,29 @@ horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - traitCollectionContext:(id)traitCollectionContext + containerSize:(CGSize)windowSize { return [[[self class] alloc] initWithDisplayScale:displayScale userInterfaceIdiom:userInterfaceIdiom horizontalSizeClass:horizontalSizeClass verticalSizeClass:verticalSizeClass forceTouchCapability:forceTouchCapability - traitCollectionContext:traitCollectionContext]; + containerSize:windowSize]; } + (ASTraitCollection *)traitCollectionWithASEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traits { - return [[[self class] alloc] initWithDisplayScale:traits.displayScale - userInterfaceIdiom:traits.userInterfaceIdiom - horizontalSizeClass:traits.horizontalSizeClass - verticalSizeClass:traits.verticalSizeClass - forceTouchCapability:traits.forceTouchCapability - traitCollectionContext:traits.displayContext]; + return [[[self class] alloc] initWithDisplayScale:traits.displayScale + userInterfaceIdiom:traits.userInterfaceIdiom + horizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + forceTouchCapability:traits.forceTouchCapability + containerSize:traits.containerSize]; } + (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection - traitCollectionContext:(id)traitCollectionContext + containerSize:(CGSize)windowSize { ASTraitCollection *asyncTraitCollection = nil; if (AS_AT_LEAST_IOS9) { @@ -71,7 +70,7 @@ horizontalSizeClass:traitCollection.horizontalSizeClass verticalSizeClass:traitCollection.verticalSizeClass forceTouchCapability:traitCollection.forceTouchCapability - traitCollectionContext:traitCollectionContext]; + containerSize:windowSize]; } else if (AS_AT_LEAST_IOS8) { asyncTraitCollection = [[[self class] alloc] initWithDisplayScale:traitCollection.displayScale @@ -79,7 +78,7 @@ horizontalSizeClass:traitCollection.horizontalSizeClass verticalSizeClass:traitCollection.verticalSizeClass forceTouchCapability:0 - traitCollectionContext:traitCollectionContext]; + containerSize:windowSize]; } else { asyncTraitCollection = [[[self class] alloc] init]; } @@ -95,7 +94,7 @@ .userInterfaceIdiom = self.userInterfaceIdiom, .verticalSizeClass = self.verticalSizeClass, .forceTouchCapability = self.forceTouchCapability, - .displayContext = self.traitCollectionContext, + .containerSize = self.containerSize, }; } @@ -105,7 +104,7 @@ self.horizontalSizeClass == traitCollection.horizontalSizeClass && self.verticalSizeClass == traitCollection.verticalSizeClass && self.userInterfaceIdiom == traitCollection.userInterfaceIdiom && - self.traitCollectionContext == traitCollection.traitCollectionContext && + CGSizeEqualToSize(self.containerSize, traitCollection.containerSize) && self.forceTouchCapability == traitCollection.forceTouchCapability; } diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.m b/AsyncDisplayKit/Details/CGRect+ASConvenience.m index 32ca3ca04d..76e6fbc4c1 100644 --- a/AsyncDisplayKit/Details/CGRect+ASConvenience.m +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.m @@ -9,8 +9,6 @@ // #import "CGRect+ASConvenience.h" -#import "ASScrollDirection.h" -#import "ASLayoutController.h" ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, ASRangeTuningParameters rangeTuningParameters) diff --git a/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.h b/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.h new file mode 100644 index 0000000000..179e685639 --- /dev/null +++ b/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.h @@ -0,0 +1,25 @@ +// +// NSIndexSet+ASHelpers.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 6/23/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface NSIndexSet (ASHelpers) + +- (NSIndexSet *)as_indexesByMapping:(NSUInteger (^)(NSUInteger idx))block; + +- (NSIndexSet *)as_intersectionWithIndexes:(NSIndexSet *)indexes; + +/// Returns all the item indexes from the given index paths that are in the given section. ++ (NSIndexSet *)as_indexSetFromIndexPaths:(NSArray *)indexPaths inSection:(NSUInteger)section; + +/// If you've got an old index, and you insert items using this index set, this returns the change to get to the new index. +- (NSUInteger)as_indexChangeByInsertingItemsBelowIndex:(NSUInteger)index; + +- (NSString *)as_smallDescription; + +@end diff --git a/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.m b/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.m new file mode 100644 index 0000000000..de3314c07a --- /dev/null +++ b/AsyncDisplayKit/Details/NSIndexSet+ASHelpers.m @@ -0,0 +1,80 @@ +// +// NSIndexSet+ASHelpers.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 6/23/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +#import "NSIndexSet+ASHelpers.h" + +@implementation NSIndexSet (ASHelpers) + +- (NSIndexSet *)as_indexesByMapping:(NSUInteger (^)(NSUInteger))block +{ + NSMutableIndexSet *result = [NSMutableIndexSet indexSet]; + [self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { + NSUInteger newIndex = block(i); + if (newIndex != NSNotFound) { + [result addIndex:newIndex]; + } + } + }]; + return result; +} + +- (NSIndexSet *)as_intersectionWithIndexes:(NSIndexSet *)indexes +{ + NSMutableIndexSet *result = [NSMutableIndexSet indexSet]; + [self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + [indexes enumerateRangesInRange:range options:kNilOptions usingBlock:^(NSRange range, BOOL * _Nonnull stop) { + [result addIndexesInRange:range]; + }]; + }]; + return result; +} + ++ (NSIndexSet *)as_indexSetFromIndexPaths:(NSArray *)indexPaths inSection:(NSUInteger)section +{ + NSMutableIndexSet *result = [NSMutableIndexSet indexSet]; + for (NSIndexPath *indexPath in indexPaths) { + if (indexPath.section == section) { + [result addIndex:indexPath.item]; + } + } + return result; +} + +- (NSUInteger)as_indexChangeByInsertingItemsBelowIndex:(NSUInteger)index +{ + __block NSUInteger newIndex = index; + [self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { + if (i <= newIndex) { + newIndex += 1; + } else { + *stop = YES; + } + } + }]; + return newIndex - index; +} + +- (NSString *)as_smallDescription +{ + NSMutableString *result = [NSMutableString stringWithString:@"{ "]; + [self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + if (range.length == 1) { + [result appendFormat:@"%tu ", range.location]; + } else { + [result appendFormat:@"%tu-%tu ", range.location, NSMaxRange(range) - 1]; + } + }]; + [result appendString:@"}"]; + return result; +} + +@end diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm index 5342e34f33..f869f5e108 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.mm @@ -20,7 +20,7 @@ NSInteger const ASDefaultTransactionPriority = 0; @interface ASDisplayNodeAsyncTransactionOperation : NSObject - (instancetype)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock; @property (nonatomic, copy) asyncdisplaykit_async_transaction_operation_completion_block_t operationCompletionBlock; -@property (atomic, strong) id value; // set on bg queue by the operation block +@property (nonatomic, strong) id value; // set on bg queue by the operation block @end @implementation ASDisplayNodeAsyncTransactionOperation diff --git a/AsyncDisplayKit/Details/UIView+ASConvenience.h b/AsyncDisplayKit/Details/UIView+ASConvenience.h index cb3e561414..ef0d10211c 100644 --- a/AsyncDisplayKit/Details/UIView+ASConvenience.h +++ b/AsyncDisplayKit/Details/UIView+ASConvenience.h @@ -67,16 +67,16 @@ NS_ASSUME_NONNULL_BEGIN We don't declare them here, so _ASPendingState does not complain about them being not implemented, as they are already on NSObject - @property (atomic, assign) BOOL isAccessibilityElement; - @property (atomic, copy) NSString *accessibilityLabel; - @property (atomic, copy) NSString *accessibilityHint; - @property (atomic, copy) NSString *accessibilityValue; - @property (atomic, assign) UIAccessibilityTraits accessibilityTraits; - @property (atomic, assign) CGRect accessibilityFrame; - @property (atomic, strong) NSString *accessibilityLanguage; - @property (atomic, assign) BOOL accessibilityElementsHidden; - @property (atomic, assign) BOOL accessibilityViewIsModal; - @property (atomic, assign) BOOL shouldGroupAccessibilityChildren; + @property (nonatomic, assign) BOOL isAccessibilityElement; + @property (nonatomic, copy) NSString *accessibilityLabel; + @property (nonatomic, copy) NSString *accessibilityHint; + @property (nonatomic, copy) NSString *accessibilityValue; + @property (nonatomic, assign) UIAccessibilityTraits accessibilityTraits; + @property (nonatomic, assign) CGRect accessibilityFrame; + @property (nonatomic, strong) NSString *accessibilityLanguage; + @property (nonatomic, assign) BOOL accessibilityElementsHidden; + @property (nonatomic, assign) BOOL accessibilityViewIsModal; + @property (nonatomic, assign) BOOL shouldGroupAccessibilityChildren; */ // Accessibility identification support diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.h b/AsyncDisplayKit/Details/_ASDisplayLayer.h index 6e8e81ebcc..1dde2e319d 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.h +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.h @@ -24,7 +24,7 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); @default YES (note that this might change for subclasses) */ -@property (atomic, assign) BOOL displaysAsynchronously; +@property (nonatomic, assign) BOOL displaysAsynchronously; /** @summary Cancels any pending async display. @@ -48,7 +48,7 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); @desc The asyncDelegate will have the opportunity to override the methods related to async display. */ -@property (atomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate; +@property (nonatomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate; /** @summary Suspends both asynchronous and synchronous display of the receiver if YES. @@ -58,7 +58,7 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); @default NO */ -@property (atomic, assign, getter=isDisplaySuspended) BOOL displaySuspended; +@property (nonatomic, assign, getter=isDisplaySuspended) BOOL displaySuspended; /** @summary Bypasses asynchronous rendering and performs a blocking display immediately on the current thread. diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index ba0de48e0f..2d4deb5259 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -86,8 +86,7 @@ UIView *currentSuperview = self.superview; if (!currentSuperview && newSuperview) { self.keepalive_node = _node; - } - else if (currentSuperview && !newSuperview) { + } else if (currentSuperview && !newSuperview) { // Clearing keepalive_node may cause deallocation of the node. In this case, __exitHierarchy may not have an opportunity (e.g. _node will be cleared // by the time -didMoveToWindow occurs after this) to clear the Visible interfaceState, which we need to do before deallocation to meet an API guarantee. if (_node.inHierarchy) { @@ -95,6 +94,8 @@ } self.keepalive_node = nil; } + + ASDisplayNodeAssert(self.keepalive_node == nil || newSuperview != nil, @"Keepalive reference should not exist if there is no superview."); if (newSuperview) { ASDisplayNode *supernode = _node.supernode; diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm index 57ddef0d59..baa2b1b7a2 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -8,7 +8,6 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "_ASDisplayViewAccessiblity.h" #import "_ASDisplayView.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" diff --git a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm index 00201a79b9..99ec4061fe 100644 --- a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm @@ -11,7 +11,6 @@ #import "ASBackgroundLayoutSpec.h" #import "ASAssert.h" -#import "ASBaseDefines.h" #import "ASLayout.h" static NSUInteger const kForegroundChildIndex = 0; diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm index 8a8d7266d7..e5ffb8d6d7 100644 --- a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm @@ -10,7 +10,6 @@ #import "ASCenterLayoutSpec.h" -#import "ASInternalHelpers.h" #import "ASLayout.h" @implementation ASCenterLayoutSpec diff --git a/AsyncDisplayKit/Layout/ASDimension.h b/AsyncDisplayKit/Layout/ASDimension.h index e0a043c632..822a1c1963 100644 --- a/AsyncDisplayKit/Layout/ASDimension.h +++ b/AsyncDisplayKit/Layout/ASDimension.h @@ -12,9 +12,7 @@ #import #import -/** - A dimension relative to constraints to be provided in the future. - */ +/** A dimension relative to constraints to be provided in the future. */ typedef NS_ENUM(NSInteger, ASRelativeDimensionType) { /** Just a number. It will always resolve to exactly this amount. This is the default type. */ ASRelativeDimensionTypePoints, @@ -40,7 +38,7 @@ extern ASRelativeDimension const ASRelativeDimensionUnconstrained; ASDISPLAYNODE_EXTERN_C_BEGIN NS_ASSUME_NONNULL_BEGIN -#pragma mark ASRelativeDimension +#pragma mark - ASRelativeDimension extern ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value); @@ -56,8 +54,7 @@ extern NSString *NSStringFromASRelativeDimension(ASRelativeDimension dimension); extern CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat parent); -#pragma mark - -#pragma mark ASSizeRange +#pragma mark - ASSizeRange extern ASSizeRange ASSizeRangeMake(CGSize min, CGSize max); @@ -68,8 +65,8 @@ extern ASSizeRange ASSizeRangeMakeExactSize(CGSize size); extern CGSize ASSizeRangeClamp(ASSizeRange sizeRange, CGSize size); /** - Intersects another size range. If the other size range does not overlap in either dimension, this size range - "wins" by returning a single point within its own range that is closest to the non-overlapping range. + * Intersects another size range. If the other size range does not overlap in either dimension, this size range + * "wins" by returning a single point within its own range that is closest to the non-overlapping range. */ extern ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRange); diff --git a/AsyncDisplayKit/Layout/ASDimension.mm b/AsyncDisplayKit/Layout/ASDimension.mm index b07e223f87..d7309d7370 100644 --- a/AsyncDisplayKit/Layout/ASDimension.mm +++ b/AsyncDisplayKit/Layout/ASDimension.mm @@ -13,21 +13,28 @@ ASRelativeDimension const ASRelativeDimensionUnconstrained = {}; -#pragma mark ASRelativeDimension +#pragma mark - ASRelativeDimension ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) { - if (type == ASRelativeDimensionTypePoints) { ASDisplayNodeCAssertPositiveReal(@"Points", value); } + if (type == ASRelativeDimensionTypePoints) { + ASDisplayNodeCAssertPositiveReal(@"Points", value); + } else if (type == ASRelativeDimensionTypePercent) { + // 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); + } ASRelativeDimension dimension; dimension.type = type; dimension.value = value; return dimension; } ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points) { + ASDisplayNodeCAssertPositiveReal(@"Points", points); return ASRelativeDimensionMake(ASRelativeDimensionTypePoints, points); } ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent) { + // ASDisplayNodeCAssert( 0 <= percent && percent <= 1.0, @"ASRelativeDimension percent value (%f) must be between 0 and 1.", percent); return ASRelativeDimensionMake(ASRelativeDimensionTypePercent, percent); } @@ -61,8 +68,7 @@ CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat parent } } -#pragma mark - -#pragma mark ASSizeRange +#pragma mark - ASSizeRange ASSizeRange ASSizeRangeMake(CGSize min, CGSize max) { diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm index 685c1a29b0..9a6a32707c 100644 --- a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm @@ -11,7 +11,6 @@ #import "ASInsetLayoutSpec.h" #import "ASAssert.h" -#import "ASBaseDefines.h" #import "ASInternalHelpers.h" #import "ASLayout.h" diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/AsyncDisplayKit/Layout/ASLayout.h index 7b23fd704b..31465987fb 100644 --- a/AsyncDisplayKit/Layout/ASLayout.h +++ b/AsyncDisplayKit/Layout/ASLayout.h @@ -145,4 +145,19 @@ extern BOOL CGPointIsNull(CGPoint point); @end +@interface ASLayout (Debugging) + +/** + * Recrusively output the description of the layout tree. + */ +- (NSString *)recursiveDescription; + +@end + +@interface ASLayout (Unavailable) + +- (instancetype)init __unavailable; + +@end + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index ca0033e8be..575f593ed6 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -10,7 +10,6 @@ #import "ASLayout.h" -#import "ASAssert.h" #import "ASDimension.h" #import "ASInternalHelpers.h" #import "ASLayoutSpecUtilities.h" @@ -24,6 +23,21 @@ extern BOOL CGPointIsNull(CGPoint point) return isnan(point.x) && isnan(point.y); } +/** + * Creates an defined number of " |" indent blocks for the recursive description. + */ +static inline NSString * descriptionIndents(NSUInteger indents) +{ + NSMutableString *description = [NSMutableString string]; + for (NSUInteger i = 0; i < indents; i++) { + [description appendString:@" |"]; + } + if (indents > 0) { + [description appendString:@" "]; + } + return description; +} + @interface ASLayout () /** @@ -115,6 +129,7 @@ extern BOOL CGPointIsNull(CGPoint point) return [self layoutWithLayoutableObject:layoutableObject constrainedSizeRange:sizeRange size:size + position:CGPointNull sublayouts:nil]; } @@ -212,4 +227,29 @@ extern BOOL CGPointIsNull(CGPoint point) return subnodeFrame; } +#pragma mark - Description + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<, layoutable = %@, position = %@; size = %@; constrainedSizeRange = %@>", + self, self.layoutableObject, NSStringFromCGPoint(self.position), NSStringFromCGSize(self.size), NSStringFromASSizeRange(self.constrainedSizeRange)]; +} + +- (NSString *)recursiveDescription +{ + return [self _recursiveDescriptionForLayout:self level:0]; +} + +- (NSString *)_recursiveDescriptionForLayout:(ASLayout *)layout level:(NSUInteger)level +{ + NSMutableString *description = [NSMutableString string]; + [description appendString:descriptionIndents(level)]; + [description appendString:[layout description]]; + for (ASLayout *sublayout in layout.sublayouts) { + [description appendString:@"\n"]; + [description appendString:[self _recursiveDescriptionForLayout:sublayout level:level + 1]]; + } + return description; +} + @end diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm index b0bd8ee8ed..4c66b3c129 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -11,10 +11,8 @@ #import "ASLayoutSpec.h" #import "ASAssert.h" -#import "ASBaseDefines.h" #import "ASEnvironmentInternal.h" -#import "ASInternalHelpers.h" #import "ASLayout.h" #import "ASThread.h" #import "ASTraitCollection.h" @@ -27,7 +25,7 @@ typedef std::map, std::less> ASCh @interface ASLayoutSpec() { ASEnvironmentState _environmentState; - ASDN::RecursiveMutex _propertyLock; + ASDN::RecursiveMutex __instanceLock__; ASChildMap _children; } @end @@ -54,6 +52,11 @@ typedef std::map, std::less> ASCh return ASLayoutableTypeLayoutSpec; } +- (BOOL)canLayoutAsynchronous +{ + return YES; +} + #pragma mark - Layout - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize @@ -145,9 +148,11 @@ typedef std::map, std::less> ASCh ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); _children.clear(); - [children enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - _children[idx] = obj; - }]; + NSUInteger i = 0; + for (id child in children) { + _children[i] = [self layoutableToAddFromLayoutable:child]; + i += 1; + } } - (id)childForIndex:(NSUInteger)index @@ -222,7 +227,7 @@ ASEnvironmentLayoutExtensibilityForwarding - (ASTraitCollection *)asyncTraitCollection { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return [ASTraitCollection traitCollectionWithASEnvironmentTraitCollection:self.environmentTraitCollection]; } diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h index 1199c89fdf..0f68d5e2dd 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.h +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -46,8 +46,16 @@ NS_ASSUME_NONNULL_BEGIN */ @protocol ASLayoutable +/** + * @abstract Returns type of layoutable + */ @property (nonatomic, readonly) ASLayoutableType layoutableType; +/** + * @abstract Returns if the layoutable can be used to layout in an asynchronous way on a background thread. + */ +@property (nonatomic, readonly) BOOL canLayoutAsynchronous; + /** * @abstract Calculate a layout based on given size range. * diff --git a/AsyncDisplayKit/Layout/ASLayoutable.mm b/AsyncDisplayKit/Layout/ASLayoutable.mm index 0113150e84..de0f6e5cad 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.mm +++ b/AsyncDisplayKit/Layout/ASLayoutable.mm @@ -11,16 +11,10 @@ // #import "ASLayoutablePrivate.h" -#import "ASInternalHelpers.h" #import "ASEnvironmentInternal.h" #import "ASDisplayNodeInternal.h" -#import "ASTextNode.h" -#import "ASLayoutSpec.h" -#import "pthread.h" #import -#import -#import "ASThread.h" int32_t const ASLayoutableContextInvalidTransitionID = 0; int32_t const ASLayoutableContextDefaultTransitionID = ASLayoutableContextInvalidTransitionID + 1; diff --git a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm index 669a5ad9eb..02f7b4d5b4 100644 --- a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm @@ -11,7 +11,6 @@ #import "ASOverlayLayoutSpec.h" #import "ASAssert.h" -#import "ASBaseDefines.h" #import "ASLayout.h" static NSUInteger const kUnderlayChildIndex = 0; diff --git a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm index 2825101233..8b51c71148 100644 --- a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm @@ -14,7 +14,6 @@ #import #import "ASAssert.h" -#import "ASBaseDefines.h" #import "ASInternalHelpers.h" #import "ASLayout.h" diff --git a/AsyncDisplayKit/Layout/ASRelativeSize.h b/AsyncDisplayKit/Layout/ASRelativeSize.h index 479b865c43..12f4845531 100644 --- a/AsyncDisplayKit/Layout/ASRelativeSize.h +++ b/AsyncDisplayKit/Layout/ASRelativeSize.h @@ -35,14 +35,16 @@ extern ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained; ASDISPLAYNODE_EXTERN_C_BEGIN NS_ASSUME_NONNULL_BEGIN -#pragma mark - -#pragma mark ASRelativeSize +#pragma mark - ASRelativeSize extern ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height); /** Convenience constructor to provide size in Points. */ extern ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size); +/** Convenience constructor to provide size in Percentage. */ +extern ASRelativeSize ASRelativeSizeMakeWithPercent(CGFloat percent); + /** Resolve this relative size relative to a parent size. */ extern CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize); @@ -50,8 +52,7 @@ extern BOOL ASRelativeSizeEqualToRelativeSize(ASRelativeSize lhs, ASRelativeSize extern NSString *NSStringFromASRelativeSize(ASRelativeSize size); -#pragma mark - -#pragma mark ASRelativeSizeRange +#pragma mark - ASRelativeSizeRange extern ASRelativeSizeRange ASRelativeSizeRangeMake(ASRelativeSize min, ASRelativeSize max); @@ -60,16 +61,15 @@ extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelati extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact); +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactPercent(CGFloat percent); + extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight); extern BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs); -/** - Provided a parent size, compute final dimensions for this RelativeSizeRange to arrive at a SizeRange. - */ -extern ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, - CGSize parentSize); +/** Provided a parent size, compute final dimensions for this RelativeSizeRange to arrive at a SizeRange. */ +extern ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, CGSize parentSize); NS_ASSUME_NONNULL_END ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Layout/ASRelativeSize.mm b/AsyncDisplayKit/Layout/ASRelativeSize.mm index 9d08009f78..575c1f197e 100644 --- a/AsyncDisplayKit/Layout/ASRelativeSize.mm +++ b/AsyncDisplayKit/Layout/ASRelativeSize.mm @@ -9,12 +9,10 @@ // #import "ASRelativeSize.h" -#import "ASAssert.h" ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained = {}; -#pragma mark - -#pragma mark ASRelativeSize +#pragma mark - ASRelativeSize ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) { @@ -27,6 +25,12 @@ ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size) ASRelativeDimensionMakeWithPoints(size.height)); } +ASRelativeSize ASRelativeSizeMakeWithPercent(CGFloat percent) +{ + return ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(percent), + ASRelativeDimensionMakeWithPercent(percent)); +} + CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize) { return CGSizeMake(ASRelativeDimensionResolve(relativeSize.width, parentSize.width), @@ -46,8 +50,7 @@ NSString *NSStringFromASRelativeSize(ASRelativeSize size) NSStringFromASRelativeDimension(size.height)]; } -#pragma mark - -#pragma mark ASRelativeSizeRange +#pragma mark - ASRelativeSizeRange ASRelativeSizeRange ASRelativeSizeRangeMake(ASRelativeSize min, ASRelativeSize max) { @@ -64,6 +67,11 @@ ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact)); } +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactPercent(CGFloat percent) +{ + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithPercent(percent)); +} + ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) { diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index e169cf488b..2393972a53 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -11,18 +11,15 @@ #import #import -#import "ASBaseDefines.h" #import "ASInternalHelpers.h" #import "ASLayoutSpecUtilities.h" #import "ASStackBaselinePositionedLayout.h" -#import "ASStackLayoutSpecUtilities.h" -#import "ASStackUnpositionedLayout.h" #import "ASThread.h" @implementation ASStackLayoutSpec { - ASDN::RecursiveMutex _propertyLock; + ASDN::RecursiveMutex __instanceLock__; } - (instancetype)init @@ -147,11 +144,11 @@ // and min descender in case this spec is a child in another spec that wants to align to a baseline. const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize); if (self.direction == ASStackLayoutDirectionVertical) { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); self.ascender = [[self.children firstObject] ascender]; self.descender = [[self.children lastObject] descender]; } else { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); self.ascender = baselinePositionedLayout.ascender; self.descender = baselinePositionedLayout.descender; } diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm index a7e93805c0..516e79e721 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm @@ -11,7 +11,6 @@ #import "ASStaticLayoutSpec.h" #import "ASLayoutSpecUtilities.h" -#import "ASInternalHelpers.h" #import "ASLayout.h" @implementation ASStaticLayoutSpec diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Private/ASDataController+Subclasses.h similarity index 75% rename from AsyncDisplayKit/Details/ASDataController+Subclasses.h rename to AsyncDisplayKit/Private/ASDataController+Subclasses.h index d837540362..dd3905d96c 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Private/ASDataController+Subclasses.h @@ -9,6 +9,7 @@ // #pragma once +#import @class ASIndexedNodeContext; @@ -33,20 +34,30 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS */ - (NSMutableArray *)completedNodesOfKind:(NSString *)kind; +/** + * Ensure that next time `itemCountsFromDataSource` is called, new values are retrieved. + * + * This must be called on the main thread. + */ +- (void)invalidateDataSourceItemCounts; + +/** + * Returns the most recently gathered item counts from the data source. If the counts + * have been invalidated, this synchronously queries the data source and saves the result. + * + * This must be called on the main thread. + */ +- (std::vector)itemCountsFromDataSource; + #pragma mark - Node sizing /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. - */ -- (void)batchLayoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock; - -/** - * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. * - * @discussion Once nodes have loaded their views, we can't layout in the background so this is a chance - * to do so immediately on the main thread. + * This method runs synchronously. + * @param batchCompletion A handler to be run after each batch is completed. It is executed synchronously on the calling thread. */ -- (void)layoutLoadedNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts ofKind:(NSString *)kind; +- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler; /** * Provides the size range for a specific node during the layout process. @@ -128,28 +139,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS */ - (void)willDeleteSections:(NSIndexSet *)sections; -/** - * Notifies the subclass to perform any work needed before the given sections will be reloaded. - * - * @discussion This method will be performed before the data controller enters its editing queue, usually on the main - * thread. 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. - * - * @param sections Indices of sections to be reloaded - */ -- (void)prepareForReloadSections:(NSIndexSet *)sections; - -/** - * Notifies the subclass that the data controller will reload the sections in the given index set - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform any additional transformations like supplementary views - * or header/footer nodes. - * - * @param sections Indices of sections to be reloaded - */ -- (void)willReloadSections:(NSIndexSet *)sections; - /** * Notifies the subclass that the data controller will move a section to a new position * @@ -206,26 +195,4 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS */ - (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths; -/** - * Notifies the subclass to perform any work needed before the given rows will be reloaded. - * - * @discussion This method will be performed before the data controller enters its editing queue, usually on the main - * thread. 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. - * - * @param indexPaths Index paths for the rows to be reloaded. - */ -- (void)prepareForReloadRowsAtIndexPaths:(NSArray *)indexPaths; - -/** - * Notifies the subclass that the data controller will reload the rows at the given index paths. - * - * @discussion This method will be performed on the data controller's editing background queue before the parent's - * concrete implementation. This is a great place to perform any additional transformations like supplementary views - * or header/footer nodes. - * - * @param indexPaths Index paths for the rows to be reloaded. - */ -- (void)willReloadRowsAtIndexPaths:(NSArray *)indexPaths; - @end diff --git a/AsyncDisplayKit/Private/ASDefaultPlayButton.h b/AsyncDisplayKit/Private/ASDefaultPlayButton.h index 08939784f7..cab4ae4231 100644 --- a/AsyncDisplayKit/Private/ASDefaultPlayButton.h +++ b/AsyncDisplayKit/Private/ASDefaultPlayButton.h @@ -10,7 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import +#import @interface ASDefaultPlayButton : ASButtonNode diff --git a/AsyncDisplayKit/Private/ASDefaultPlayButton.m b/AsyncDisplayKit/Private/ASDefaultPlayButton.m index b64650235b..37012d609e 100644 --- a/AsyncDisplayKit/Private/ASDefaultPlayButton.m +++ b/AsyncDisplayKit/Private/ASDefaultPlayButton.m @@ -11,6 +11,7 @@ // #import "ASDefaultPlayButton.h" +#import "_ASDisplayLayer.h" @implementation ASDefaultPlayButton diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index 6d25d63a43..e426812606 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -14,7 +14,6 @@ #import "ASAssert.h" #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNode+Beta.h" @interface ASDisplayNode () <_ASDisplayLayerDelegate> @end @@ -76,6 +75,20 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, } } +#define DISPLAY_COUNT_INCREMENT() __ASDisplayLayerIncrementConcurrentDisplayCount(asynchronous, rasterizing); +#define DISPLAY_COUNT_DECREMENT() __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); +#define CHECK_CANCELLED_AND_RETURN_NIL_WITH_DECREMENT(expr) if (isCancelledBlock()) { \ + expr; \ + __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); \ + return nil; \ + } \ + +#define CHECK_CANCELLED_AND_RETURN_NIL(expr) if (isCancelledBlock()) { \ + expr; \ + return nil; \ + } \ + + - (NSObject *)drawParameters { if (_flags.implementsDrawParameters) { @@ -180,137 +193,122 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, } } -- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock rasterizing:(BOOL)rasterizing +- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous + isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock + rasterizing:(BOOL)rasterizing { asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil; + ASDisplayNodeFlags flags; + + __instanceLock__.lock(); - ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized), @"Rasterized descendants should never display unless being drawn into the rasterized container."); + flags = _flags; + + // We always create a graphics context, unless a -display method is used, OR if we are a subnode drawing into a rasterized parent. + BOOL shouldCreateGraphicsContext = (flags.implementsInstanceImageDisplay == NO && flags.implementsImageDisplay == NO && rasterizing == NO); + BOOL shouldBeginRasterizing = (rasterizing == NO && flags.shouldRasterizeDescendants); + BOOL usesInstanceMethodDisplay = (flags.implementsInstanceDrawRect || flags.implementsInstanceImageDisplay); + BOOL usesImageDisplay = (flags.implementsImageDisplay || flags.implementsInstanceImageDisplay); + BOOL usesDrawRect = (flags.implementsDrawRect || flags.implementsInstanceDrawRect); + + if (usesImageDisplay == NO && usesDrawRect == NO && shouldBeginRasterizing == NO) { + // Early exit before requesting more expensive properties like bounds and opaque from the layer. + __instanceLock__.unlock(); + return nil; + } + + BOOL opaque = self.opaque; + CGRect bounds = self.bounds; + CGFloat contentsScaleForDisplay = _contentsScaleForDisplay; - if (!rasterizing && self.shouldRasterizeDescendants) { - CGRect bounds = self.bounds; - if (CGRectIsEmpty(bounds)) { - return nil; - } + // Capture drawParameters from delegate on main thread, if this node is displaying itself rather than recursively rasterizing. + id drawParameters = (shouldBeginRasterizing == NO ? [self drawParameters] : nil); + __instanceLock__.unlock(); + + // Only the -display methods should be called if we can't size the graphics buffer to use. + if (CGRectIsEmpty(bounds) && (shouldBeginRasterizing || shouldCreateGraphicsContext)) { + return nil; + } + + ASDisplayNodeAssert(contentsScaleForDisplay != 0.0, @"Invalid contents scale"); + ASDisplayNodeAssert(usesInstanceMethodDisplay == NO || (flags.implementsDrawRect == NO && flags.implementsImageDisplay == NO), + @"Node %@ should not implement both class and instance method display or draw", self); + ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized), + @"Rasterized descendants should never display unless being drawn into the rasterized container."); + + if (shouldBeginRasterizing == YES) { // Collect displayBlocks for all descendants. NSMutableArray *displayBlocks = [NSMutableArray array]; [self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks]; - - CGFloat contentsScaleForDisplay = self.contentsScaleForDisplay; - BOOL opaque = self.opaque && CGColorGetAlpha(self.backgroundColor.CGColor) == 1.0f; - - ASDisplayNodeAssert(self.contentsScaleForDisplay != 0.0, @"Invalid contents scale"); + CHECK_CANCELLED_AND_RETURN_NIL(); + + // If [UIColor clearColor] or another semitransparent background color is used, include alpha channel when rasterizing. + // Unlike CALayer drawing, we include the backgroundColor as a base during rasterization. + opaque = opaque && CGColorGetAlpha(self.backgroundColor.CGColor) == 1.0f; displayBlock = ^id{ - __ASDisplayLayerIncrementConcurrentDisplayCount(asynchronous, rasterizing); - if (isCancelledBlock()) { - __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); - return nil; - } - - ASDN_DELAY_FOR_DISPLAY(); + DISPLAY_COUNT_INCREMENT(); + CHECK_CANCELLED_AND_RETURN_NIL_WITH_DECREMENT(); + UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); for (dispatch_block_t block in displayBlocks) { - if (isCancelledBlock()) { - UIGraphicsEndImageContext(); - __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); - return nil; - } + CHECK_CANCELLED_AND_RETURN_NIL_WITH_DECREMENT(UIGraphicsEndImageContext()); block(); } - + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); - + ASDN_DELAY_FOR_DISPLAY(); + DISPLAY_COUNT_DECREMENT(); return image; }; - } else if (_flags.implementsInstanceImageDisplay || _flags.implementsImageDisplay) { - // Capture drawParameters from delegate on main thread - id drawParameters = [self drawParameters]; - + } else { displayBlock = ^id{ - __ASDisplayLayerIncrementConcurrentDisplayCount(asynchronous, rasterizing); - if (isCancelledBlock()) { - __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); - return nil; - } - - ASDN_DELAY_FOR_DISPLAY(); - - UIImage *result = nil; - //We can't call _willDisplayNodeContentWithRenderingContext or _didDisplayNodeContentWithRenderingContext because we don't - //have a context. We rely on implementors of displayWithParameters:isCancelled: to call - if (_flags.implementsInstanceImageDisplay) { - result = [self displayWithParameters:drawParameters isCancelled:isCancelledBlock]; - } else { - result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock]; - } - __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); - return result; - }; - - } else if (_flags.implementsInstanceDrawRect || _flags.implementsDrawRect) { + DISPLAY_COUNT_INCREMENT(); + CHECK_CANCELLED_AND_RETURN_NIL_WITH_DECREMENT(); - CGRect bounds = self.bounds; - if (CGRectIsEmpty(bounds)) { - return nil; - } - - // Capture drawParameters from delegate on main thread - id drawParameters = [self drawParameters]; - CGFloat contentsScaleForDisplay = self.contentsScaleForDisplay; - BOOL opaque = self.opaque; - - displayBlock = ^id{ - __ASDisplayLayerIncrementConcurrentDisplayCount(asynchronous, rasterizing); - if (isCancelledBlock()) { - __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); - return nil; - } - - ASDN_DELAY_FOR_DISPLAY(); - - if (!rasterizing) { + if (shouldCreateGraphicsContext) { UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); + CHECK_CANCELLED_AND_RETURN_NIL_WITH_DECREMENT( UIGraphicsEndImageContext(); ); } CGContextRef currentContext = UIGraphicsGetCurrentContext(); + UIImage *image = nil; + + // For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or + // _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs. if (currentContext && _willDisplayNodeContentWithRenderingContext) { _willDisplayNodeContentWithRenderingContext(currentContext); } - if (_flags.implementsInstanceDrawRect) { - [self drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing]; - } else { - [[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing]; + // Decide if we use a class or instance method to draw or display. + id object = usesInstanceMethodDisplay ? self : [self class]; + + if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly. + image = [object displayWithParameters:drawParameters + isCancelled:isCancelledBlock]; + } else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext. + [object drawRect:bounds withParameters:drawParameters + isCancelled:isCancelledBlock isRasterizing:rasterizing]; } if (currentContext && _didDisplayNodeContentWithRenderingContext) { _didDisplayNodeContentWithRenderingContext(currentContext); } - - if (isCancelledBlock()) { - if (!rasterizing) { - UIGraphicsEndImageContext(); - } - __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); - return nil; - } - - UIImage *image = nil; - if (!rasterizing) { + + if (shouldCreateGraphicsContext) { + CHECK_CANCELLED_AND_RETURN_NIL_WITH_DECREMENT( UIGraphicsEndImageContext(); ); image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } - __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); - + ASDN_DELAY_FOR_DISPLAY(); + DISPLAY_COUNT_DECREMENT(); return image; }; - } return [displayBlock copy]; @@ -320,7 +318,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, { ASDisplayNodeAssertMainThread(); - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_hierarchyState & ASHierarchyStateRasterized) { return; @@ -353,7 +351,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, ASDisplayNodeCAssertMainThread(); if (!canceled && !isCancelledBlock()) { UIImage *image = (UIImage *)value; - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); + BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero)); if (stretchable) { ASDisplayNodeSetupLayerContentsWithResizableImage(_layer, image); } else { @@ -395,25 +393,25 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, - (ASDisplayNodeContextModifier)willDisplayNodeContentWithRenderingContext { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _willDisplayNodeContentWithRenderingContext; } - (ASDisplayNodeContextModifier)didDisplayNodeContentWithRenderingContext { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _didDisplayNodeContentWithRenderingContext; } - (void)setWillDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _willDisplayNodeContentWithRenderingContext = contextModifier; } - (void)setDidDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier; { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); _didDisplayNodeContentWithRenderingContext = contextModifier; } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 78ce6ce05e..fd80d0e6d6 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -17,7 +17,6 @@ #import "ASDisplayNode.h" #import "ASSentinel.h" #import "ASThread.h" -#import "_ASDisplayLayer.h" NS_ASSUME_NONNULL_BEGIN @@ -135,6 +134,11 @@ inline BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState */ @property (nonatomic, assign) BOOL shouldBypassEnsureDisplay; +/** + * @abstract Checks whether a node should be scheduled for display, considering its current and new interface states. + */ +- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState; + @end @interface UIView (ASDisplayNodeInternal) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index c2c7b2ca3a..aa99cfd031 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -37,9 +37,9 @@ #define __loaded(node) (node->_view != nil || (node->_layer != nil && node->_flags.layerBacked)) #if DISPLAYNODE_USE_LOCKS -#define _bridge_prologue_read ASDN::MutexLocker l(_propertyLock); ASDisplayNodeAssertThreadAffinity(self) -#define _bridge_prologue_write ASDN::MutexLocker l(_propertyLock) -#define _bridge_prologue_write_unlock ASDN::MutexUnlocker u(_propertyLock) +#define _bridge_prologue_read ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssertThreadAffinity(self) +#define _bridge_prologue_write ASDN::MutexLocker l(__instanceLock__) +#define _bridge_prologue_write_unlock ASDN::MutexUnlocker u(__instanceLock__) #else #define _bridge_prologue_read ASDisplayNodeAssertThreadAffinity(self) #define _bridge_prologue_write diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index ee3da6b041..f6337ba03c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -61,7 +61,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo _ASPendingState *_pendingViewState; // Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads. - ASDN::RecursiveMutex _propertyLock; + ASDN::RecursiveMutex __instanceLock__; UIView *_view; CALayer *_layer; @@ -116,13 +116,14 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo CGFloat _contentsScaleForDisplay; ASEnvironmentState _environmentState; - ASLayout *_layout; + ASLayout *_calculatedLayout; + UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; // Main thread only - _ASTransitionContext *_transitionContext; + _ASTransitionContext *_pendingLayoutTransitionContext; BOOL _usesImplicitHierarchyManagement; int32_t _pendingTransitionID; diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.h b/AsyncDisplayKit/Private/ASEnvironmentInternal.h index 26f0747b81..7179830eef 100644 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.h +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.h @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASEnvironment.h" +#import #pragma once @@ -43,13 +43,13 @@ enum class ASEnvironmentStatePropagation { DOWN, UP }; static const struct ASEnvironmentStateExtensions ASEnvironmentDefaultStateExtensions = {}; -static const struct ASEnvironmentLayoutOptionsState ASEnvironmentDefaultLayoutOptionsState = {}; +static const struct ASEnvironmentLayoutOptionsState ASEnvironmentDefaultLayoutOptionsState = ASEnvironmentLayoutOptionsStateMakeDefault(); ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentLayoutOptionsState state, ASEnvironmentStatePropagation propagation); -static const struct ASEnvironmentHierarchyState ASEnvironmentDefaultHierarchyState = {}; +static const struct ASEnvironmentHierarchyState ASEnvironmentDefaultHierarchyState = ASEnvironmentHierarchyStateMakeDefault(); ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentHierarchyState state, ASEnvironmentStatePropagation propagation); -static const struct ASEnvironmentTraitCollection ASEnvironmentDefaultTraitCollection = {}; +static const struct ASEnvironmentTraitCollection ASEnvironmentDefaultTraitCollection = ASEnvironmentTraitCollectionMakeDefault(); ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentTraitCollection state, ASEnvironmentStatePropagation propagation); diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm index 727b073b01..5a3c88b784 100644 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm @@ -207,7 +207,7 @@ ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState childEnvi childTraitCollection.userInterfaceIdiom = parentTraitCollection.userInterfaceIdiom; childTraitCollection.forceTouchCapability = parentTraitCollection.forceTouchCapability; childTraitCollection.displayScale = parentTraitCollection.displayScale; - childTraitCollection.displayContext = parentTraitCollection.displayContext; + childTraitCollection.containerSize = parentTraitCollection.containerSize; childEnvironmentState.environmentTraitCollection = childTraitCollection; } diff --git a/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h b/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h index 64a20d8cc3..6748f1af65 100644 --- a/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h +++ b/AsyncDisplayKit/Private/ASImageNode+AnimatedImagePrivate.h @@ -28,6 +28,13 @@ extern NSString *const ASAnimatedImageDefaultRunLoopMode; NSUInteger _playedLoops; } -@property (atomic, assign) CFTimeInterval lastDisplayLinkFire; +@property (nonatomic, assign) CFTimeInterval lastDisplayLinkFire; + +@end + + +@interface ASImageNode (AnimatedImageInvalidation) + +- (void)invalidateAnimatedImage; @end diff --git a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m b/AsyncDisplayKit/Private/ASImageNode+CGExtras.m index b1912e6b2b..b988617198 100644 --- a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m +++ b/AsyncDisplayKit/Private/ASImageNode+CGExtras.m @@ -20,7 +20,7 @@ static CGSize _ASSizeFillWithAspectRatio(CGFloat sizeToScaleAspectRatio, CGSize if (sizeToScaleAspectRatio > destinationAspectRatio) { return CGSizeMake(destinationSize.height * sizeToScaleAspectRatio, destinationSize.height); } else { - return CGSizeMake(destinationSize.width, floorf(destinationSize.width / sizeToScaleAspectRatio)); + return CGSizeMake(destinationSize.width, roundf(destinationSize.width / sizeToScaleAspectRatio)); } } diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index cff8c1fc53..dac6ef6530 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -8,10 +8,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#include -#import #import -#import "ASBaseDefines.h" +#import + +#import ASDISPLAYNODE_EXTERN_C_BEGIN @@ -35,8 +35,6 @@ CGFloat ASCeilPixelValue(CGFloat f); CGFloat ASRoundPixelValue(CGFloat f); -BOOL ASRunningOnOS7(); - ASDISPLAYNODE_EXTERN_C_END /** diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.m similarity index 85% rename from AsyncDisplayKit/Private/ASInternalHelpers.mm rename to AsyncDisplayKit/Private/ASInternalHelpers.m index 4d4ccbbf74..61a554a7f8 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.m @@ -10,11 +10,10 @@ #import "ASInternalHelpers.h" -#import #import #import "ASThread.h" -#import "ASLayout.h" +#import BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector) { @@ -36,6 +35,9 @@ BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL sele void ASPerformBlockOnMainThread(void (^block)()) { + if (block == nil){ + return; + } if (ASDisplayNodeThreadIsMain()) { block(); } else { @@ -45,6 +47,9 @@ void ASPerformBlockOnMainThread(void (^block)()) void ASPerformBlockOnBackgroundThread(void (^block)()) { + if (block == nil){ + return; + } if (ASDisplayNodeThreadIsMain()) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); } else { @@ -76,27 +81,20 @@ CGFloat ASScreenScale() CGFloat ASFloorPixelValue(CGFloat f) { - return floorf(f * ASScreenScale()) / ASScreenScale(); + CGFloat scale = ASScreenScale(); + return floor(f * scale) / scale; } CGFloat ASCeilPixelValue(CGFloat f) { - return ceilf(f * ASScreenScale()) / ASScreenScale(); + CGFloat scale = ASScreenScale(); + return ceil(f * scale) / scale; } CGFloat ASRoundPixelValue(CGFloat f) { - return roundf(f * ASScreenScale()) / ASScreenScale(); -} - -BOOL ASRunningOnOS7() -{ - static BOOL isOS7 = NO; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - isOS7 = ([[UIDevice currentDevice].systemVersion floatValue] < 8.0); - }); - return isOS7; + CGFloat scale = ASScreenScale(); + return round(f * scale) / scale; } @implementation NSIndexPath (ASInverseComparison) diff --git a/AsyncDisplayKit/Private/ASLayoutTransition.h b/AsyncDisplayKit/Private/ASLayoutTransition.h index c972f41aef..64869dc085 100644 --- a/AsyncDisplayKit/Private/ASLayoutTransition.h +++ b/AsyncDisplayKit/Private/ASLayoutTransition.h @@ -18,16 +18,45 @@ @interface ASLayoutTransition : NSObject <_ASTransitionContextLayoutDelegate> +/** + * Node to apply layout transition on + */ @property (nonatomic, readonly, weak) ASDisplayNode *node; -@property (nonatomic, readonly, strong) ASLayout *pendingLayout; + +/** + * Previous layout to transition from + */ @property (nonatomic, readonly, strong) ASLayout *previousLayout; -- (instancetype)initWithNode:(ASDisplayNode *)node - pendingLayout:(ASLayout *)pendingLayout - previousLayout:(ASLayout *)previousLayout; +/** + * Pending layout to transition to + */ +@property (nonatomic, readonly, strong) ASLayout *pendingLayout; +/** + * Returns if the layout transition needs to happen synchronously + */ +@property (nonatomic, readonly, assign) BOOL isSynchronous; + +/** + * Returns a newly initialized layout transition + */ +- (instancetype)initWithNode:(ASDisplayNode *)node pendingLayout:(ASLayout *)pendingLayout previousLayout:(ASLayout *)previousLayout NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +/** + * Insert and remove subnodes that where added or removed between the previousLayout and the pendingLayout + */ +- (void)commitTransition; + +/** + * Insert all new subnodes that where added between the previous layout and the pending layout + */ - (void)applySubnodeInsertions; +/** + * Remove all subnodes that are removed between the previous layout and the pending layout + */ - (void)applySubnodeRemovals; @end diff --git a/AsyncDisplayKit/Private/ASLayoutTransition.mm b/AsyncDisplayKit/Private/ASLayoutTransition.mm index 560ac6135e..e169b6e142 100644 --- a/AsyncDisplayKit/Private/ASLayoutTransition.mm +++ b/AsyncDisplayKit/Private/ASLayoutTransition.mm @@ -12,18 +12,42 @@ #import "ASLayoutTransition.h" -#import "ASDisplayNode.h" #import "ASDisplayNodeInternal.h" -#import "ASDisplayNode+Subclasses.h" #import "ASLayout.h" -#import +#import #import "NSArray+Diffing.h" #import "ASEqualityHelpers.h" +/** + * Search the whole layout stack if at least one layout has a layoutable object that can not be layed out asynchronous. + * This can be the case for example if a node was already loaded + */ +static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { + // Queue used to keep track of sublayouts while traversing this layout in a BFS fashion. + std::queue queue; + queue.push(layout); + + while (!queue.empty()) { + layout = queue.front(); + queue.pop(); + + if (layout.layoutableObject.canLayoutAsynchronous == NO) { + return NO; + } + + // Add all sublayouts to process in next step + for (ASLayout *sublayout in layout.sublayouts) { + queue.push(sublayout); + } + } + + return YES; +} + @implementation ASLayoutTransition { - ASDN::RecursiveMutex _propertyLock; + ASDN::RecursiveMutex __instanceLock__; BOOL _calculatedSubnodeOperations; NSArray *_insertedSubnodes; NSArray *_removedSubnodes; @@ -44,9 +68,21 @@ return self; } +- (BOOL)isSynchronous +{ + ASDN::MutexLocker l(__instanceLock__); + return !ASLayoutCanTransitionAsynchronous(_pendingLayout); +} + +- (void)commitTransition +{ + [self applySubnodeInsertions]; + [self applySubnodeRemovals]; +} + - (void)applySubnodeInsertions { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; NSUInteger i = 0; @@ -59,7 +95,7 @@ - (void)applySubnodeRemovals { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; for (ASDisplayNode *subnode in _removedSubnodes) { [subnode removeFromSupernode]; @@ -68,7 +104,7 @@ - (void)calculateSubnodeOperationsIfNeeded { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if (_calculatedSubnodeOperations) { return; } @@ -98,27 +134,27 @@ - (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); return _node.subnodes; } - (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; return _insertedSubnodes; } - (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; return _removedSubnodes; } - (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { return _previousLayout; } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { @@ -130,7 +166,7 @@ - (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key { - ASDN::MutexLocker l(_propertyLock); + ASDN::MutexLocker l(__instanceLock__); if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { return _previousLayout.constrainedSizeRange; } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { @@ -163,21 +199,27 @@ static inline void findNodesInLayoutAtIndexesWithFilteredNodes(ASLayout *layout, NSArray * __strong *storedNodes, std::vector *storedPositions) { - NSMutableArray *nodes = [NSMutableArray array]; + NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:indexes.count]; std::vector positions = std::vector(); - NSUInteger idx = [indexes firstIndex]; - while (idx != NSNotFound) { - ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject; - ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts"); - // Ignore the odd case in which a non-node sublayout is accessed and the type cast fails - if (node != nil) { - BOOL notFiltered = (filteredNodes == nil || [filteredNodes indexOfObjectIdenticalTo:node] == NSNotFound); - if (notFiltered) { - [nodes addObject:node]; - positions.push_back(idx); + // From inspection, this is how enumerateObjectsAtIndexes: works under the hood + NSUInteger firstIndex = indexes.firstIndex; + NSUInteger lastIndex = indexes.lastIndex; + NSUInteger idx = 0; + for (ASLayout *sublayout in layout.sublayouts) { + if (idx > lastIndex) { break; } + if (idx >= firstIndex && [indexes containsIndex:idx]) { + ASDisplayNode *node = (ASDisplayNode *)sublayout.layoutableObject; + ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts"); + // Ignore the odd case in which a non-node sublayout is accessed and the type cast fails + if (node != nil) { + BOOL notFiltered = (filteredNodes == nil || [filteredNodes indexOfObjectIdenticalTo:node] == NSNotFound); + if (notFiltered) { + [nodes addObject:node]; + positions.push_back(idx); + } } } - idx = [indexes indexGreaterThanIndex:idx]; + idx += 1; } *storedNodes = nodes; *storedPositions = positions; diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm index 5c7e437387..e8b3671977 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm @@ -11,6 +11,10 @@ #import "ASAssert.h" #import "ASMultidimensionalArrayUtils.h" +// Import UIKit to get [NSIndexPath indexPathForItem:inSection:] which uses +// static memory addresses rather than allocating new index path objects. +#import + #pragma mark - Internal Methods static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, @@ -25,8 +29,10 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray } if (curIndexPath.length < dimension - 1) { - for (int i = 0; i < mutableArray.count; i++) { - ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray[i], indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock); + NSInteger i = 0; + for (NSMutableArray *subarray in mutableArray) { + ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(subarray, indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock); + i += 1; } } else { NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init]; @@ -37,7 +43,9 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray curIdx++; } - updateBlock(mutableArray, indexSet, curIdx); + if (updateBlock){ + updateBlock(mutableArray, indexSet, curIdx); + } } } @@ -72,7 +80,13 @@ static BOOL ASElementExistsAtIndexPathForMultidimensionalArray(NSArray *array, N NSUInteger indexesLength = indexLength - 1; NSUInteger indexes[indexesLength]; [indexPath getIndexes:indexes range:NSMakeRange(1, indexesLength)]; - NSIndexPath *indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength]; + NSIndexPath *indexPathByRemovingFirstIndex; + // Use -indexPathForItem:inSection: if possible because it does not allocate into the heap + if (indexesLength == 2) { + indexPathByRemovingFirstIndex = [NSIndexPath indexPathForItem:indexes[1] inSection:indexes[0]]; + } else { + indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength]; + } return ASElementExistsAtIndexPathForMultidimensionalArray(array[firstIndex], indexPathByRemovingFirstIndex); } @@ -159,8 +173,10 @@ NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutab NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet) { NSMutableArray *res = [NSMutableArray array]; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray[idx], [NSIndexPath indexPathWithIndex:idx], res); + [indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { + ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray[i], [NSIndexPath indexPathWithIndex:i], res); + } }]; return res; @@ -184,9 +200,8 @@ NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalA NSUInteger section = 0; for (NSArray *subarray in twoDimensionalArray) { ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - NSUInteger itemCount = subarray.count; - for (NSUInteger item = 0; item < itemCount; item++) { - [result addObject:[NSIndexPath indexPathWithIndexes:(const NSUInteger []){ section, item } length:2]]; + for (NSUInteger item = 0; item < subarray.count; item++) { + [result addObject:[NSIndexPath indexPathForItem:item inSection:section]]; } section++; } diff --git a/AsyncDisplayKit/Private/ASPendingStateController.mm b/AsyncDisplayKit/Private/ASPendingStateController.mm index 7874924d36..e02e1e5ee0 100644 --- a/AsyncDisplayKit/Private/ASPendingStateController.mm +++ b/AsyncDisplayKit/Private/ASPendingStateController.mm @@ -13,7 +13,6 @@ #import "ASPendingStateController.h" #import "ASThread.h" #import "ASWeakSet.h" -#import "ASAssert.h" #import "ASDisplayNodeInternal.h" @interface ASPendingStateController() diff --git a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm index 19b6167358..4dc3e194f2 100644 --- a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm @@ -11,7 +11,6 @@ #import "ASStackBaselinePositionedLayout.h" #import "ASLayoutSpecUtilities.h" -#import "ASStackLayoutSpecUtilities.h" static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, const ASLayout *layout) { diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 2f13b23d6b..82cc1608be 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -12,9 +12,6 @@ #import "ASInternalHelpers.h" #import "ASLayoutSpecUtilities.h" -#import "ASStackLayoutSpecUtilities.h" -#import "ASLayoutable.h" -#import "ASAssert.h" static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, const ASStackUnpositionedItem &l, diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm index 7df078ae55..e0026fcba9 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm @@ -13,7 +13,6 @@ #import #import "ASLayoutSpecUtilities.h" -#import "ASStackLayoutSpecUtilities.h" /** Sizes the child given the parameters specified, and returns the computed layout. @@ -29,7 +28,9 @@ static ASLayout *crossChildLayout(const id child, // stretched children will have a cross dimension of at least crossMin const CGFloat childCrossMin = alignItems == ASStackLayoutAlignItemsStretch ? crossMin : 0; const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, crossMax); - return [child measureWithSizeRange:childSizeRange]; + ASLayout *layout = [child measureWithSizeRange:childSizeRange]; + ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from measureWithSizeRange: must not be nil: %@", child); + return layout ? : [ASLayout layoutWithLayoutableObject:child constrainedSizeRange:childSizeRange size:CGSizeZero]; } /** diff --git a/AsyncDisplayKit/Private/ASWeakMap.h b/AsyncDisplayKit/Private/ASWeakMap.h new file mode 100644 index 0000000000..ead428040b --- /dev/null +++ b/AsyncDisplayKit/Private/ASWeakMap.h @@ -0,0 +1,59 @@ +// +// ASWeakMap.h +// AsyncDisplayKit +// +// Created by Chris Danford on 7/11/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 + +NS_ASSUME_NONNULL_BEGIN + + +/** + * This class is used in conjunction with ASWeakMap. Instances of this type are returned by an ASWeakMap, + * must retain this value for as long as they want the entry to exist in the map. + */ +@interface ASWeakMapEntry : NSObject + +@property (nonatomic, retain, readonly) Value value; + +@end + + +/** + * This is not a full-featured map. It does not support features like `count` and FastEnumeration because there + * is not currently a need. + * + * This is a map that does not retain keys or values added to it. When both getting and setting, the caller is + * returned a ASWeakMapEntry and must retain it for as long as it wishes the key/value to remain in the map. + * We return a single Entry value to the caller to avoid two potential problems: + * + * 1) It's easier for callers to retain one value (the Entry) and not two (a key and a value). + * 2) Key values are tested for `isEqual` equality. If if a caller asks for a key "A" that is equal to a key "B" + * already in the map, then we need the caller to retain key "B" and not key "A". Returning an Entry simplifies + * the semantics. + * + * The underlying storage is a hash table and the Key type should implement `hash` and `isEqual:`. + */ +@interface ASWeakMap<__covariant Key : NSObject *, Value> : NSObject + +/** + * Read from the cache. The Value object is accessible from the returned ASWeakMapEntry. + */ +- (nullable ASWeakMapEntry *)entryForKey:(Key)key; + +/** + * Put a value into the cache. If an entry with an equal key already exists, then the value is updated on the existing entry. + */ +- (ASWeakMapEntry *)setObject:(Value)value forKey:(Key)key; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/ASWeakMap.m b/AsyncDisplayKit/Private/ASWeakMap.m new file mode 100644 index 0000000000..1c9f6896c9 --- /dev/null +++ b/AsyncDisplayKit/Private/ASWeakMap.m @@ -0,0 +1,85 @@ +// +// ASWeakMap.m +// AsyncDisplayKit +// +// Created by Chris Danford on 7/11/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 "ASWeakMap.h" + +@interface ASWeakMapEntry () +@property (nonatomic, strong) NSObject *key; +@end + +@implementation ASWeakMapEntry + +- (instancetype)initWithKey:(NSObject *)key value:(NSObject *)value +{ + self = [super init]; + if (self) { + _key = key; + _value = value; + } + return self; +} + +- (void)setValue:(NSObject *)value +{ + _value = value; +} + +@end + + +@interface ASWeakMap () +@property (nonatomic, strong) NSMapTable *hashTable; +@end + +/** + * Implementation details: + * + * The retained size of our keys is potentially very large (for example, a UIImage is commonly part of a key). + * Unfortunately, NSMapTable does not make guarantees about how quickly it will dispose of entries where + * either the key or the value is weak and has been disposed. So, a NSMapTable with "strong key to weak value" is + * unsuitable for our purpose because the strong keys are retained longer than the value and for an indefininte period of time. + * More details here: http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/ + * + * Our NSMapTable is "weak key to weak value" where each key maps to an Entry. The Entry object is responsible + * for retaining both the key and value. Our convention is that the caller must retain the Entry object + * in order to keep the key and the value in the cache. + */ +@implementation ASWeakMap + +- (instancetype)init +{ + self = [super init]; + if (self) { + _hashTable = [NSMapTable weakToWeakObjectsMapTable]; + } + return self; +} + +- (ASWeakMapEntry *)entryForKey:(NSObject *)key +{ + return [self.hashTable objectForKey:key]; +} + +- (ASWeakMapEntry *)setObject:(NSObject *)value forKey:(NSObject *)key +{ + ASWeakMapEntry *entry = [self.hashTable objectForKey:key]; + if (entry != nil) { + // Update the value in the existing entry. + entry.value = value; + } else { + entry = [[ASWeakMapEntry alloc] initWithKey:key value:value]; + [self.hashTable setObject:entry forKey:key]; + } + return entry; +} + +@end diff --git a/AsyncDisplayKit/Private/ASWeakSet.h b/AsyncDisplayKit/Private/ASWeakSet.h index e44e68a1a5..9a8707eb84 100644 --- a/AsyncDisplayKit/Private/ASWeakSet.h +++ b/AsyncDisplayKit/Private/ASWeakSet.h @@ -35,10 +35,9 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)allObjects; /** - How many objects are contained in this set. + * How many objects are contained in this set. - NOTE: This method is O(N). Consider using the `empty` - property. + * NOTE: This computed property is O(N). Consider using the `empty` property. */ @property (nonatomic, readonly) NSUInteger count; diff --git a/AsyncDisplayKit/Private/ASWeakSet.m b/AsyncDisplayKit/Private/ASWeakSet.m index 62eccf61fa..c68b730db9 100644 --- a/AsyncDisplayKit/Private/ASWeakSet.m +++ b/AsyncDisplayKit/Private/ASWeakSet.m @@ -11,10 +11,9 @@ // #import "ASWeakSet.h" -#import @interface ASWeakSet<__covariant ObjectType> () -@property (nonatomic, strong, readonly) NSMapTable *mapTable; +@property (nonatomic, strong, readonly) NSHashTable *hashTable; @end @implementation ASWeakSet @@ -23,59 +22,45 @@ { self = [super init]; if (self) { - _mapTable = [NSMapTable weakToStrongObjectsMapTable]; + _hashTable = [NSHashTable weakObjectsHashTable]; } return self; } - (void)addObject:(id)object { - [_mapTable setObject:(NSNull *)kCFNull forKey:object]; + [_hashTable addObject:object]; } - (void)removeObject:(id)object { - [_mapTable removeObjectForKey:object]; + [_hashTable removeObject:object]; } - (void)removeAllObjects { - [_mapTable removeAllObjects]; + [_hashTable removeAllObjects]; } - (NSArray *)allObjects { - // We use keys instead of values in the map table for efficiency and better characteristics when the keys are deallocated. - // Documentation is currently unclear on whether -keyEnumerator retains its values, but does imply that modifying a - // mutable collection is still not safe while enumerating that way - which is one of the main uses for this method. - // A helper function called NSAllMapTableKeys() might do exactly what we want and should be more efficient, but unfortunately - // is throwing a strange compiler error and may not be available in practice on the latest iOS version. - // Lastly, even -dictionaryRepresentation and then -allKeys won't work, because it attempts to copy the values of each key, - // which may not support copying (such as ASRangeControllers). - NSMutableArray *allObjects = [NSMutableArray array]; - for (id object in _mapTable) { - [allObjects addObject:object]; - } - return allObjects; + return _hashTable.allObjects; } - (BOOL)containsObject:(id)object { - return [_mapTable objectForKey:object] != nil; + return [_hashTable containsObject:object]; } - (BOOL)isEmpty { - for (__unused id object in _mapTable) { - return NO; - } - return YES; + return [_hashTable anyObject] == nil; } /** - Note: The `count` property of NSMapTable is unreliable - in the case of weak-to-strong map tables because entries - whose keys have been deallocated are not removed immediately. + Note: The `count` property of NSHashTable is unreliable + in the case of weak-object hash tables because entries + that have been deallocated are not removed immediately. In order to get the true count we have to fall back to using fast enumeration. @@ -83,7 +68,7 @@ - (NSUInteger)count { NSUInteger count = 0; - for (__unused id object in _mapTable) { + for (__unused id object in _hashTable) { count += 1; } return count; @@ -91,12 +76,12 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id _Nonnull *)buffer count:(NSUInteger)len { - return [_mapTable countByEnumeratingWithState:state objects:buffer count:len]; + return [_hashTable countByEnumeratingWithState:state objects:buffer count:len]; } - (NSString *)description { - return [[super description] stringByAppendingFormat:@" count: %lu, contents: %@", (unsigned long)self.count, _mapTable]; + return [[super description] stringByAppendingFormat:@" count: %tu, contents: %@", self.count, _hashTable]; } @end diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h index 6ec3b4ca75..af5839508c 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h @@ -11,16 +11,53 @@ // #import -#import +#import + +NS_ASSUME_NONNULL_BEGIN typedef NSUInteger ASDataControllerAnimationOptions; typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) { + /** + * A reload change, as submitted by the user. When a change set is + * completed, these changes are decomposed into delete-insert pairs + * and combined with the original deletes and inserts of the change. + */ _ASHierarchyChangeTypeReload, + + /** + * A change that was either an original delete, or the first + * part of a decomposed reload. + */ _ASHierarchyChangeTypeDelete, - _ASHierarchyChangeTypeInsert + + /** + * A change that was submitted by the user as a delete. + */ + _ASHierarchyChangeTypeOriginalDelete, + + /** + * A change that was either an original insert, or the second + * part of a decomposed reload. + */ + _ASHierarchyChangeTypeInsert, + + /** + * A change that was submitted by the user as an insert. + */ + _ASHierarchyChangeTypeOriginalInsert }; +/** + * Returns YES if the given change type is either .Insert or .Delete, NO otherwise. + * Other change types – .Reload, .OriginalInsert, .OriginalDelete – are + * intermediary types used while building the change set. All changes will + * be reduced to either .Insert or .Delete when the change is marked completed. + */ +BOOL ASHierarchyChangeTypeIsFinal(_ASHierarchyChangeType changeType); + +NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); + @interface _ASHierarchySectionChange : NSObject // FIXME: Generalize this to `changeMetadata` dict? @@ -28,27 +65,39 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) { @property (nonatomic, strong, readonly) NSIndexSet *indexSet; @property (nonatomic, readonly) _ASHierarchyChangeType changeType; + +/** + * If this is a .OriginalInsert or .OriginalDelete change, this returns a copied change + * with type .Insert or .Delete. Calling this on changes of other types is an error. + */ +- (_ASHierarchySectionChange *)changeByFinalizingType; @end @interface _ASHierarchyItemChange : NSObject @property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; /// Index paths are sorted descending for changeType .Delete, ascending otherwise -@property (nonatomic, strong, readonly) NSArray *indexPaths; +@property (nonatomic, strong, readonly) NSArray *indexPaths; @property (nonatomic, readonly) _ASHierarchyChangeType changeType; -+ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray *)changes ofType:(_ASHierarchyChangeType)changeType; ++ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofType:(_ASHierarchyChangeType)changeType; + +/** + * If this is a .OriginalInsert or .OriginalDelete change, this returns a copied change + * with type .Insert or .Delete. Calling this on changes of other types is an error. + */ +- (_ASHierarchyItemChange *)changeByFinalizingType; @end @interface _ASHierarchyChangeSet : NSObject +- (instancetype)initWithOldData:(std::vector)oldItemCounts NS_DESIGNATED_INITIALIZER; + /// @precondition The change set must be completed. @property (nonatomic, strong, readonly) NSIndexSet *deletedSections; /// @precondition The change set must be completed. @property (nonatomic, strong, readonly) NSIndexSet *insertedSections; -/// @precondition The change set must be completed. -@property (nonatomic, strong, readonly) NSIndexSet *reloadedSections; /** Get the section index after the update for the given section before the update. @@ -56,34 +105,26 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) { @precondition The change set must be completed. @returns The new section index, or NSNotFound if the given section was deleted. */ -- (NSInteger)newSectionForOldSection:(NSInteger)oldSection; +- (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection; @property (nonatomic, readonly) BOOL completed; /// Call this once the change set has been constructed to prevent future modifications to the changeset. Calling this more than once is a programmer error. -- (void)markCompleted; +/// NOTE: Calling this method will cause the changeset to convert all reloads into delete/insert pairs. +- (void)markCompletedWithNewItemCounts:(std::vector)newItemCounts; -/** - @abstract Return sorted changes of the given type, grouped by animation options. - - Items deleted from deleted sections are not reported. - Items inserted into inserted sections are not reported. - Items reloaded in reloaded sections are not reported. - - The safe order for processing change groups is: - - Reloaded sections & reloaded items - - Deleted items, descending order - - Deleted sections, descending order - - Inserted sections, ascending order - - Inserted items, ascending order - */ -- (NSArray /*<_ASHierarchySectionChange *>*/ *)sectionChangesOfType:(_ASHierarchyChangeType)changeType; -- (NSArray /*<_ASHierarchyItemChange *>*/ *)itemChangesOfType:(_ASHierarchyChangeType)changeType; +- (nullable NSArray <_ASHierarchySectionChange *> *)sectionChangesOfType:(_ASHierarchyChangeType)changeType; +- (nullable NSArray <_ASHierarchyItemChange *> *)itemChangesOfType:(_ASHierarchyChangeType)changeType; + +/// Returns all item indexes affected by changes of the given type in the given section. +- (NSIndexSet *)indexesForItemChangesOfType:(_ASHierarchyChangeType)changeType inSection:(NSUInteger)section; - (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options; - (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options; - (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options; -- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; -- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; -- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; +- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; +- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; +- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; @end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m deleted file mode 100644 index c89fc99332..0000000000 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m +++ /dev/null @@ -1,462 +0,0 @@ -// -// _ASHierarchyChangeSet.m -// AsyncDisplayKit -// -// Created by Adlai Holler on 9/29/15. -// -// 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 "_ASHierarchyChangeSet.h" -#import "ASInternalHelpers.h" - -@interface _ASHierarchySectionChange () -- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions; - -/** - On return `changes` is sorted according to the change type with changes coalesced by animationOptions - Assumes: `changes` is [_ASHierarchySectionChange] all with the same changeType - */ -+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes; - -/// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects. -+ (NSMutableIndexSet *)allIndexesInChanges:(NSArray *)changes; -@end - -@interface _ASHierarchyItemChange () -- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted; - -/** - On return `changes` is sorted according to the change type with changes coalesced by animationOptions - Assumes: `changes` is [_ASHierarchyItemChange] all with the same changeType - */ -+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections; -@end - -@interface _ASHierarchyChangeSet () - -@property (nonatomic, strong, readonly) NSMutableArray *insertItemChanges; -@property (nonatomic, strong, readonly) NSMutableArray *deleteItemChanges; -@property (nonatomic, strong, readonly) NSMutableArray *reloadItemChanges; -@property (nonatomic, strong, readonly) NSMutableArray *insertSectionChanges; -@property (nonatomic, strong, readonly) NSMutableArray *deleteSectionChanges; -@property (nonatomic, strong, readonly) NSMutableArray *reloadSectionChanges; - -@end - -@implementation _ASHierarchyChangeSet - -- (instancetype)init -{ - self = [super init]; - if (self) { - - _insertItemChanges = [NSMutableArray new]; - _deleteItemChanges = [NSMutableArray new]; - _reloadItemChanges = [NSMutableArray new]; - _insertSectionChanges = [NSMutableArray new]; - _deleteSectionChanges = [NSMutableArray new]; - _reloadSectionChanges = [NSMutableArray new]; - } - return self; -} - -#pragma mark External API - -- (void)markCompleted -{ - NSAssert(!_completed, @"Attempt to mark already-completed changeset as completed."); - _completed = YES; - [self _sortAndCoalesceChangeArrays]; -} - -- (NSArray *)sectionChangesOfType:(_ASHierarchyChangeType)changeType -{ - [self _ensureCompleted]; - switch (changeType) { - case _ASHierarchyChangeTypeInsert: - return _insertSectionChanges; - case _ASHierarchyChangeTypeReload: - return _reloadSectionChanges; - case _ASHierarchyChangeTypeDelete: - return _deleteSectionChanges; - default: - NSAssert(NO, @"Request for section changes with invalid type: %lu", (long)changeType); - } -} - -- (NSArray *)itemChangesOfType:(_ASHierarchyChangeType)changeType -{ - [self _ensureCompleted]; - switch (changeType) { - case _ASHierarchyChangeTypeInsert: - return _insertItemChanges; - case _ASHierarchyChangeTypeReload: - return _reloadItemChanges; - case _ASHierarchyChangeTypeDelete: - return _deleteItemChanges; - default: - NSAssert(NO, @"Request for item changes with invalid type: %lu", (long)changeType); - } -} - -- (NSInteger)newSectionForOldSection:(NSInteger)oldSection -{ - [self _ensureCompleted]; - if ([_deletedSections containsIndex:oldSection]) { - return NSNotFound; - } - - __block NSInteger newIndex = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)]; - [_insertedSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - if (idx <= newIndex) { - newIndex += 1; - } else { - *stop = YES; - } - }]; - return newIndex; -} - -- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:indexPaths animationOptions:options presorted:NO]; - [_deleteItemChanges addObject:change]; -} - -- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexSet:sections animationOptions:options]; - [_deleteSectionChanges addObject:change]; -} - -- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:indexPaths animationOptions:options presorted:NO]; - [_insertItemChanges addObject:change]; -} - -- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexSet:sections animationOptions:options]; - [_insertSectionChanges addObject:change]; -} - -- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexPaths:indexPaths animationOptions:options presorted:NO]; - [_reloadItemChanges addObject:change]; -} - -- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options -{ - [self _ensureNotCompleted]; - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexSet:sections animationOptions:options]; - [_reloadSectionChanges addObject:change]; -} - -#pragma mark Private - -- (BOOL)_ensureNotCompleted -{ - NSAssert(!_completed, @"Attempt to modify completed changeset %@", self); - return !_completed; -} - -- (BOOL)_ensureCompleted -{ - NSAssert(_completed, @"Attempt to process incomplete changeset %@", self); - return _completed; -} - -- (void)_sortAndCoalesceChangeArrays -{ - @autoreleasepool { - [_ASHierarchySectionChange sortAndCoalesceChanges:_deleteSectionChanges]; - [_ASHierarchySectionChange sortAndCoalesceChanges:_insertSectionChanges]; - [_ASHierarchySectionChange sortAndCoalesceChanges:_reloadSectionChanges]; - - _deletedSections = [[_ASHierarchySectionChange allIndexesInChanges:_deleteSectionChanges] copy]; - _insertedSections = [[_ASHierarchySectionChange allIndexesInChanges:_insertSectionChanges] copy]; - _reloadedSections = [[_ASHierarchySectionChange allIndexesInChanges:_reloadSectionChanges] copy]; - - // These are invalid old section indexes. - NSMutableIndexSet *deletedOrReloaded = [_deletedSections mutableCopy]; - [deletedOrReloaded addIndexes:_reloadedSections]; - - // These are invalid new section indexes. - NSMutableIndexSet *insertedOrReloaded = [_insertedSections mutableCopy]; - - // Get the new section that each reloaded section index corresponds to. - // Coalesce reload sections' indexes into deletes and inserts - [_reloadedSections enumerateIndexesUsingBlock:^(NSUInteger oldIndex, __unused BOOL * stop) { - NSUInteger newIndex = [self newSectionForOldSection:oldIndex]; - if (newIndex != NSNotFound) { - [insertedOrReloaded addIndex:newIndex]; - } - [deletedOrReloaded addIndex:oldIndex]; - }]; - - _deletedSections = deletedOrReloaded; - _insertedSections = insertedOrReloaded; - _reloadedSections = nil; - - // reload items changes need to be adjusted so that we access the correct indexPaths in the datasource - NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges ofType:_ASHierarchyChangeTypeInsert]; - NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges ofType:_ASHierarchyChangeTypeDelete]; - - for (_ASHierarchyItemChange *change in _reloadItemChanges) { - NSAssert(change.changeType == _ASHierarchyChangeTypeReload, @"It must be a reload change to be in here"); - NSMutableArray *newIndexPaths = [NSMutableArray array]; - - // Every indexPaths in the change need to update its section and/or row - // depending on all the deletions and insertions - // For reference, when batching reloads/deletes/inserts: - // - delete/reload indexPaths that are passed in should all be their current indexPaths - // - insert indexPaths that are passed in should all be their future indexPaths after deletions - for (NSIndexPath *indexPath in change.indexPaths) { - __block NSUInteger section = indexPath.section; - __block NSUInteger row = indexPath.row; - - - // Update section number based on section insertions/deletions that are above the current section - section -= [_deletedSections countOfIndexesInRange:NSMakeRange(0, section)]; - [_insertedSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - if (idx <= section) { - section += 1; - } else { - *stop = YES; - } - }]; - - // Update row number based on deletions that are above the current row in the current section - NSIndexSet *indicesDeletedInSection = deletedIndexPathsMap[@(indexPath.section)]; - row -= [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, row)]; - // Update row number based on insertions that are above the current row in the future section - NSIndexSet *indicesInsertedInSection = insertedIndexPathsMap[@(section)]; - [indicesInsertedInSection enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - if (idx <= row) { - row += 1; - } else { - *stop = YES; - } - }]; - - //TODO: reuse the old indexPath object if section and row aren't changed - NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:row inSection:section]; - [newIndexPaths addObject:newIndexPath]; - } - - // All reload changes are coalesced into deletes and inserts - // We delete the items that needs reload together with other deleted items, at their original index - _ASHierarchyItemChange *deleteItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:change.indexPaths animationOptions:change.animationOptions presorted:NO]; - [_deleteItemChanges addObject:deleteItemChangeFromReloadChange]; - // We insert the items that needs reload together with other inserted items, at their future index - _ASHierarchyItemChange *insertItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:newIndexPaths animationOptions:change.animationOptions presorted:NO]; - [_insertItemChanges addObject:insertItemChangeFromReloadChange]; - } - [_reloadItemChanges removeAllObjects]; - - // Ignore item deletes in reloaded/deleted sections. - [_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:deletedOrReloaded]; - - // Ignore item inserts in reloaded(new)/inserted sections. - [_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:insertedOrReloaded]; - } -} - - - -@end - -@implementation _ASHierarchySectionChange - -- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - self = [super init]; - if (self) { - _changeType = changeType; - _indexSet = indexSet; - _animationOptions = animationOptions; - } - return self; -} - -+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes -{ - if (changes.count < 1) { - return; - } - - _ASHierarchyChangeType type = [changes.firstObject changeType]; - - // Lookup table [Int: AnimationOptions] - NSMutableDictionary *animationOptions = [NSMutableDictionary new]; - - // All changed indexes, sorted - NSMutableIndexSet *allIndexes = [NSMutableIndexSet new]; - - for (_ASHierarchySectionChange *change in changes) { - [change.indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, __unused BOOL *stop) { - animationOptions[@(idx)] = @(change.animationOptions); - }]; - [allIndexes addIndexes:change.indexSet]; - } - - // Create new changes by grouping sorted changes by animation option - NSMutableArray *result = [NSMutableArray new]; - - __block ASDataControllerAnimationOptions currentOptions = 0; - NSMutableIndexSet *currentIndexes = [NSMutableIndexSet indexSet]; - - NSEnumerationOptions options = type == _ASHierarchyChangeTypeDelete ? NSEnumerationReverse : kNilOptions; - - [allIndexes enumerateIndexesWithOptions:options usingBlock:^(NSUInteger idx, __unused BOOL * stop) { - ASDataControllerAnimationOptions options = [animationOptions[@(idx)] integerValue]; - - // End the previous group if needed. - if (options != currentOptions && currentIndexes.count > 0) { - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions]; - [result addObject:change]; - [currentIndexes removeAllIndexes]; - } - - // Start a new group if needed. - if (currentIndexes.count == 0) { - currentOptions = options; - } - - [currentIndexes addIndex:idx]; - }]; - - // Finish up the last group. - if (currentIndexes.count > 0) { - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions]; - [result addObject:change]; - } - - [changes setArray:result]; -} - -+ (NSMutableIndexSet *)allIndexesInChanges:(NSArray *)changes -{ - NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; - for (_ASHierarchySectionChange *change in changes) { - [indexes addIndexes:change.indexSet]; - } - return indexes; -} - -@end - -@implementation _ASHierarchyItemChange - -- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted -{ - self = [super init]; - if (self) { - _changeType = changeType; - if (presorted) { - _indexPaths = indexPaths; - } else { - SEL sorting = changeType == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:); - _indexPaths = [indexPaths sortedArrayUsingSelector:sorting]; - } - _animationOptions = animationOptions; - } - return self; -} - -// Create a mapping out of changes indexPaths to a {@section : [indexSet]} fashion -// e.g. changes: (0 - 0), (0 - 1), (2 - 5) -// will become: {@0 : [0, 1], @2 : [5]} -+ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray *)changes ofType:(_ASHierarchyChangeType)changeType -{ - NSMutableDictionary *sectionToIndexSetMap = [NSMutableDictionary dictionary]; - for (_ASHierarchyItemChange *change in changes) { - NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now"); - for (NSIndexPath *indexPath in change.indexPaths) { - NSNumber *sectionKey = @(indexPath.section); - NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey]; - if (indexSet) { - [indexSet addIndex:indexPath.row]; - } else { - indexSet = [NSMutableIndexSet indexSetWithIndex:indexPath.row]; - sectionToIndexSetMap[sectionKey] = indexSet; - } - } - } - return sectionToIndexSetMap; -} - -+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections -{ - if (changes.count < 1) { - return; - } - - _ASHierarchyChangeType type = [changes.firstObject changeType]; - - // Lookup table [NSIndexPath: AnimationOptions] - NSMutableDictionary *animationOptions = [NSMutableDictionary new]; - - // All changed index paths, sorted - NSMutableArray *allIndexPaths = [NSMutableArray new]; - - NSPredicate *indexPathInValidSection = [NSPredicate predicateWithBlock:^BOOL(NSIndexPath *indexPath, __unused NSDictionary *_) { - return ![sections containsIndex:indexPath.section]; - }]; - for (_ASHierarchyItemChange *change in changes) { - for (NSIndexPath *indexPath in change.indexPaths) { - if ([indexPathInValidSection evaluateWithObject:indexPath]) { - animationOptions[indexPath] = @(change.animationOptions); - [allIndexPaths addObject:indexPath]; - } - } - } - - SEL sorting = type == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:); - [allIndexPaths sortUsingSelector:sorting]; - - // Create new changes by grouping sorted changes by animation option - NSMutableArray *result = [NSMutableArray new]; - - ASDataControllerAnimationOptions currentOptions = 0; - NSMutableArray *currentIndexPaths = [NSMutableArray array]; - - for (NSIndexPath *indexPath in allIndexPaths) { - ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue]; - - // End the previous group if needed. - if (options != currentOptions && currentIndexPaths.count > 0) { - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES]; - [result addObject:change]; - [currentIndexPaths removeAllObjects]; - } - - // Start a new group if needed. - if (currentIndexPaths.count == 0) { - currentOptions = options; - } - - [currentIndexPaths addObject:indexPath]; - } - - // Finish up the last group. - if (currentIndexPaths.count > 0) { - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES]; - [result addObject:change]; - } - - [changes setArray:result]; -} - -@end diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm new file mode 100644 index 0000000000..2b080cece7 --- /dev/null +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm @@ -0,0 +1,706 @@ +// +// _ASHierarchyChangeSet.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 9/29/15. +// +// 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 "_ASHierarchyChangeSet.h" +#import "ASInternalHelpers.h" +#import "NSIndexSet+ASHelpers.h" +#import "ASAssert.h" +#import "ASDisplayNode+Beta.h" +#import + +#define ASFailUpdateValidation(...)\ + if ([ASDisplayNode suppressesInvalidCollectionUpdateExceptions]) {\ + NSLog(__VA_ARGS__);\ + } else {\ + ASDisplayNodeFailAssert(__VA_ARGS__);\ + } + +BOOL ASHierarchyChangeTypeIsFinal(_ASHierarchyChangeType changeType) { + switch (changeType) { + case _ASHierarchyChangeTypeInsert: + case _ASHierarchyChangeTypeDelete: + return YES; + default: + return NO; + } +} + +NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) +{ + switch (changeType) { + case _ASHierarchyChangeTypeInsert: + return @"Insert"; + case _ASHierarchyChangeTypeOriginalInsert: + return @"OriginalInsert"; + case _ASHierarchyChangeTypeDelete: + return @"Delete"; + case _ASHierarchyChangeTypeOriginalDelete: + return @"OriginalDelete"; + case _ASHierarchyChangeTypeReload: + return @"Reload"; + default: + return @"(invalid)"; + } +} + +@interface _ASHierarchySectionChange () +- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + On return `changes` is sorted according to the change type with changes coalesced by animationOptions + Assumes: `changes` all have the same changeType + */ ++ (void)sortAndCoalesceSectionChanges:(NSMutableArray<_ASHierarchySectionChange *> *)changes; + +/// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects. ++ (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray *)changes; +@end + +@interface _ASHierarchyItemChange () +- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted; + +/** + On return `changes` is sorted according to the change type with changes coalesced by animationOptions + Assumes: `changes` all have the same changeType + */ ++ (void)sortAndCoalesceItemChanges:(NSMutableArray<_ASHierarchyItemChange *> *)changes ignoringChangesInSections:(NSIndexSet *)sections; +@end + +@interface _ASHierarchyChangeSet () + +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *insertItemChanges; +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *originalInsertItemChanges; + +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *deleteItemChanges; +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *originalDeleteItemChanges; + +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *reloadItemChanges; + +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *insertSectionChanges; +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *originalInsertSectionChanges; + +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *deleteSectionChanges; +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *originalDeleteSectionChanges; + +@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *reloadSectionChanges; + +@end + +@implementation _ASHierarchyChangeSet { + std::vector _oldItemCounts; + std::vector _newItemCounts; +} + +- (instancetype)init +{ + ASFailUpdateValidation(@"_ASHierarchyChangeSet: -init is not supported. Call -initWithOldData:"); + return [self initWithOldData:std::vector()]; +} + +- (instancetype)initWithOldData:(std::vector)oldItemCounts +{ + self = [super init]; + if (self) { + _oldItemCounts = oldItemCounts; + + _originalInsertItemChanges = [[NSMutableArray alloc] init]; + _insertItemChanges = [[NSMutableArray alloc] init]; + _originalDeleteItemChanges = [[NSMutableArray alloc] init]; + _deleteItemChanges = [[NSMutableArray alloc] init]; + _reloadItemChanges = [[NSMutableArray alloc] init]; + + _originalInsertSectionChanges = [[NSMutableArray alloc] init]; + _insertSectionChanges = [[NSMutableArray alloc] init]; + _originalDeleteSectionChanges = [[NSMutableArray alloc] init]; + _deleteSectionChanges = [[NSMutableArray alloc] init]; + _reloadSectionChanges = [[NSMutableArray alloc] init]; + } + return self; +} + +#pragma mark External API + +- (void)markCompletedWithNewItemCounts:(std::vector)newItemCounts +{ + NSAssert(!_completed, @"Attempt to mark already-completed changeset as completed."); + _completed = YES; + _newItemCounts = newItemCounts; + [self _sortAndCoalesceChangeArrays]; + [self _validateUpdate]; +} + +- (NSArray *)sectionChangesOfType:(_ASHierarchyChangeType)changeType +{ + [self _ensureCompleted]; + switch (changeType) { + case _ASHierarchyChangeTypeInsert: + return _insertSectionChanges; + case _ASHierarchyChangeTypeReload: + return _reloadSectionChanges; + case _ASHierarchyChangeTypeDelete: + return _deleteSectionChanges; + case _ASHierarchyChangeTypeOriginalDelete: + return _originalDeleteSectionChanges; + case _ASHierarchyChangeTypeOriginalInsert: + return _originalInsertSectionChanges; + default: + NSAssert(NO, @"Request for section changes with invalid type: %lu", (long)changeType); + return nil; + } +} + +- (NSArray *)itemChangesOfType:(_ASHierarchyChangeType)changeType +{ + [self _ensureCompleted]; + switch (changeType) { + case _ASHierarchyChangeTypeInsert: + return _insertItemChanges; + case _ASHierarchyChangeTypeReload: + return _reloadItemChanges; + case _ASHierarchyChangeTypeDelete: + return _deleteItemChanges; + case _ASHierarchyChangeTypeOriginalInsert: + return _originalInsertItemChanges; + case _ASHierarchyChangeTypeOriginalDelete: + return _originalDeleteItemChanges; + default: + NSAssert(NO, @"Request for item changes with invalid type: %lu", (long)changeType); + return nil; + } +} + +- (NSIndexSet *)indexesForItemChangesOfType:(_ASHierarchyChangeType)changeType inSection:(NSUInteger)section +{ + [self _ensureCompleted]; + NSMutableIndexSet *result = [NSMutableIndexSet indexSet]; + for (_ASHierarchyItemChange *change in [self itemChangesOfType:changeType]) { + [result addIndexes:[NSIndexSet as_indexSetFromIndexPaths:change.indexPaths inSection:section]]; + } + return result; +} + +- (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection +{ + ASDisplayNodeAssertNotNil(_deletedSections, @"Cannot call %@ before `markCompleted` returns.", NSStringFromSelector(_cmd)); + ASDisplayNodeAssertNotNil(_insertedSections, @"Cannot call %@ before `markCompleted` returns.", NSStringFromSelector(_cmd)); + [self _ensureCompleted]; + if ([_deletedSections containsIndex:oldSection]) { + return NSNotFound; + } + + NSUInteger newIndex = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)]; + newIndex += [_insertedSections as_indexChangeByInsertingItemsBelowIndex:newIndex]; + return newIndex; +} + +- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options +{ + [self _ensureNotCompleted]; + _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalDelete indexPaths:indexPaths animationOptions:options presorted:NO]; + [_originalDeleteItemChanges addObject:change]; +} + +- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options +{ + [self _ensureNotCompleted]; + _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalDelete indexSet:sections animationOptions:options]; + [_originalDeleteSectionChanges addObject:change]; +} + +- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options +{ + [self _ensureNotCompleted]; + _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalInsert indexPaths:indexPaths animationOptions:options presorted:NO]; + [_originalInsertItemChanges addObject:change]; +} + +- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options +{ + [self _ensureNotCompleted]; + _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalInsert indexSet:sections animationOptions:options]; + [_originalInsertSectionChanges addObject:change]; +} + +- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options +{ + [self _ensureNotCompleted]; + _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexPaths:indexPaths animationOptions:options presorted:NO]; + [_reloadItemChanges addObject:change]; +} + +- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options +{ + [self _ensureNotCompleted]; + _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexSet:sections animationOptions:options]; + [_reloadSectionChanges addObject:change]; +} + +#pragma mark Private + +- (BOOL)_ensureNotCompleted +{ + NSAssert(!_completed, @"Attempt to modify completed changeset %@", self); + return !_completed; +} + +- (BOOL)_ensureCompleted +{ + NSAssert(_completed, @"Attempt to process incomplete changeset %@", self); + return _completed; +} + +- (void)_sortAndCoalesceChangeArrays +{ + @autoreleasepool { + + // Split reloaded sections into [delete(oldIndex), insert(newIndex)] + + // Give these their "pre-reloads" values. Once we add in the reloads we'll re-process them. + _deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_originalDeleteSectionChanges]; + _insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_originalInsertSectionChanges]; + for (_ASHierarchySectionChange *originalDeleteSectionChange in _originalDeleteSectionChanges) { + [_deleteSectionChanges addObject:[originalDeleteSectionChange changeByFinalizingType]]; + } + for (_ASHierarchySectionChange *originalInsertSectionChange in _originalInsertSectionChanges) { + [_insertSectionChanges addObject:[originalInsertSectionChange changeByFinalizingType]]; + } + + for (_ASHierarchySectionChange *change in _reloadSectionChanges) { + NSIndexSet *newSections = [change.indexSet as_indexesByMapping:^(NSUInteger idx) { + NSUInteger newSec = [self newSectionForOldSection:idx]; + ASDisplayNodeAssert(newSec != NSNotFound, @"Request to reload and delete same section %tu", idx); + return newSec; + }]; + + _ASHierarchySectionChange *deleteChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexSet:change.indexSet animationOptions:change.animationOptions]; + [_deleteSectionChanges addObject:deleteChange]; + + _ASHierarchySectionChange *insertChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexSet:newSections animationOptions:change.animationOptions]; + [_insertSectionChanges addObject:insertChange]; + } + + [_ASHierarchySectionChange sortAndCoalesceSectionChanges:_deleteSectionChanges]; + [_ASHierarchySectionChange sortAndCoalesceSectionChanges:_insertSectionChanges]; + _deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_deleteSectionChanges]; + _insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_insertSectionChanges]; + + // Split reloaded items into [delete(oldIndexPath), insert(newIndexPath)] + for (_ASHierarchyItemChange *originalDeleteItemChange in _originalDeleteItemChanges) { + [_deleteItemChanges addObject:[originalDeleteItemChange changeByFinalizingType]]; + } + for (_ASHierarchyItemChange *originalInsertItemChange in _originalInsertItemChanges) { + [_insertItemChanges addObject:[originalInsertItemChange changeByFinalizingType]]; + } + + NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges ofType:_ASHierarchyChangeTypeInsert]; + NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges ofType:_ASHierarchyChangeTypeDelete]; + + for (_ASHierarchyItemChange *change in _reloadItemChanges) { + NSAssert(change.changeType == _ASHierarchyChangeTypeReload, @"It must be a reload change to be in here"); + NSMutableArray *newIndexPaths = [NSMutableArray arrayWithCapacity:change.indexPaths.count]; + + // Every indexPaths in the change need to update its section and/or row + // depending on all the deletions and insertions + // For reference, when batching reloads/deletes/inserts: + // - delete/reload indexPaths that are passed in should all be their current indexPaths + // - insert indexPaths that are passed in should all be their future indexPaths after deletions + for (NSIndexPath *indexPath in change.indexPaths) { + NSUInteger section = [self newSectionForOldSection:indexPath.section]; + NSUInteger item = indexPath.item; + + // Update row number based on deletions that are above the current row in the current section + NSIndexSet *indicesDeletedInSection = deletedIndexPathsMap[@(indexPath.section)]; + item -= [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, item)]; + // Update row number based on insertions that are above the current row in the future section + NSIndexSet *indicesInsertedInSection = insertedIndexPathsMap[@(section)]; + item += [indicesInsertedInSection as_indexChangeByInsertingItemsBelowIndex:item]; + + NSIndexPath *newIndexPath = [NSIndexPath indexPathForItem:item inSection:section]; + [newIndexPaths addObject:newIndexPath]; + } + + // All reload changes are translated into deletes and inserts + // We delete the items that needs reload together with other deleted items, at their original index + _ASHierarchyItemChange *deleteItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:change.indexPaths animationOptions:change.animationOptions presorted:NO]; + [_deleteItemChanges addObject:deleteItemChangeFromReloadChange]; + // We insert the items that needs reload together with other inserted items, at their future index + _ASHierarchyItemChange *insertItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:newIndexPaths animationOptions:change.animationOptions presorted:NO]; + [_insertItemChanges addObject:insertItemChangeFromReloadChange]; + } + + // Ignore item deletes in reloaded/deleted sections. + [_ASHierarchyItemChange sortAndCoalesceItemChanges:_deleteItemChanges ignoringChangesInSections:_deletedSections]; + + // Ignore item inserts in reloaded(new)/inserted sections. + [_ASHierarchyItemChange sortAndCoalesceItemChanges:_insertItemChanges ignoringChangesInSections:_insertedSections]; + } +} + +- (void)_validateUpdate +{ + NSIndexSet *allReloadedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_reloadSectionChanges]; + + NSInteger newSectionCount = _newItemCounts.size(); + NSInteger oldSectionCount = _oldItemCounts.size(); + + NSInteger insertedSectionCount = _insertedSections.count; + NSInteger deletedSectionCount = _deletedSections.count; + // Assert that the new section count is correct. + if (newSectionCount != oldSectionCount + insertedSectionCount - deletedSectionCount) { + ASFailUpdateValidation(@"Invalid number of sections. The number of sections after the update (%zd) must be equal to the number of sections before the update (%zd) plus or minus the number of sections inserted or deleted (%tu inserted, %tu deleted)", newSectionCount, oldSectionCount, insertedSectionCount, deletedSectionCount); + return; + } + + // Assert that no invalid deletes/reloads happened. + NSInteger invalidSectionDelete = NSNotFound; + if (oldSectionCount == 0) { + invalidSectionDelete = _deletedSections.firstIndex; + } else { + invalidSectionDelete = [_deletedSections indexGreaterThanIndex:oldSectionCount - 1]; + } + if (invalidSectionDelete != NSNotFound) { + ASFailUpdateValidation(@"Attempt to delete section %zd but there are only %zd sections before the update.", invalidSectionDelete, oldSectionCount); + return; + } + + for (_ASHierarchyItemChange *change in _deleteItemChanges) { + for (NSIndexPath *indexPath in change.indexPaths) { + // Assert that item delete happened in a valid section. + NSInteger section = indexPath.section; + NSInteger item = indexPath.item; + if (section >= oldSectionCount) { + ASFailUpdateValidation(@"Attempt to delete item %zd from section %zd, but there are only %zd sections before the update.", item, section, oldSectionCount); + return; + } + + // Assert that item delete happened to a valid item. + NSInteger oldItemCount = _oldItemCounts[section]; + if (item >= oldItemCount) { + ASFailUpdateValidation(@"Attempt to delete item %zd from section %zd, which only contains %zd items before the update.", item, section, oldItemCount); + return; + } + } + } + + for (_ASHierarchyItemChange *change in _insertItemChanges) { + for (NSIndexPath *indexPath in change.indexPaths) { + NSInteger section = indexPath.section; + NSInteger item = indexPath.item; + // Assert that item insert happened in a valid section. + if (section >= newSectionCount) { + ASFailUpdateValidation(@"Attempt to insert item %zd into section %zd, but there are only %zd sections after the update.", item, section, newSectionCount); + return; + } + + // Assert that item delete happened to a valid item. + NSInteger newItemCount = _newItemCounts[section]; + if (item >= newItemCount) { + ASFailUpdateValidation(@"Attempt to insert item %zd into section %zd, which only contains %zd items after the update.", item, section, newItemCount); + return; + } + } + } + + // Assert that no sections were inserted out of bounds. + NSInteger invalidSectionInsert = NSNotFound; + if (newSectionCount == 0) { + invalidSectionInsert = _insertedSections.firstIndex; + } else { + invalidSectionInsert = [_insertedSections indexGreaterThanIndex:newSectionCount - 1]; + } + if (invalidSectionInsert != NSNotFound) { + ASFailUpdateValidation(@"Attempt to insert section %zd but there are only %zd sections after the update.", invalidSectionInsert, newSectionCount); + return; + } + + for (NSUInteger oldSection = 0; oldSection < oldSectionCount; oldSection++) { + NSInteger oldItemCount = _oldItemCounts[oldSection]; + // If section was reloaded, ignore. + if ([allReloadedSections containsIndex:oldSection]) { + continue; + } + + // If section was deleted, ignore. + NSUInteger newSection = [self newSectionForOldSection:oldSection]; + if (newSection == NSNotFound) { + continue; + } + + NSIndexSet *originalInsertedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeOriginalInsert inSection:newSection]; + NSIndexSet *originalDeletedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeOriginalDelete inSection:oldSection]; + NSIndexSet *reloadedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeReload inSection:oldSection]; + + // Assert that no reloaded items were deleted. + NSInteger deletedReloadedItem = [originalDeletedItems as_intersectionWithIndexes:reloadedItems].firstIndex; + if (deletedReloadedItem != NSNotFound) { + ASFailUpdateValidation(@"Attempt to delete and reload the same item at index path %@", [NSIndexPath indexPathForItem:deletedReloadedItem inSection:oldSection]); + return; + } + + // Assert that the new item count is correct. + NSInteger newItemCount = _newItemCounts[newSection]; + NSInteger insertedItemCount = originalInsertedItems.count; + NSInteger deletedItemCount = originalDeletedItems.count; + if (newItemCount != oldItemCount + insertedItemCount - deletedItemCount) { + ASFailUpdateValidation(@"Invalid number of items in section %zd. The number of items after the update (%zd) must be equal to the number of items before the update (%zd) plus or minus the number of items inserted or deleted (%zd inserted, %zd deleted).", oldSection, newItemCount, oldItemCount, insertedItemCount, deletedItemCount); + return; + } + } +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@ %p: deletedSections=%@, insertedSections=%@, deletedItems=%@, insertedItems=%@>", NSStringFromClass(self.class), self, _deletedSections, _insertedSections, _deleteItemChanges, _insertItemChanges]; +} + + +@end + +@implementation _ASHierarchySectionChange + +- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + self = [super init]; + if (self) { + ASDisplayNodeAssert(indexSet.count > 0, @"Request to create _ASHierarchySectionChange with no sections!"); + _changeType = changeType; + _indexSet = indexSet; + _animationOptions = animationOptions; + } + return self; +} + +- (_ASHierarchySectionChange *)changeByFinalizingType +{ + _ASHierarchyChangeType newType; + switch (_changeType) { + case _ASHierarchyChangeTypeOriginalInsert: + newType = _ASHierarchyChangeTypeInsert; + break; + case _ASHierarchyChangeTypeOriginalDelete: + newType = _ASHierarchyChangeTypeDelete; + break; + default: + ASFailUpdateValidation(@"Attempt to finalize section change of invalid type %@.", NSStringFromASHierarchyChangeType(_changeType)); + return self; + } + return [[_ASHierarchySectionChange alloc] initWithChangeType:newType indexSet:_indexSet animationOptions:_animationOptions]; +} + ++ (void)sortAndCoalesceSectionChanges:(NSMutableArray<_ASHierarchySectionChange *> *)changes +{ + _ASHierarchySectionChange *firstChange = changes.firstObject; + if (firstChange == nil) { + return; + } + _ASHierarchyChangeType type = [firstChange changeType]; + + ASDisplayNodeAssert(ASHierarchyChangeTypeIsFinal(type), @"Attempt to sort and coalesce section changes of intermediary type %@. Why?", NSStringFromASHierarchyChangeType(type)); + + // Lookup table [Int: AnimationOptions] + __block std::unordered_map animationOptions; + + // All changed indexes + NSMutableIndexSet *allIndexes = [NSMutableIndexSet new]; + + for (_ASHierarchySectionChange *change in changes) { + ASDataControllerAnimationOptions options = change.animationOptions; + NSIndexSet *indexes = change.indexSet; + [indexes enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { + for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { + animationOptions[i] = options; + } + }]; + [allIndexes addIndexes:indexes]; + } + + // Create new changes by grouping sorted changes by animation option + NSMutableArray *result = [[NSMutableArray alloc] init]; + + __block ASDataControllerAnimationOptions currentOptions = 0; + NSMutableIndexSet *currentIndexes = [NSMutableIndexSet indexSet]; + + BOOL reverse = type == _ASHierarchyChangeTypeDelete || type == _ASHierarchyChangeTypeOriginalDelete; + NSEnumerationOptions options = reverse ? NSEnumerationReverse : kNilOptions; + + [allIndexes enumerateRangesWithOptions:options usingBlock:^(NSRange range, BOOL * _Nonnull stop) { + NSInteger increment = reverse ? -1 : 1; + NSUInteger start = reverse ? NSMaxRange(range) - 1 : range.location; + NSInteger limit = reverse ? range.location - 1 : NSMaxRange(range); + for (NSInteger i = start; i != limit; i += increment) { + ASDataControllerAnimationOptions options = animationOptions[i]; + + // End the previous group if needed. + if (options != currentOptions && currentIndexes.count > 0) { + _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions]; + [result addObject:change]; + [currentIndexes removeAllIndexes]; + } + + // Start a new group if needed. + if (currentIndexes.count == 0) { + currentOptions = options; + } + + [currentIndexes addIndex:i]; + } + }]; + + // Finish up the last group. + if (currentIndexes.count > 0) { + _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions]; + [result addObject:change]; + } + + [changes setArray:result]; +} + ++ (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes +{ + NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; + for (_ASHierarchySectionChange *change in changes) { + [indexes addIndexes:change.indexSet]; + } + return indexes; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: anim=%lu, type=%@, indexes=%@>", NSStringFromClass(self.class), (unsigned long)_animationOptions, NSStringFromASHierarchyChangeType(_changeType), [self.indexSet as_smallDescription]]; +} + +@end + +@implementation _ASHierarchyItemChange + +- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted +{ + self = [super init]; + if (self) { + ASDisplayNodeAssert(indexPaths.count > 0, @"Request to create _ASHierarchyItemChange with no items!"); + _changeType = changeType; + if (presorted) { + _indexPaths = indexPaths; + } else { + SEL sorting = changeType == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:); + _indexPaths = [indexPaths sortedArrayUsingSelector:sorting]; + } + _animationOptions = animationOptions; + } + return self; +} + +// Create a mapping out of changes indexPaths to a {@section : [indexSet]} fashion +// e.g. changes: (0 - 0), (0 - 1), (2 - 5) +// will become: {@0 : [0, 1], @2 : [5]} ++ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray *)changes ofType:(_ASHierarchyChangeType)changeType +{ + NSMutableDictionary *sectionToIndexSetMap = [NSMutableDictionary dictionary]; + for (_ASHierarchyItemChange *change in changes) { + NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now"); + for (NSIndexPath *indexPath in change.indexPaths) { + NSNumber *sectionKey = @(indexPath.section); + NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey]; + if (indexSet) { + [indexSet addIndex:indexPath.item]; + } else { + indexSet = [NSMutableIndexSet indexSetWithIndex:indexPath.item]; + sectionToIndexSetMap[sectionKey] = indexSet; + } + } + } + return sectionToIndexSetMap; +} + +- (_ASHierarchyItemChange *)changeByFinalizingType +{ + _ASHierarchyChangeType newType; + switch (_changeType) { + case _ASHierarchyChangeTypeOriginalInsert: + newType = _ASHierarchyChangeTypeInsert; + break; + case _ASHierarchyChangeTypeOriginalDelete: + newType = _ASHierarchyChangeTypeDelete; + break; + default: + ASFailUpdateValidation(@"Attempt to finalize item change of invalid type %@.", NSStringFromASHierarchyChangeType(_changeType)); + return self; + } + return [[_ASHierarchyItemChange alloc] initWithChangeType:newType indexPaths:_indexPaths animationOptions:_animationOptions presorted:YES]; +} + ++ (void)sortAndCoalesceItemChanges:(NSMutableArray<_ASHierarchyItemChange *> *)changes ignoringChangesInSections:(NSIndexSet *)ignoredSections +{ + if (changes.count < 1) { + return; + } + + _ASHierarchyChangeType type = [changes.firstObject changeType]; + ASDisplayNodeAssert(ASHierarchyChangeTypeIsFinal(type), @"Attempt to sort and coalesce item changes of intermediary type %@. Why?", NSStringFromASHierarchyChangeType(type)); + + // Lookup table [NSIndexPath: AnimationOptions] + NSMutableDictionary *animationOptions = [NSMutableDictionary new]; + + // All changed index paths, sorted + NSMutableArray *allIndexPaths = [[NSMutableArray alloc] init]; + + for (_ASHierarchyItemChange *change in changes) { + for (NSIndexPath *indexPath in change.indexPaths) { + if (![ignoredSections containsIndex:indexPath.section]) { + animationOptions[indexPath] = @(change.animationOptions); + [allIndexPaths addObject:indexPath]; + } + } + } + + SEL sorting = type == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:); + [allIndexPaths sortUsingSelector:sorting]; + + // Create new changes by grouping sorted changes by animation option + NSMutableArray *result = [[NSMutableArray alloc] init]; + + ASDataControllerAnimationOptions currentOptions = 0; + NSMutableArray *currentIndexPaths = [NSMutableArray array]; + + for (NSIndexPath *indexPath in allIndexPaths) { + ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue]; + + // End the previous group if needed. + if (options != currentOptions && currentIndexPaths.count > 0) { + _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES]; + [result addObject:change]; + [currentIndexPaths removeAllObjects]; + } + + // Start a new group if needed. + if (currentIndexPaths.count == 0) { + currentOptions = options; + } + + [currentIndexPaths addObject:indexPath]; + } + + // Finish up the last group. + if (currentIndexPaths.count > 0) { + _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES]; + [result addObject:change]; + } + + [changes setArray:result]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: anim=%lu, type=%@, indexPaths=%@>", NSStringFromClass(self.class), (unsigned long)_animationOptions, NSStringFromASHierarchyChangeType(_changeType), self.indexPaths]; +} + +@end diff --git a/AsyncDisplayKit/TextKit/ASTextKitComponents.h b/AsyncDisplayKit/TextKit/ASTextKitComponents.h index 5acb59c491..0b873c3191 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitComponents.h +++ b/AsyncDisplayKit/TextKit/ASTextKitComponents.h @@ -13,20 +13,6 @@ NS_ASSUME_NONNULL_BEGIN -ASDISPLAYNODE_INLINE CGFloat ceilPixelValueForScale(CGFloat f, CGFloat scale) -{ - // Round up to device pixel (.5 on retina) - return ceilf(f * scale) / scale; -} - -ASDISPLAYNODE_INLINE CGSize ceilSizeValue(CGSize s) -{ - CGFloat screenScale = [UIScreen mainScreen].scale; - s.width = ceilPixelValueForScale(s.width, screenScale); - s.height = ceilPixelValueForScale(s.height, screenScale); - return s; -} - @interface ASTextKitComponents : NSObject /** diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.mm b/AsyncDisplayKit/TextKit/ASTextKitContext.mm index 7410ed9f45..e1f65a0122 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitContext.mm @@ -75,7 +75,9 @@ NSTextContainer *))block { std::lock_guard l(_textKitMutex); - block(_layoutManager, _textStorage, _textContainer); + if (block) { + block(_layoutManager, _textStorage, _textContainer); + } } @end diff --git a/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m b/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m index 5d0337cd5f..d157cb4797 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m +++ b/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m @@ -223,9 +223,12 @@ NSAttributedString *ASCleanseAttributedStringOfCoreTextAttributes(NSAttributedSt // kCTParagraphStyleSpecifierLineSpacing -> lineSpacing // Note that kCTParagraphStyleSpecifierLineSpacing is deprecated and will die soon. We should not be using it. - CGFloat lineSpacing; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(lineSpacing), &lineSpacing)) - newParagraphStyle.lineSpacing = lineSpacing; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + CGFloat lineSpacing; + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(lineSpacing), &lineSpacing)) + newParagraphStyle.lineSpacing = lineSpacing; +#pragma clang diagnostic pop // kCTParagraphStyleSpecifierParagraphSpacing -> paragraphSpacing CGFloat paragraphSpacing; diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.mm index 83fb9706bf..23b181f8bc 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer+TextChecking.mm @@ -10,7 +10,6 @@ #import "ASTextKitRenderer+TextChecking.h" -#import "ASTextKitAttributes.h" #import "ASTextKitEntityAttribute.h" #import "ASTextKitRenderer+Positioning.h" #import "ASTextKitTailTruncater.h" diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index 05463f39e4..efaef16ded 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -16,7 +16,6 @@ #import "ASTextKitShadower.h" #import "ASTextKitTailTruncater.h" #import "ASTextKitFontSizeAdjuster.h" -#import "ASTextKitTruncating.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -124,14 +123,15 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() if (!CGSizeEqualToSize(constrainedSize, _constrainedSize)) { _sizeIsCalculated = NO; _constrainedSize = constrainedSize; - // If the context isn't created yet, it will be initialized with the appropriate size when next accessed. - if (_context || _fontSizeAdjuster) { - // If we're updating an existing context, make sure to use the same inset logic used during initialization. - // This codepath allows us to reuse the - CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:constrainedSize]; - if (_context) _context.constrainedSize = shadowConstrainedSize; - if (_fontSizeAdjuster) _fontSizeAdjuster.constrainedSize = shadowConstrainedSize; - } + _calculatedSize = CGSizeZero; + + // Throw away the all subcomponents to create them with the new constrained size new as well as let the + // truncater do it's job again for the new constrained size. This is necessary as after a truncation did happen + // the context would use the truncated string and not the original string to truncate based on the new + // constrained size + _context = nil; + _truncater = nil; + _fontSizeAdjuster = nil; } } diff --git a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm index 57a9eb2f27..9b870a36a6 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm @@ -8,8 +8,6 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASAssert.h" - #import "ASTextKitContext.h" #import "ASTextKitTailTruncater.h" diff --git a/AsyncDisplayKit/UIImage+ASConvenience.h b/AsyncDisplayKit/UIImage+ASConvenience.h new file mode 100644 index 0000000000..092bdbcd55 --- /dev/null +++ b/AsyncDisplayKit/UIImage+ASConvenience.h @@ -0,0 +1,71 @@ +// +// UIImage+ASConvenience.h +// AsyncDisplayKit +// +// Created by Hannah Troisi on 6/24/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 +#import + +// High-performance flat-colored, rounded-corner resizable images +// +// For "Baked-in Opaque" corners, set cornerColor equal to the color behind the rounded image object, e.g. the background color. +// For "Baked-in Alpha" corners, set cornerColor = [UIColor clearColor] +// +// See http://asyncdisplaykit.org/docs/corner-rounding.html for an explanation. + +@interface UIImage (ASDKAdditions) + +/** + * This generates a flat-color, rounded-corner resizeable image + * + * @param cornerRadius The radius of the rounded-corner + * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) + * @param fillColor The fill color of the rounded-corner image + */ ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor; + +/** + * This generates a flat-color, rounded-corner resizeable image with a border + * + * @param cornerRadius The radius of the rounded-corner + * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) + * @param fillColor The fill color of the rounded-corner image + * @param borderColor The border color. Set to nil for no border. + * @param borderWidth The border width. Dummy value if borderColor = nil. + */ ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor + borderColor:(UIColor *)borderColor + borderWidth:(CGFloat)borderWidth; + +/** + * This generates a flat-color, rounded-corner resizeable image with a border + * + * @param cornerRadius The radius of the rounded-corner + * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) + * @param fillColor The fill color of the rounded-corner image + * @param borderColor The border color. Set to nil for no border. + * @param borderWidth The border width. Dummy value if borderColor = nil. + * @param roundedCorners Select individual or multiple corners to round. Set to UIRectCornerAllCorners to round all 4 corners. + * @param scale The number of pixels per point. Provide 0.0 to use the screen scale. + */ ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor + borderColor:(UIColor *)borderColor + borderWidth:(CGFloat)borderWidth + roundedCorners:(UIRectCorner)roundedCorners + scale:(CGFloat)scale; + +@end + diff --git a/AsyncDisplayKit/UIImage+ASConvenience.m b/AsyncDisplayKit/UIImage+ASConvenience.m new file mode 100644 index 0000000000..4b3ce606ec --- /dev/null +++ b/AsyncDisplayKit/UIImage+ASConvenience.m @@ -0,0 +1,131 @@ +// +// UIImage+ASConvenience.m +// AsyncDisplayKit +// +// Created by Hannah Troisi on 6/24/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 "UIImage+ASConvenience.h" +#import + +@implementation UIImage (ASDKAdditions) + + + + ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor +{ + return [self as_resizableRoundedImageWithCornerRadius:cornerRadius + cornerColor:cornerColor + fillColor:fillColor + borderColor:nil + borderWidth:1.0 + roundedCorners:UIRectCornerAllCorners + scale:0.0]; +} + ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor + borderColor:(UIColor *)borderColor + borderWidth:(CGFloat)borderWidth +{ + return [self as_resizableRoundedImageWithCornerRadius:cornerRadius + cornerColor:cornerColor + fillColor:fillColor + borderColor:borderColor + borderWidth:borderWidth + roundedCorners:UIRectCornerAllCorners + scale:0.0]; +} + ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor + borderColor:(UIColor *)borderColor + borderWidth:(CGFloat)borderWidth + roundedCorners:(UIRectCorner)roundedCorners + scale:(CGFloat)scale +{ + static NSCache *__pathCache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + __pathCache = [[NSCache alloc] init]; + // UIBezierPath objects are fairly small and these are equally sized. 20 should be plenty for many different parameters. + __pathCache.countLimit = 20; + }); + + // Treat clear background color as no background color + if ([cornerColor isEqual:[UIColor clearColor]]) { + cornerColor = nil; + } + + CGFloat dimension = (cornerRadius * 2) + 1; + CGRect bounds = CGRectMake(0, 0, dimension, dimension); + + // This is a hack to make one NSNumber key out of the corners and cornerRadius + if (roundedCorners == UIRectCornerAllCorners) { + // UIRectCornerAllCorners is ~0, but below is equivalent and we can pack it into half an NSUInteger + roundedCorners = UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomLeft | UIRectCornerBottomRight; + } + // Left half of NSUInteger is roundedCorners, right half is cornerRadius + UInt64 pathKeyNSUInteger = (UInt64)roundedCorners << sizeof(Float32) * 8; + Float32 floatCornerRadius = cornerRadius; + pathKeyNSUInteger |= (NSUInteger)floatCornerRadius; + + NSNumber *pathKey = [NSNumber numberWithUnsignedLongLong:pathKeyNSUInteger]; + + UIBezierPath *path = nil; + CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius); + + @synchronized(__pathCache) { + path = [__pathCache objectForKey:pathKey]; + if (!path) { + path = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:roundedCorners cornerRadii:cornerRadii]; + [__pathCache setObject:path forKey:pathKey]; + } + } + + // We should probably check if the background color has any alpha component but that + // might be expensive due to needing to check mulitple color spaces. + UIGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); + + if (cornerColor) { + [cornerColor setFill]; + // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overwrites directly. + UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy); + } + + [fillColor setFill]; + [path fill]; + + if (borderColor) { + [borderColor setStroke]; + + // Inset border fully inside filled path (not halfway on each side of path) + CGRect strokeRect = CGRectInset(bounds, borderWidth / 2.0, borderWidth / 2.0); + + // It is rarer to have a stroke path, and our cache key only handles rounded rects for the exact-stretchable + // size calculated by cornerRadius, so we won't bother caching this path. Profiling validates this decision. + UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect cornerRadius:cornerRadius]; + [strokePath setLineWidth:borderWidth]; + [strokePath stroke]; + } + + UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + UIEdgeInsets capInsets = UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius); + result = [result resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; + + return result; +} + +@end \ No newline at end of file diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m index 1e33f77cd6..ba02de42e7 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m @@ -13,6 +13,7 @@ #import "ASCollectionView.h" #import "ASCollectionViewFlowLayoutInspector.h" +#import "ASCellNode.h" /** * Test Data Source diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.m b/AsyncDisplayKitTests/ASCollectionViewTests.m deleted file mode 100644 index 4c7f84aec3..0000000000 --- a/AsyncDisplayKitTests/ASCollectionViewTests.m +++ /dev/null @@ -1,137 +0,0 @@ -// -// ASCollectionViewTests.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 -#import "ASCollectionView.h" -#import "ASCollectionDataController.h" -#import "ASCollectionViewFlowLayoutInspector.h" - -@interface ASCollectionViewTestDelegate : NSObject - -@property (nonatomic, assign) NSInteger numberOfSections; -@property (nonatomic, assign) NSInteger numberOfItemsInSection; - -@end - -@implementation ASCollectionViewTestDelegate - -- (id)initWithNumberOfSections:(NSInteger)numberOfSections numberOfItemsInSection:(NSInteger)numberOfItemsInSection { - if (self = [super init]) { - _numberOfSections = numberOfSections; - _numberOfItemsInSection = numberOfItemsInSection; - } - - return self; -} - -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath { - ASTextCellNode *textCellNode = [ASTextCellNode new]; - textCellNode.text = indexPath.description; - - return textCellNode; -} - - -- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { - return ^{ - ASTextCellNode *textCellNode = [ASTextCellNode new]; - textCellNode.text = indexPath.description; - return textCellNode; - }; -} - -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { - return self.numberOfSections; -} - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - return self.numberOfItemsInSection; -} - -@end - -@interface ASCollectionViewTestController: UIViewController - -@property (nonatomic, strong) ASCollectionViewTestDelegate *asyncDelegate; -@property (nonatomic, strong) ASCollectionView *collectionView; - -@end - -@implementation ASCollectionViewTestController - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.asyncDelegate = [[ASCollectionViewTestDelegate alloc] initWithNumberOfSections:10 numberOfItemsInSection:10]; - - self.collectionView = [[ASCollectionView alloc] initWithFrame:self.view.bounds - collectionViewLayout:[UICollectionViewFlowLayout new]]; - self.collectionView.asyncDataSource = self.asyncDelegate; - self.collectionView.asyncDelegate = self.asyncDelegate; - - [self.view addSubview:self.collectionView]; -} - -- (void)viewWillLayoutSubviews { - [super viewWillLayoutSubviews]; - - self.collectionView.frame = self.view.bounds; -} - -@end - -@interface ASCollectionView (InternalTesting) - -- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController; - -@end - -@interface ASCollectionViewTests : XCTestCase - -@end - -@implementation ASCollectionViewTests - -- (void)testThatItSetsALayoutInspectorForFlowLayouts -{ - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - XCTAssert(collectionView.layoutInspector != nil, @"should automatically set a layout delegate for flow layouts"); - XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewFlowLayoutInspector class]], @"should have a flow layout inspector by default"); -} - -- (void)testThatItDoesNotSetALayoutInspectorForCustomLayouts -{ - UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - XCTAssert(collectionView.layoutInspector == nil, @"should not set a layout delegate for custom layouts"); -} - -- (void)testThatRegisteringASupplementaryNodeStoresItForIntrospection -{ - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - [collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; - XCTAssertEqualObjects([collectionView supplementaryNodeKindsInDataController:nil], @[UICollectionElementKindSectionHeader]); -} - -- (void)testCollectionViewController -{ - ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; - - UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; - [containerView addSubview:testController.view]; - - [testController.collectionView reloadData]; - - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; -} - -@end diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.mm b/AsyncDisplayKitTests/ASCollectionViewTests.mm new file mode 100644 index 0000000000..922b438518 --- /dev/null +++ b/AsyncDisplayKitTests/ASCollectionViewTests.mm @@ -0,0 +1,348 @@ +// +// ASCollectionViewTests.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 +#import "ASCollectionView.h" +#import "ASCollectionDataController.h" +#import "ASCollectionViewFlowLayoutInspector.h" +#import "ASCellNode.h" +#import "ASCollectionNode.h" +#import "ASDisplayNode+Beta.h" +#import + +@interface ASTextCellNodeWithSetSelectedCounter : ASTextCellNode + +@property (nonatomic, assign) NSUInteger setSelectedCounter; + +@end + +@implementation ASTextCellNodeWithSetSelectedCounter + +- (void)setSelected:(BOOL)selected +{ + [super setSelected:selected]; + _setSelectedCounter++; +} + +@end + +@interface ASCollectionViewTestDelegate : NSObject + +@end + +@implementation ASCollectionViewTestDelegate { + @package + std::vector _itemCounts; +} + +- (id)initWithNumberOfSections:(NSInteger)numberOfSections numberOfItemsInSection:(NSInteger)numberOfItemsInSection { + if (self = [super init]) { + for (NSInteger i = 0; i < numberOfSections; i++) { + _itemCounts.push_back(numberOfItemsInSection); + } + } + + return self; +} + +- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath { + ASTextCellNodeWithSetSelectedCounter *textCellNode = [ASTextCellNodeWithSetSelectedCounter new]; + textCellNode.text = indexPath.description; + + return textCellNode; +} + + +- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { + return ^{ + ASTextCellNodeWithSetSelectedCounter *textCellNode = [ASTextCellNodeWithSetSelectedCounter new]; + textCellNode.text = indexPath.description; + return textCellNode; + }; +} + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return _itemCounts.size(); +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return _itemCounts[section]; +} + +@end + +@interface ASCollectionViewTestController: UIViewController + +@property (nonatomic, strong) ASCollectionViewTestDelegate *asyncDelegate; +@property (nonatomic, strong) ASCollectionView *collectionView; + +@end + +@implementation ASCollectionViewTestController + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Populate these immediately so that they're not unexpectedly nil during tests. + self.asyncDelegate = [[ASCollectionViewTestDelegate alloc] initWithNumberOfSections:10 numberOfItemsInSection:10]; + + self.collectionView = [[ASCollectionView alloc] initWithFrame:self.view.bounds + collectionViewLayout:[UICollectionViewFlowLayout new]]; + self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.collectionView.asyncDataSource = self.asyncDelegate; + self.collectionView.asyncDelegate = self.asyncDelegate; + + [self.view addSubview:self.collectionView]; + } + return self; +} + +@end + +@interface ASCollectionView (InternalTesting) + +- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController; + +@end + +@interface ASCollectionViewTests : XCTestCase + +@end + +@implementation ASCollectionViewTests + +- (void)testThatItSetsALayoutInspectorForFlowLayouts +{ + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + XCTAssert(collectionView.layoutInspector != nil, @"should automatically set a layout delegate for flow layouts"); + XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewFlowLayoutInspector class]], @"should have a flow layout inspector by default"); +} + +- (void)testThatADefaultLayoutInspectorIsProvidedForCustomLayouts +{ + UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + XCTAssert(collectionView.layoutInspector != nil, @"should automatically set a layout delegate for flow layouts"); + XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewLayoutInspector class]], @"should have a default layout inspector by default"); +} + +- (void)testThatRegisteringASupplementaryNodeStoresItForIntrospection +{ + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + [collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; + XCTAssertEqualObjects([collectionView supplementaryNodeKindsInDataController:nil], @[UICollectionElementKindSectionHeader]); +} + +- (void)testSelection +{ + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + [window setRootViewController:testController]; + [window makeKeyAndVisible]; + + [testController.collectionView reloadDataImmediately]; + [testController.collectionView layoutIfNeeded]; + + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; + ASCellNode *node = [testController.collectionView nodeForItemAtIndexPath:indexPath]; + + // selecting node should select cell + node.selected = YES; + XCTAssertTrue([[testController.collectionView indexPathsForSelectedItems] containsObject:indexPath], @"Selecting node should update cell selection."); + + // deselecting node should deselect cell + node.selected = NO; + XCTAssertTrue([[testController.collectionView indexPathsForSelectedItems] isEqualToArray:@[]], @"Deselecting node should update cell selection."); + + // selecting cell via collectionView should select node + [testController.collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; + XCTAssertTrue(node.isSelected == YES, @"Selecting cell should update node selection."); + + // deselecting cell via collectionView should deselect node + [testController.collectionView deselectItemAtIndexPath:indexPath animated:NO]; + XCTAssertTrue(node.isSelected == NO, @"Deselecting cell should update node selection."); + + // select the cell again, scroll down and back up, and check that the state persisted + [testController.collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; + XCTAssertTrue(node.isSelected == YES, @"Selecting cell should update node selection."); + + // reload cell (-prepareForReuse is called) & check that selected state is preserved + [testController.collectionView setContentOffset:CGPointMake(0,testController.collectionView.bounds.size.height)]; + [testController.collectionView layoutIfNeeded]; + [testController.collectionView setContentOffset:CGPointMake(0,0)]; + [testController.collectionView layoutIfNeeded]; + XCTAssertTrue(node.isSelected == YES, @"Reloaded cell should preserve state."); + + // deselecting cell should deselect node + UICollectionViewCell *cell = [testController.collectionView cellForItemAtIndexPath:indexPath]; + cell.selected = NO; + XCTAssertTrue(node.isSelected == NO, @"Deselecting cell should update node selection."); + + // check setSelected not called extra times + XCTAssertTrue([(ASTextCellNodeWithSetSelectedCounter *)node setSelectedCounter] == 6, @"setSelected: should not be called on node multiple times."); +} + +- (void)testTuningParametersWithExplicitRangeMode +{ + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + + ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.1, .trailingBufferScreenfuls = 0.1 }; + ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 0.1, .trailingBufferScreenfuls = 0.1 }; + ASRangeTuningParameters fullRenderParams = { .leadingBufferScreenfuls = 0.5, .trailingBufferScreenfuls = 0.5 }; + ASRangeTuningParameters fullPreloadParams = { .leadingBufferScreenfuls = 1, .trailingBufferScreenfuls = 0.5 }; + + [collectionView setTuningParameters:minimumRenderParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay]; + [collectionView setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeFetchData]; + [collectionView setTuningParameters:fullRenderParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay]; + [collectionView setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeFetchData]; + + XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(minimumRenderParams, + [collectionView tuningParametersForRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay])); + XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(minimumPreloadParams, + [collectionView tuningParametersForRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeFetchData])); + XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(fullRenderParams, + [collectionView tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay])); + XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(fullPreloadParams, + [collectionView tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeFetchData])); +} + +- (void)testTuningParameters +{ + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + + ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.2, .trailingBufferScreenfuls = 3.2 }; + ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 4.3, .trailingBufferScreenfuls = 2.3 }; + + [collectionView setTuningParameters:renderParams forRangeType:ASLayoutRangeTypeDisplay]; + [collectionView setTuningParameters:preloadParams forRangeType:ASLayoutRangeTypeFetchData]; + + XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(renderParams, [collectionView tuningParametersForRangeType:ASLayoutRangeTypeDisplay])); + XCTAssertTrue(ASRangeTuningParametersEqualToRangeTuningParameters(preloadParams, [collectionView tuningParametersForRangeType:ASLayoutRangeTypeFetchData])); +} + +/** + * This may seem silly, but we had issues where the runtime sometimes wouldn't correctly report + * conformances declared on categories. + */ +- (void)testThatCollectionNodeConformsToExpectedProtocols +{ + ASCollectionNode *node = [[ASCollectionNode alloc] initWithFrame:CGRectZero collectionViewLayout:[[UICollectionViewFlowLayout alloc] init]]; + XCTAssert([node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]); +} + +#pragma mark - Update Validations + +#define updateValidationTestPrologue \ + [ASDisplayNode setSuppressesInvalidCollectionUpdateExceptions:NO];\ + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil];\ + __unused ASCollectionViewTestDelegate *del = testController.asyncDelegate;\ + __unused ASCollectionView *cv = testController.collectionView;\ + UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];\ + window.rootViewController = testController;\ + \ + [testController.collectionView reloadDataImmediately];\ + [testController.collectionView layoutIfNeeded]; + +- (void)testThatSubmittingAValidInsertDoesNotThrowAnException +{ + updateValidationTestPrologue + NSInteger sectionCount = del->_itemCounts.size(); + + del->_itemCounts[sectionCount - 1]++; + XCTAssertNoThrow([cv insertItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:sectionCount - 1] ]]); +} + +- (void)testThatSubmittingAValidReloadDoesNotThrowAnException +{ + updateValidationTestPrologue + NSInteger sectionCount = del->_itemCounts.size(); + + XCTAssertNoThrow([cv reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:sectionCount - 1] ]]); +} + +- (void)testThatSubmittingAnInvalidInsertThrowsAnException +{ + updateValidationTestPrologue + NSInteger sectionCount = del->_itemCounts.size(); + + XCTAssertThrows([cv insertItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:sectionCount + 1] ]]); +} + +- (void)testThatSubmittingAnInvalidDeleteThrowsAnException +{ + updateValidationTestPrologue + NSInteger sectionCount = del->_itemCounts.size(); + + XCTAssertThrows([cv deleteItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:sectionCount + 1] ]]); +} + +- (void)testThatDeletingAndReloadingTheSameItemThrowsAnException +{ + updateValidationTestPrologue + + XCTAssertThrows([cv performBatchUpdates:^{ + NSArray *indexPaths = @[ [NSIndexPath indexPathForItem:0 inSection:0] ]; + [cv deleteItemsAtIndexPaths:indexPaths]; + [cv reloadItemsAtIndexPaths:indexPaths]; + } completion:nil]); +} + +- (void)testThatHavingAnIncorrectSectionCountThrowsAnException +{ + updateValidationTestPrologue + + XCTAssertThrows([cv deleteSections:[NSIndexSet indexSetWithIndex:0]]); +} + +- (void)testThatHavingAnIncorrectItemCountThrowsAnException +{ + updateValidationTestPrologue + + XCTAssertThrows([cv deleteItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:0] ]]); +} + +- (void)testThatHavingAnIncorrectItemCountWithNoUpdatesThrowsAnException +{ + updateValidationTestPrologue + + XCTAssertThrows([cv performBatchUpdates:^{ + del->_itemCounts[0]++; + } completion:nil]); +} + +- (void)testThatInsertingAnInvalidSectionThrowsAnException +{ + updateValidationTestPrologue + NSInteger sectionCount = del->_itemCounts.size(); + + del->_itemCounts.push_back(10); + XCTAssertThrows([cv performBatchUpdates:^{ + [cv insertSections:[NSIndexSet indexSetWithIndex:sectionCount + 1]]; + } completion:nil]); +} + +- (void)testThatDeletingAndReloadingASectionThrowsAnException +{ + updateValidationTestPrologue + NSInteger sectionCount = del->_itemCounts.size(); + + del->_itemCounts.pop_back(); + XCTAssertThrows([cv performBatchUpdates:^{ + NSIndexSet *sections = [NSIndexSet indexSetWithIndex:sectionCount - 1]; + [cv reloadSections:sections]; + [cv deleteSections:sections]; + } completion:nil]); +} + +@end diff --git a/AsyncDisplayKitTests/ASDisplayNodeExtrasTests.m b/AsyncDisplayKitTests/ASDisplayNodeExtrasTests.m new file mode 100644 index 0000000000..6f1731d211 --- /dev/null +++ b/AsyncDisplayKitTests/ASDisplayNodeExtrasTests.m @@ -0,0 +1,76 @@ +// +// ASDisplayNodeExtrasTests.m +// AsyncDisplayKit +// +// Created by Kiel Gillard on 27/06/2016. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import +#import + +@interface ASDisplayNodeExtrasTests : XCTestCase + +@end + +@interface TestDisplayNode : ASDisplayNode +@end + +@implementation TestDisplayNode +@end + +@implementation ASDisplayNodeExtrasTests + +- (void)testShallowFindSubnodesOfSubclass { + ASDisplayNode *supernode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{ + return [CALayer layer]; + }]; + NSUInteger count = 10; + NSMutableArray *expected = [[NSMutableArray alloc] initWithCapacity:count]; + for (NSUInteger nodeIndex = 0; nodeIndex < count; nodeIndex++) { + TestDisplayNode *node = [[TestDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{ + return [CALayer layer]; + }]; + [supernode addSubnode:node]; + [expected addObject:node]; + } + NSArray *found = ASDisplayNodeFindAllSubnodesOfClass(supernode, [TestDisplayNode class]); + XCTAssertEqualObjects(found, expected, @"Expecting %lu %@ nodes, found %lu", (unsigned long)count, [TestDisplayNode class], (unsigned long)found.count); +} + +- (void)testDeepFindSubnodesOfSubclass { + ASDisplayNode *supernode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{ + return [CALayer layer]; + }]; + + const NSUInteger count = 2; + const NSUInteger levels = 2; + const NSUInteger capacity = [[self class] capacityForCount:count levels:levels]; + NSMutableArray *expected = [[NSMutableArray alloc] initWithCapacity:capacity]; + + [[self class] addSubnodesToNode:supernode number:count remainingLevels:levels accumulated:expected]; + + NSArray *found = ASDisplayNodeFindAllSubnodesOfClass(supernode, [TestDisplayNode class]); + XCTAssertEqualObjects(found, expected, @"Expecting %lu %@ nodes, found %lu", (unsigned long)count, [TestDisplayNode class], (unsigned long)found.count); +} + ++ (void)addSubnodesToNode:(ASDisplayNode *)supernode number:(NSUInteger)number remainingLevels:(NSUInteger)level accumulated:(inout NSMutableArray *)expected { + if (level == 0) return; + for (NSUInteger nodeIndex = 0; nodeIndex < number; nodeIndex++) { + TestDisplayNode *node = [[TestDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{ + return [CALayer layer]; + }]; + [supernode addSubnode:node]; + [expected addObject:node]; + [self addSubnodesToNode:node number:number remainingLevels:(level - 1) accumulated:expected]; + } +} + +// Graph theory is failing me atm. ++ (NSUInteger)capacityForCount:(NSUInteger)count levels:(NSUInteger)level { + if (level == 0) return 0; + return pow(count, level) + [self capacityForCount:count levels:(level - 1)]; +} + +@end diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index a701fa050b..627960d12d 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -130,4 +130,88 @@ XCTAssertEqual(node.subnodes[2], node2); } +- (void)testMeasurementInBackgroundThreadWithLoadedNode +{ + ASDisplayNode *node1 = [[ASDisplayNode alloc] init]; + ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; + + ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { + ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; + if ([strongNode.layoutState isEqualToNumber:@1]) { + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node1]]; + } else { + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node2]]; + } + }; + + // Intentionally trigger view creation + [node2 view]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout also if one node is already loaded"]; + + dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; + XCTAssertEqual(node.subnodes[0], node1); + + node.layoutState = @2; + [node invalidateCalculatedLayout]; + [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; + + // Dispatch back to the main thread to let the insertion / deletion of subnodes happening + dispatch_async(dispatch_get_main_queue(), ^{ + XCTAssertEqual(node.subnodes[0], node2); + [expectation fulfill]; + }); + }); + + [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { + if (error) { + NSLog(@"Timeout Error: %@", error); + } + }]; +} + +- (void)testTransitionLayoutWithAnimationWithLoadedNodes +{ + ASDisplayNode *node1 = [[ASDisplayNode alloc] init]; + ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; + + ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + + node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { + ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; + if ([strongNode.layoutState isEqualToNumber:@1]) { + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node1]]; + } else { + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node2]]; + } + }; + + // Intentionally trigger view creation + [node2 view]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"]; + + [node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)]; + XCTAssertEqual(node.subnodes[0], node1); + + node.layoutState = @2; + [node invalidateCalculatedLayout]; + [node transitionLayoutWithAnimation:YES shouldMeasureAsync: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); + [expectation fulfill]; + }); + }]; + + [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { + if (error) { + NSLog(@"Timeout Error: %@", error); + } + }]; +} + @end diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 86529730f3..9be86b49de 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -85,15 +85,15 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @end @interface ASTestDisplayNode : ASDisplayNode -@property (atomic, copy) void (^willDeallocBlock)(ASTestDisplayNode *node); -@property (atomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size); -@property (atomic) BOOL hasFetchedData; +@property (nonatomic, copy) void (^willDeallocBlock)(ASTestDisplayNode *node); +@property (nonatomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size); +@property (nonatomic) BOOL hasFetchedData; -@property (atomic) BOOL displayRangeStateChangedToYES; -@property (atomic) BOOL displayRangeStateChangedToNO; +@property (nonatomic) BOOL displayRangeStateChangedToYES; +@property (nonatomic) BOOL displayRangeStateChangedToNO; -@property (atomic) BOOL loadStateChangedToYES; -@property (atomic) BOOL loadStateChangedToNO; +@property (nonatomic) BOOL loadStateChangedToYES; +@property (nonatomic) BOOL loadStateChangedToNO; @end @interface ASTestResponderNode : ASTestDisplayNode @@ -160,7 +160,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @end @interface UIResponderNodeTestView : _ASDisplayView -@property(nonatomic) BOOL isFirstResponder; +@property(nonatomic) BOOL testIsFirstResponder; @end @implementation UIDisplayNodeTestView @@ -192,7 +192,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @implementation UIResponderNodeTestView - (BOOL)becomeFirstResponder { - self.isFirstResponder = YES; + self.testIsFirstResponder = YES; return YES; } @@ -202,8 +202,8 @@ for (ASDisplayNode *n in @[ nodes ]) {\ - (BOOL)resignFirstResponder { [super resignFirstResponder]; - if (self.isFirstResponder) { - self.isFirstResponder = NO; + if (self.testIsFirstResponder) { + self.testIsFirstResponder = NO; return YES; } return NO; @@ -1127,7 +1127,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point { ASDisplayNode *parent = [[[ASDisplayNode alloc] init] autorelease]; ASDisplayNode *nilNode = nil; - XCTAssertNoThrow([parent addSubnode:nilNode], @"Don't try to add nil, but we'll deal."); + XCTAssertThrows([parent addSubnode:nilNode], @"Don't try to add nil, but we'll deal with it in production, but throw in development."); XCTAssertNoThrow([parent addSubnode:parent], @"Not good, test that we recover"); XCTAssertEqual(0u, parent.subnodes.count, @"We shouldn't have any subnodes"); } @@ -1320,6 +1320,9 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point XCTAssertEqual(3u, parent.subnodes.count, @"Should have the right subnode count"); XCTAssertEqualObjects(nilParent, d.supernode, @"d's parent is messed up"); + // Check insert a nil node + ASDisplayNode *nilNode = nil; + XCTAssertThrows([parent insertSubnode:nilNode atIndex:0], @"Should not allow insertion of nil node. We will throw in development and deal with it in production"); // Check insert at invalid index XCTAssertThrows([parent insertSubnode:d atIndex:NSNotFound], @"Should not allow insertion at invalid index"); diff --git a/AsyncDisplayKitTests/ASEditableTextNodeTests.m b/AsyncDisplayKitTests/ASEditableTextNodeTests.m index 43d415ac51..83960f7331 100644 --- a/AsyncDisplayKitTests/ASEditableTextNodeTests.m +++ b/AsyncDisplayKitTests/ASEditableTextNodeTests.m @@ -47,7 +47,6 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta) _attributedText = mas; _editableTextNode.attributedText = _attributedText; - } #pragma mark - ASEditableTextNode @@ -55,10 +54,89 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta) - (void)testAllocASEditableTextNode { ASEditableTextNode *node = [[ASEditableTextNode alloc] init]; - XCTAssertTrue([[node class] isSubclassOfClass:[ASEditableTextNode class]], @"ASTextNode alloc should return an instance of ASTextNode, instead returned %@", [node class]); + XCTAssertTrue([[node class] isSubclassOfClass:[ASEditableTextNode class]], @"ASEditableTextNode alloc should return an instance of ASEditableTextNode, instead returned %@", [node class]); } -#pragma mark - ASEditableTextNode +#pragma mark - ASEditableTextNode Tests + +- (void)testUITextInputTraitDefaults +{ + ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init]; + + XCTAssertTrue(editableTextNode.autocapitalizationType == UITextAutocapitalizationTypeSentences, @"_ASTextInputTraitsPendingState's autocapitalizationType default should be UITextAutocapitalizationTypeSentences."); + XCTAssertTrue(editableTextNode.autocorrectionType == UITextAutocorrectionTypeDefault, @"_ASTextInputTraitsPendingState's autocorrectionType default should be UITextAutocorrectionTypeDefault."); + XCTAssertTrue(editableTextNode.spellCheckingType == UITextSpellCheckingTypeDefault, @"_ASTextInputTraitsPendingState's spellCheckingType default should be UITextSpellCheckingTypeDefault."); + XCTAssertTrue(editableTextNode.keyboardType == UIKeyboardTypeDefault, @"_ASTextInputTraitsPendingState's keyboardType default should be UIKeyboardTypeDefault."); + XCTAssertTrue(editableTextNode.keyboardAppearance == UIKeyboardAppearanceDefault, @"_ASTextInputTraitsPendingState's keyboardAppearance default should be UIKeyboardAppearanceDefault."); + XCTAssertTrue(editableTextNode.returnKeyType == UIReturnKeyDefault, @"_ASTextInputTraitsPendingState's returnKeyType default should be UIReturnKeyDefault."); + XCTAssertTrue(editableTextNode.enablesReturnKeyAutomatically == NO, @"_ASTextInputTraitsPendingState's enablesReturnKeyAutomatically default should be NO."); + XCTAssertTrue(editableTextNode.isSecureTextEntry == NO, @"_ASTextInputTraitsPendingState's isSecureTextEntry default should be NO."); + + XCTAssertTrue(editableTextNode.textView.autocapitalizationType == UITextAutocapitalizationTypeSentences, @"textView's autocapitalizationType default should be UITextAutocapitalizationTypeSentences."); + XCTAssertTrue(editableTextNode.textView.autocorrectionType == UITextAutocorrectionTypeDefault, @"textView's autocorrectionType default should be UITextAutocorrectionTypeDefault."); + XCTAssertTrue(editableTextNode.textView.spellCheckingType == UITextSpellCheckingTypeDefault, @"textView's spellCheckingType default should be UITextSpellCheckingTypeDefault."); + XCTAssertTrue(editableTextNode.textView.keyboardType == UIKeyboardTypeDefault, @"textView's keyboardType default should be UIKeyboardTypeDefault."); + XCTAssertTrue(editableTextNode.textView.keyboardAppearance == UIKeyboardAppearanceDefault, @"textView's keyboardAppearance default should be UIKeyboardAppearanceDefault."); + XCTAssertTrue(editableTextNode.textView.returnKeyType == UIReturnKeyDefault, @"textView's returnKeyType default should be UIReturnKeyDefault."); + XCTAssertTrue(editableTextNode.textView.enablesReturnKeyAutomatically == NO, @"textView's enablesReturnKeyAutomatically default should be NO."); + XCTAssertTrue(editableTextNode.textView.isSecureTextEntry == NO, @"textView's isSecureTextEntry default should be NO."); +} + +- (void)testUITextInputTraitsSetTraitsBeforeViewLoaded +{ + // UITextView ignores any values set on the first 3 properties below if secureTextEntry is enabled. + // Because of this UIKit behavior, we'll test secure entry seperately + ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init]; + + editableTextNode.autocapitalizationType = UITextAutocapitalizationTypeWords; + editableTextNode.autocorrectionType = UITextAutocorrectionTypeYes; + editableTextNode.spellCheckingType = UITextSpellCheckingTypeYes; + editableTextNode.keyboardType = UIKeyboardTypeTwitter; + editableTextNode.keyboardAppearance = UIKeyboardAppearanceDark; + editableTextNode.returnKeyType = UIReturnKeyGo; + editableTextNode.enablesReturnKeyAutomatically = YES; + + XCTAssertTrue(editableTextNode.textView.autocapitalizationType == UITextAutocapitalizationTypeWords, @"textView's autocapitalizationType should be UITextAutocapitalizationTypeAllCharacters."); + XCTAssertTrue(editableTextNode.textView.autocorrectionType == UITextAutocorrectionTypeYes, @"textView's autocorrectionType should be UITextAutocorrectionTypeYes."); + XCTAssertTrue(editableTextNode.textView.spellCheckingType == UITextSpellCheckingTypeYes, @"textView's spellCheckingType should be UITextSpellCheckingTypeYes."); + XCTAssertTrue(editableTextNode.textView.keyboardType == UIKeyboardTypeTwitter, @"textView's keyboardType should be UIKeyboardTypeTwitter."); + XCTAssertTrue(editableTextNode.textView.keyboardAppearance == UIKeyboardAppearanceDark, @"textView's keyboardAppearance should be UIKeyboardAppearanceDark."); + XCTAssertTrue(editableTextNode.textView.returnKeyType == UIReturnKeyGo, @"textView's returnKeyType should be UIReturnKeyGo."); + XCTAssertTrue(editableTextNode.textView.enablesReturnKeyAutomatically == YES, @"textView's enablesReturnKeyAutomatically should be YES."); + + ASEditableTextNode *secureEditableTextNode = [[ASEditableTextNode alloc] init]; + secureEditableTextNode.secureTextEntry = YES; + + XCTAssertTrue(secureEditableTextNode.textView.secureTextEntry == YES, @"textView's isSecureTextEntry should be YES."); +} + +- (void)testUITextInputTraitsChangeTraitAfterViewLoaded +{ + // UITextView ignores any values set on the first 3 properties below if secureTextEntry is enabled. + // Because of this UIKit behavior, we'll test secure entry seperately + ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init]; + + editableTextNode.textView.autocapitalizationType = UITextAutocapitalizationTypeWords; + editableTextNode.textView.autocorrectionType = UITextAutocorrectionTypeYes; + editableTextNode.textView.spellCheckingType = UITextSpellCheckingTypeYes; + editableTextNode.textView.keyboardType = UIKeyboardTypeTwitter; + editableTextNode.textView.keyboardAppearance = UIKeyboardAppearanceDark; + editableTextNode.textView.returnKeyType = UIReturnKeyGo; + editableTextNode.textView.enablesReturnKeyAutomatically = YES; + + XCTAssertTrue(editableTextNode.textView.autocapitalizationType == UITextAutocapitalizationTypeWords, @"textView's autocapitalizationType should be UITextAutocapitalizationTypeAllCharacters."); + XCTAssertTrue(editableTextNode.textView.autocorrectionType == UITextAutocorrectionTypeYes, @"textView's autocorrectionType should be UITextAutocorrectionTypeYes."); + XCTAssertTrue(editableTextNode.textView.spellCheckingType == UITextSpellCheckingTypeYes, @"textView's spellCheckingType should be UITextSpellCheckingTypeYes."); + XCTAssertTrue(editableTextNode.textView.keyboardType == UIKeyboardTypeTwitter, @"textView's keyboardType should be UIKeyboardTypeTwitter."); + XCTAssertTrue(editableTextNode.textView.keyboardAppearance == UIKeyboardAppearanceDark, @"textView's keyboardAppearance should be UIKeyboardAppearanceDark."); + XCTAssertTrue(editableTextNode.textView.returnKeyType == UIReturnKeyGo, @"textView's returnKeyType should be UIReturnKeyGo."); + XCTAssertTrue(editableTextNode.textView.enablesReturnKeyAutomatically == YES, @"textView's enablesReturnKeyAutomatically should be YES."); + + ASEditableTextNode *secureEditableTextNode = [[ASEditableTextNode alloc] init]; + secureEditableTextNode.textView.secureTextEntry = YES; + + XCTAssertTrue(secureEditableTextNode.textView.secureTextEntry == YES, @"textView's isSecureTextEntry should be YES."); +} - (void)testSetPreferredFrameSize { @@ -66,8 +144,8 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta) _editableTextNode.preferredFrameSize = preferredFrameSize; CGSize calculatedSize = [_editableTextNode measure:CGSizeZero]; - XCTAssertTrue(calculatedSize.width != preferredFrameSize.width, @"Calculated width (%f) should be equal than preferred width (%f)", calculatedSize.width, preferredFrameSize.width); - XCTAssertTrue(calculatedSize.width != preferredFrameSize.width, @"Calculated height (%f) should be equal than preferred height (%f)", calculatedSize.width, preferredFrameSize.width); + XCTAssertTrue(calculatedSize.width != preferredFrameSize.width, @"Calculated width (%f) should be equal to preferred width (%f)", calculatedSize.width, preferredFrameSize.width); + XCTAssertTrue(calculatedSize.width != preferredFrameSize.width, @"Calculated height (%f) should be equal to preferred height (%f)", calculatedSize.width, preferredFrameSize.width); _editableTextNode.preferredFrameSize = CGSizeZero; } diff --git a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m index 9b7b325660..51d5921753 100644 --- a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m +++ b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m @@ -45,42 +45,6 @@ return [[[UIImage alloc] initWithContentsOfFile:[self _testImageURL].path] autorelease]; } -static BOOL ASInvokeConditionBlockWithBarriers(BOOL (^block)()) { - // In case the block does multiple comparisons, ensure it has a consistent view of memory by issuing read-write - // barriers on either side of the block. - OSMemoryBarrier(); - BOOL result = block(); - OSMemoryBarrier(); - return result; -} - -static BOOL ASRunRunLoopUntilBlockIsTrue(BOOL (^block)()) -{ - // Time out after 30 seconds. - CFTimeInterval timeoutDate = CACurrentMediaTime() + 30.0f; - BOOL passed = NO; - - while (true) { - passed = ASInvokeConditionBlockWithBarriers(block); - - if (passed) { - break; - } - - CFTimeInterval now = CACurrentMediaTime(); - if (now > timeoutDate) { - break; - } - - // Run 1000 times a second until the poll timeout or until timeoutDate, whichever is first. - CFTimeInterval runLoopTimeout = MIN(0.001, timeoutDate - now); - CFRunLoopRunInMode(kCFRunLoopDefaultMode, runLoopTimeout, true); - } - - return passed; -} - - #pragma mark - #pragma mark Unit tests. @@ -360,14 +324,11 @@ static BOOL ASRunRunLoopUntilBlockIsTrue(BOOL (^block)()) [imageNode reloadImageIdentifierSources]; // Wait until the image is loaded. - ASRunRunLoopUntilBlockIsTrue(^BOOL{ - return [(id)imageNode.loadedImageIdentifier isEqual:imageIdentifier]; - }); + [self expectationForPredicate:[NSPredicate predicateWithFormat:@"loadedImageIdentifier = %@", imageIdentifier] evaluatedWithObject:imageNode handler:nil]; + [self waitForExpectationsWithTimeout:30 handler:nil]; // Verify the delegation. [mockDelegate verify]; - // Also verify that it's acutally loaded (could be false if we timed out above). - XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"Failed to load image"); [imageNode release]; } diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.mm b/AsyncDisplayKitTests/ASSnapshotTestCase.mm index 1a68e47cfa..3b2aaaadd0 100644 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.mm +++ b/AsyncDisplayKitTests/ASSnapshotTestCase.mm @@ -43,7 +43,7 @@ node.displaysAsynchronously = flag; for (ASDisplayNode *subnode in node.subnodes) { - subnode.displaysAsynchronously = flag; + [self _recursivelySetDisplaysAsynchronously:flag forNode:subnode]; } } diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 19a827ea28..2c282de139 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -14,13 +14,15 @@ #import "ASTableViewInternal.h" #import "ASDisplayNode+Subclasses.h" #import "ASChangeSetDataController.h" +#import "ASCellNode.h" +#import "ASTableNode.h" #define NumberOfSections 10 #define NumberOfRowsPerSection 20 #define NumberOfReloadIterations 50 @interface ASTestDataController : ASChangeSetDataController -@property (atomic) int numberOfAllNodesRelayouts; +@property (nonatomic) int numberOfAllNodesRelayouts; @end @implementation ASTestDataController @@ -34,7 +36,7 @@ @end @interface ASTestTableView : ASTableView -@property (atomic, copy) void (^willDeallocBlock)(ASTableView *tableView); +@property (nonatomic, copy) void (^willDeallocBlock)(ASTableView *tableView); @end @implementation ASTestTableView @@ -59,7 +61,7 @@ @end @interface ASTableViewTestDelegate : NSObject -@property (atomic, copy) void (^willDeallocBlock)(ASTableViewTestDelegate *delegate); +@property (nonatomic, copy) void (^willDeallocBlock)(ASTableViewTestDelegate *delegate); @end @implementation ASTableViewTestDelegate @@ -90,7 +92,7 @@ @interface ASTestTextCellNode : ASTextCellNode /** Calculated by counting how many times -layoutSpecThatFits: is called on the main thread. */ -@property (atomic) int numberOfLayoutsOnMainThread; +@property (nonatomic) int numberOfLayoutsOnMainThread; @end @implementation ASTestTextCellNode @@ -128,7 +130,6 @@ return textCellNode; } - - (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath { return ^{ @@ -140,12 +141,52 @@ @end +@interface ASTableViewFilledDelegate : NSObject +@end + +@implementation ASTableViewFilledDelegate + +- (ASSizeRange)tableView:(ASTableView *)tableView constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return ASSizeRangeMakeExactSize(CGSizeMake(10, 42)); +} + +@end + @interface ASTableViewTests : XCTestCase -@property (atomic, retain) ASTableView *testTableView; +@property (nonatomic, retain) ASTableView *testTableView; @end @implementation ASTableViewTests +- (void)testConstrainedSizeForRowAtIndexPath +{ + // Initial width of the table view is non-zero and all nodes are measured with this size. + // Any subsequence size change must trigger a relayout. + // Width and height are swapped so that a later size change will simulate a rotation + ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 400) + style:UITableViewStylePlain]; + + ASTableViewFilledDelegate *delegate = [ASTableViewFilledDelegate new]; + ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; + + tableView.asyncDelegate = delegate; + tableView.asyncDataSource = dataSource; + + [tableView reloadDataImmediately]; + [tableView setNeedsLayout]; + [tableView layoutIfNeeded]; + + for (int section = 0; section < NumberOfSections; section++) { + for (int row = 0; row < NumberOfRowsPerSection; row++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; + CGRect rect = [tableView rectForRowAtIndexPath:indexPath]; + XCTAssertEqual(rect.size.width, 100); // specified width should be ignored for table + XCTAssertEqual(rect.size.height, 42); + } + } +} + // TODO: Convert this to ARC. - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { @@ -408,8 +449,7 @@ { CGSize tableViewSize = CGSizeMake(100, 500); ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) - style:UITableViewStylePlain - asyncDataFetching:YES]; + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; tableView.asyncDelegate = dataSource; @@ -479,4 +519,14 @@ }]; } +/** + * This may seem silly, but we had issues where the runtime sometimes wouldn't correctly report + * conformances declared on categories. + */ +- (void)testThatTableNodeConformsToExpectedProtocols +{ + ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain]; + XCTAssert([node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]); +} + @end diff --git a/AsyncDisplayKitTests/ASTableViewThrashTests.m b/AsyncDisplayKitTests/ASTableViewThrashTests.m index 5d6fa8f637..90da7eb5bf 100644 --- a/AsyncDisplayKitTests/ASTableViewThrashTests.m +++ b/AsyncDisplayKitTests/ASTableViewThrashTests.m @@ -8,6 +8,7 @@ @import XCTest; #import +#import "ASTableViewInternal.h" // Set to 1 to use UITableView and see if the issue still exists. #define USE_UIKIT_REFERENCE 0 @@ -19,8 +20,8 @@ #define TableView ASTableView #endif -#define kInitialSectionCount 20 -#define kInitialItemCount 20 +#define kInitialSectionCount 10 +#define kInitialItemCount 10 #define kMinimumItemCount 5 #define kMinimumSectionCount 3 #define kFickleness 0.1 @@ -30,7 +31,7 @@ static NSString *ASThrashArrayDescription(NSArray *array) { NSMutableString *str = [NSMutableString stringWithString:@"(\n"]; NSInteger i = 0; for (id obj in array) { - [str appendFormat:@"\t[%ld]: \"%@\",\n", i, obj]; + [str appendFormat:@"\t[%ld]: \"%@\",\n", (long)i, obj]; i += 1; } [str appendString:@")"]; @@ -145,7 +146,7 @@ static volatile int32_t ASThrashTestSectionNextID = 1; } - (NSString *)description { - return [NSString stringWithFormat:@"
", (unsigned long)_sectionID, (unsigned long)self.items.count]; + return [NSString stringWithFormat:@"
", (unsigned long)_sectionID, (unsigned long)self.items.count, ASThrashArrayDescription(self.items)]; } - (id)copyWithZone:(NSZone *)zone { @@ -445,23 +446,29 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1; @implementation ASTableViewThrashTests { // The current update, which will be logged in case of a failure. ASThrashUpdate *_update; + BOOL _failed; } #pragma mark Overrides - (void)tearDown { + if (_failed && _update != nil) { + NSLog(@"Failed update %@: %@", _update, _update.logFriendlyBase64Representation); + } + _failed = NO; _update = nil; } // NOTE: Despite the documentation, this is not always called if an exception is caught. - (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected { - [self logCurrentUpdateIfNeeded]; + _failed = YES; [super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected]; } #pragma mark Test Methods -- (void)testInitialDataRead { +// Disabled temporarily due to issue where cell nodes are not marked invisible before deallocation. +- (void)DISABLED_testInitialDataRead { ASThrashDataSource *ds = [[ASThrashDataSource alloc] initWithData:[ASThrashTestSection sectionsWithCount:kInitialSectionCount]]; [self verifyDataSource:ds]; } @@ -477,10 +484,12 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1; } ASThrashDataSource *ds = [[ASThrashDataSource alloc] initWithData:_update.oldData]; + ds.tableView.test_enableSuperUpdateCallLogging = YES; [self applyUpdate:_update toDataSource:ds]; [self verifyDataSource:ds]; } +// Disabled temporarily due to issue where cell nodes are not marked invisible before deallocation. - (void)DISABLED_testThrashingWildly { for (NSInteger i = 0; i < kThrashingIterationCount; i++) { [self setUp]; @@ -495,12 +504,6 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1; #pragma mark Helpers -- (void)logCurrentUpdateIfNeeded { - if (_update != nil) { - NSLog(@"Failed update %@: %@", _update, _update.logFriendlyBase64Representation); - } -} - - (void)applyUpdate:(ASThrashUpdate *)update toDataSource:(ASThrashDataSource *)dataSource { TableView *tableView = dataSource.tableView; @@ -533,7 +536,7 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1; [tableView waitUntilAllUpdatesAreCommitted]; #endif } @catch (NSException *exception) { - [self logCurrentUpdateIfNeeded]; + _failed = YES; @throw exception; } } @@ -553,7 +556,7 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1; XCTAssertEqual([tableView rectForRowAtIndexPath:indexPath].size.height, item.rowHeight); #else ASThrashTestNode *node = (ASThrashTestNode *)[tableView nodeForRowAtIndexPath:indexPath]; - XCTAssertEqual(node.item, item); + XCTAssertEqualObjects(node.item, item, @"Wrong node at index path %@", indexPath); #endif } } diff --git a/AsyncDisplayKitTests/ASTextNodeTests.m b/AsyncDisplayKitTests/ASTextNodeTests.m index 6990b786d5..e928a22523 100644 --- a/AsyncDisplayKitTests/ASTextNodeTests.m +++ b/AsyncDisplayKitTests/ASTextNodeTests.m @@ -125,7 +125,7 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta) for (NSInteger i = 10; i < 500; i += 50) { CGSize constrainedSize = CGSizeMake(i, i); CGSize calculatedSize = [_textNode measure:constrainedSize]; - CGSize recalculatedSize = [_textNode measure:calculatedSize]; + CGSize recalculatedSize = [_textNode measure:constrainedSize]; XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 4.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize)); } @@ -136,7 +136,7 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta) for (CGFloat i = 10; i < 500; i *= 1.3) { CGSize constrainedSize = CGSizeMake(i, i); CGSize calculatedSize = [_textNode measure:constrainedSize]; - CGSize recalculatedSize = [_textNode measure:calculatedSize]; + CGSize recalculatedSize = [_textNode measure:constrainedSize]; XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 11.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize)); } diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index f7c4a6b079..63960005d1 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -37,11 +37,11 @@ } -@property (atomic, readwrite) ASInterfaceState interfaceState; -@property (atomic, readonly) ASDisplayNode *spinner; -@property (atomic, readwrite) ASDisplayNode *playerNode; -@property (atomic, readwrite) AVPlayer *player; -@property (atomic, readwrite) BOOL shouldBePlaying; +@property (nonatomic, readwrite) ASInterfaceState interfaceState; +@property (nonatomic, readonly) ASDisplayNode *spinner; +@property (nonatomic, readwrite) ASDisplayNode *playerNode; +@property (nonatomic, readwrite) AVPlayer *player; +@property (nonatomic, readwrite) BOOL shouldBePlaying; - (void)setVideoPlaceholderImage:(UIImage *)image; - (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys; diff --git a/AsyncDisplayKitTests/ASWeakMapTests.m b/AsyncDisplayKitTests/ASWeakMapTests.m new file mode 100644 index 0000000000..9f49457797 --- /dev/null +++ b/AsyncDisplayKitTests/ASWeakMapTests.m @@ -0,0 +1,57 @@ +// +// ASWeakMapTests.m +// AsyncDisplayKit +// +// Created by Chris Danford on 7/23/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 +#import "ASWeakMap.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ASWeakMapTests : XCTestCase + +@end + +@implementation ASWeakMapTests + +- (void)testKeyAndValueAreReleasedWhenEntryIsReleased +{ + ASWeakMap *weakMap = [[ASWeakMap alloc] init]; + + __weak NSObject *weakKey; + __weak NSObject *weakValue; + @autoreleasepool { + NSObject *key = [[NSObject alloc] init]; + NSObject *value = [[NSObject alloc] init]; + ASWeakMapEntry *entry = [weakMap setObject:value forKey:key]; + XCTAssertEqual([weakMap entryForKey:key], entry); + + weakKey = key; + weakValue = value; +} + XCTAssertEqual(weakKey, nil); + XCTAssertEqual(weakValue, nil); +} + +- (void)testKeyEquality +{ + ASWeakMap *weakMap = [[ASWeakMap alloc] init]; + NSString *keyA = @"key"; + NSString *keyB = [keyA copy]; // `isEqual` but not pointer equal + NSObject *value = [[NSObject alloc] init]; + + ASWeakMapEntry *entryA = [weakMap setObject:value forKey:keyA]; + ASWeakMapEntry *entryB = [weakMap entryForKey:keyB]; + XCTAssertEqual(entryA, entryB); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKitTests/TestResources/ASThrashTestRecordedCase b/AsyncDisplayKitTests/TestResources/ASThrashTestRecordedCase index 9e8343590e..e9dc1e9cc0 100644 --- a/AsyncDisplayKitTests/TestResources/ASThrashTestRecordedCase +++ b/AsyncDisplayKitTests/TestResources/ASThrashTestRecordedCase @@ -1 +1 @@ -YnBsaXN0MDDUAAEAAgADAAQABQAGDfYN91gkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRvcBIAAYagrxEDRQAHAAgADwAtAC4ALwAwADEAMgAzADQANQA2ADcAOAA5AEAARgBdAGEAaABrAG4AcQB0AHcAegB9AIAAgwCGAIkAjACPAJIAlQCYAJsAngChAKYAqgCuAMUAyADLAM4A0QDUANcA2gDdAOAA4wDmAOkA7ADvAPIA9QD4APsA/gEBAQUBHAEfASIBJQEoASsBLgExATQBNwE6AT0BQAFDAUYBSQFMAU8BUgFVAVgBXAFzAXYBeQF8AX8BggGFAYgBiwGOAZEBlAGXAZoBnQGgAaMBpgGpAawBrwG1AbkBvgHDAdsB4AHjAeYB6AHqAewB7wHyAfQB9gH5AfwB/wICAgUCBwIKAg0CDwITAhYCGAIaAhwCIAIjAiYCKQIsAkMCSAJLAk4CUwJWAlkCXQJgAmQCZwJsAm8CcgJ4AnsCfgKBAoUCiAKNApACkwKXApoCngKhAqYCqQKsArECtAK3Ar0CwALDAsYCywLOAtEC2ALbAt4C4QLkAu4C8QL0AvcC+gL9AwADAwMHAwoDEAMTAxYDGQMeAyEDJAMnAz4DQgNZA1wDXwNiA2UDaANrA24DcQN0A3cDegN9A4ADgwOGA4kDjAOPA5IDlQOZA7ADswO2A7kDvAO/A8IDxQPIA8sDzgPRA9QD1wPaA90D4APjA+YD6QPsA/AEBwQKBA0EEAQTBBYEGQQcBB8EIgQlBCgEKwQuBDEENAQ3BDoEPQRABEMERwReBGEEZARnBGoEbQRwBHMEdgR5BHwEfwSCBIUEiASLBI4EkQSUBJcEmgSeBLUEuAS7BL4EwQTEBMcEygTNBNAE0wTWBNkE3ATfBOIE5QToBOsE7gTxBPUFDAUPBRIFFQUYBRsFHgUhBSQFJwUqBS0FMAUzBTYFOQU8BT8FQgVFBUgFTAVjBWYFaQVsBW8FcgV1BXgFewV+BYEFhAWHBYoFjQWQBZMFlgWZBZwFnwWjBboFvQXABcMFxgXJBcwFzwXSBdUF2AXbBd4F4QXkBecF6gXtBfAF8wX2BfoGEQYUBhcGGgYdBiAGIwYmBikGLAYvBjIGNQY4BjsGPgZBBkQGRwZKBk0GUQZoBmsGbgZxBnQGdwZ6Bn0GgAaDBoYGiQaMBo8GkgaVBpgGmwaeBqEGpAaoBr8GwgbFBsgGywbOBtEG1AbXBtoG3QbgBuMG5gbpBuwG7wbyBvUG+Ab7Bv8HFgcZBxwHHwciByUHKAcrBy4HMQc0BzcHOgc9B0AHQwdGB0kHTAdPB1IHVgdtB3AHcwd2B3kHfAd/B4IHhQeIB4sHjgeRB5QHlweaB50HoAejB6YHqQetB8QHxwfKB80H0AfTB9YH2QfcB98H4gflB+gH6wfuB/EH9Af3B/oH/QgACAQIGwgeCCEIJAgnCCoILQgwCDMINgg5CDwIPwhCCEUISAhLCE4IUQhUCFcIWwhyCHUIeAh7CH4IgQiECIcIigiNCJAIkwiWCJkInAifCKIIpQioCKsIrgiyCMkIzAjPCNII1QjYCNsI3gjhCOQI5wjqCO0I8AjzCPYI+Qj8CP8JAgkFCQkJIAkjCSYJKQksCS8JMgk1CTgJOwk+CUEJRAlHCUoJTQlQCVMJVglZCVwJYAl3CXoJfQmACYMJhgmJCYwJjwmSCZUJmAmbCZ4JoQmkCacJqgmtCbAJswm3Cc4J0QnUCdcJ2gndCeAJ4wnmCekJ7AnvCfIJ9Qn4CfsJ/goBCgQKBwoKCiIKJQo8Cj8KQgpcCl8KYgplCmgKfQqACpUKmAqwCrMKtgq6Cs0K0ArTCtYK2QrcCt8K4grlCugK6wruCvEK9Ar3CvoK/QsBCxcLGgsdCyALIwsmCykLLAsvCzILNQs4CzsLPgtBC0QLRwtKC00LUAtTC2wLbwtyC3ULjAuPC5ILrAuvC7ILtQu4C8wLzwvmC+kL7AvvDAgMCwwODBEMFQwoDCsMLgwxDDQMNww6DD0MQAxDDEYMSQxMDE8MUgxVDFgMWwxyDHUMeAx7DH4MgQyWDJkMnAyfDLYMuQy8DL8M1gzYDNsM3QzgDOMM5gzpDOwM7gzwDPIM9Az2DPgM+gz9DQANAw0GDQkNCw0ODRENFA0XDRoNMQ0zDTYNOQ08DT8NQg1FDUgNSw1NDU8NUQ1TDVYNWQ1cDV8NYQ1kDWcNag1tDXANcw11DXgNew1+DYENgw2bDZ8NpQ2oDaoNrQ2wDbUNug2+DcQNxw3MDdIN2Q3eDeIN5Q3oDe4N8lUkbnVsbNMACQAKAAsADAANAA5VX2RpY3RYX3ZlcnNpb25WJGNsYXNzgAIQAYEDRNMAEAARAAsAEgAfACxXTlMua2V5c1pOUy5vYmplY3RzrAATABQAFQAWABcAGAAZABoAGwAcAB0AHoADgASABYAGgAeACIAJgAqAC4AMgA2ADqwAIAAhACIAIwAkACUAJgAnACgAKQAqACuAD4BrgG+AjYDMgQKFgQLzgQL1gQMQgQMvgQNAgQNCgQNDXxAQaW5zZXJ0ZWRTZWN0aW9uc18QFnJlcGxhY2VkU2VjdGlvbkluZGV4ZXNfEBNpbnNlcnRlZEl0ZW1JbmRleGVzXnJlcGxhY2luZ0l0ZW1zV29sZERhdGFUZGF0YV8QFmluc2VydGVkU2VjdGlvbkluZGV4ZXNfEBJkZWxldGVkSXRlbUluZGV4ZXNfEBNyZXBsYWNlZEl0ZW1JbmRleGVzXWluc2VydGVkSXRlbXNfEBVkZWxldGVkU2VjdGlvbkluZGV4ZXNfEBFyZXBsYWNpbmdTZWN0aW9uc9IAEQALADoAP6QAOwA8AD0APoAQgCmAP4BVgCfTAEEAQgALAEMARABFVWl0ZW1zWXNlY3Rpb25JRIAREKiAKNIAEQALAEcAP68QFABIAEkASgBLAEwATQBOAE8AUABRAFIAUwBUAFUAVgBXAFgAWQBaAFuAEoAUgBWAFoAXgBiAGYAagBuAHIAdgB6AH4AggCGAIoAjgCSAJYAmgCfSAF4ACwBfAGBWaXRlbUlEEQQSgBPSAGIAYwBkAGVaJGNsYXNzbmFtZVgkY2xhc3Nlc18QEEFTVGhyYXNoVGVzdEl0ZW2iAGYAZ18QEEFTVGhyYXNoVGVzdEl0ZW1YTlNPYmplY3TSAF4ACwBpAGARBBOAE9IAXgALAGwAYBEEFIAT0gBeAAsAbwBgEQQVgBPSAF4ACwByAGARBBaAE9IAXgALAHUAYBEEF4AT0gBeAAsAeABgEQQYgBPSAF4ACwB7AGARBBmAE9IAXgALAH4AYBEEGoAT0gBeAAsAgQBgEQQbgBPSAF4ACwCEAGARBByAE9IAXgALAIcAYBEEHYAT0gBeAAsAigBgEQQegBPSAF4ACwCNAGARBB+AE9IAXgALAJAAYBEEIIAT0gBeAAsAkwBgEQQhgBPSAF4ACwCWAGARBCKAE9IAXgALAJkAYBEEI4AT0gBeAAsAnABgEQQkgBPSAF4ACwCfAGARBCWAE9IAYgBjAKIAo15OU011dGFibGVBcnJheaMApAClAGdeTlNNdXRhYmxlQXJyYXlXTlNBcnJhedIAYgBjAKcAqF8QE0FTVGhyYXNoVGVzdFNlY3Rpb26iAKkAZ18QE0FTVGhyYXNoVGVzdFNlY3Rpb27TAEEAQgALAKsArABFgCoQqYAo0gARAAsArwA/rxAUALAAsQCyALMAtAC1ALYAtwC4ALkAugC7ALwAvQC+AL8AwADBAMIAw4ArgCyALYAugC+AMIAxgDKAM4A0gDWANoA3gDiAOYA6gDuAPIA9gD6AJ9IAXgALAMYAYBEEJoAT0gBeAAsAyQBgEQQngBPSAF4ACwDMAGARBCiAE9IAXgALAM8AYBEEKYAT0gBeAAsA0gBgEQQqgBPSAF4ACwDVAGARBCuAE9IAXgALANgAYBEELIAT0gBeAAsA2wBgEQQtgBPSAF4ACwDeAGARBC6AE9IAXgALAOEAYBEEL4AT0gBeAAsA5ABgEQQwgBPSAF4ACwDnAGARBDGAE9IAXgALAOoAYBEEMoAT0gBeAAsA7QBgEQQzgBPSAF4ACwDwAGARBDSAE9IAXgALAPMAYBEENYAT0gBeAAsA9gBgEQQ2gBPSAF4ACwD5AGARBDeAE9IAXgALAPwAYBEEOIAT0gBeAAsA/wBgEQQ5gBPTAEEAQgALAQIBAwBFgEAQqoAo0gARAAsBBgA/rxAUAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYARkBGoBBgEKAQ4BEgEWARoBHgEiASYBKgEuATIBNgE6AT4BQgFGAUoBTgFSAJ9IAXgALAR0AYBEEOoAT0gBeAAsBIABgEQQ7gBPSAF4ACwEjAGARBDyAE9IAXgALASYAYBEEPYAT0gBeAAsBKQBgEQQ+gBPSAF4ACwEsAGARBD+AE9IAXgALAS8AYBEEQIAT0gBeAAsBMgBgEQRBgBPSAF4ACwE1AGARBEKAE9IAXgALATgAYBEEQ4AT0gBeAAsBOwBgEQREgBPSAF4ACwE+AGARBEWAE9IAXgALAUEAYBEERoAT0gBeAAsBRABgEQRHgBPSAF4ACwFHAGARBEiAE9IAXgALAUoAYBEESYAT0gBeAAsBTQBgEQRKgBPSAF4ACwFQAGARBEuAE9IAXgALAVMAYBEETIAT0gBeAAsBVgBgEQRNgBPTAEEAQgALAVkBWgBFgFYQq4Ao0gARAAsBXQA/rxAUAV4BXwFgAWEBYgFjAWQBZQFmAWcBaAFpAWoBawFsAW0BbgFvAXABcYBXgFiAWYBagFuAXIBdgF6AX4BggGGAYoBjgGSAZYBmgGeAaIBpgGqAJ9IAXgALAXQAYBEEToAT0gBeAAsBdwBgEQRPgBPSAF4ACwF6AGARBFCAE9IAXgALAX0AYBEEUYAT0gBeAAsBgABgEQRSgBPSAF4ACwGDAGARBFOAE9IAXgALAYYAYBEEVIAT0gBeAAsBiQBgEQRVgBPSAF4ACwGMAGARBFaAE9IAXgALAY8AYBEEV4AT0gBeAAsBkgBgEQRYgBPSAF4ACwGVAGARBFmAE9IAXgALAZgAYBEEWoAT0gBeAAsBmwBgEQRbgBPSAF4ACwGeAGARBFyAE9IAXgALAaEAYBEEXYAT0gBeAAsBpABgEQRegBPSAF4ACwGnAGARBF+AE9IAXgALAaoAYBEEYIAT0gBeAAsBrQBgEQRhgBPTAbAACwGxAbIBswG0XE5TUmFuZ2VDb3VudFtOU1JhbmdlRGF0YRACgG6AbNIBtgALAbcBuFdOUy5kYXRhRAYCEAGAbdIAYgBjAboBu11OU011dGFibGVEYXRhowG8Ab0AZ11OU011dGFibGVEYXRhVk5TRGF0YdIAYgBjAb8BwF8QEU5TTXV0YWJsZUluZGV4U2V0owHBAcIAZ18QEU5TTXV0YWJsZUluZGV4U2V0Wk5TSW5kZXhTZXTSABEACwHEAD+vEBUBxQHGAccByAHJAcoBywHMAc0BzgHPAdAB0QHSAdMB1AHVAdYB1wHYAdmAcIBxgHOAdIB1gHaAeIB5gHqAfIB9gH+AgICCgIOAhYCGgIeAiICKgIyAJ9QB3AALAbAB3QHeAbMADQANWk5TTG9jYXRpb25YTlNMZW5ndGgQAIBu0wGwAAsBsQGyAbMB4oBugHLSAbYACwHkAbhEAgEUAoBt0gGwAAsB3gGzgG7SAbAACwHeAbOAbtIBsAALAd4Bs4Bu0wGwAAsBsQGyAbMB7oBugHfSAbYACwHwAbhEEAETAYBt0gGwAAsB3gGzgG7SAbAACwHeAbOAbtMBsAALAbEBsgGzAfiAboB70gG2AAsB+gG4RAMBDAGAbdQB3AALAbAB3QH9AbMADQANEAeAbtMBsAALAbEBsgGzAgGAboB+0gG2AAsCAwG4RAwCFAGAbdIBsAALAd4Bs4Bu0wGwAAsBsQGyAbMCCYBugIHSAbYACwILAbhEDAEOAYBt0gGwAAsB3gGzgG7TAbAACwGxAhABswISEAOAboCE0gG2AAsCFAG4RgIBDwERAYBt0gGwAAsB3gGzgG7SAbAACwHeAbOAbtIBsAALAd4Bs4Bu0wGwAAsBsQIdAbMCHxAEgG6AidIBtgALAiEBuEgAAQIBBwEJAYBt0wGwAAsBsQGyAbMCJYBugIvSAbYACwInAbhEBwEKAYBt1AHcAAsBsAHdAioBswANAA0QBYBu0gARAAsCLQA/rxAUAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQYCOgJGAlICWgJiAm4CfgKGApICmgKiAq4CugLKAtYC6gMKAxIDIgMuAJ9IAEQALAkQAP6ICRQJGgI+AkIAn0gBeAAsCSQBgEQOsgBPSAF4ACwJMAGARA62AE9IAEQALAk8AP6ICUAJRgJKAk4An0gBeAAsCVABgEQOugBPSAF4ACwJXAGARA6+AE9IAEQALAloAP6ECW4CVgCfSAF4ACwJeAGARA7CAE9IAEQALAmEAP6ECYoCXgCfSAF4ACwJlAGARA7GAE9IAEQALAmgAP6ICaQJqgJmAmoAn0gBeAAsCbQBgEQOygBPSAF4ACwJwAGARA7OAE9IAEQALAnMAP6MCdAJ1AnaAnICdgJ6AJ9IAXgALAnkAYBEDtIAT0gBeAAsCfABgEQO1gBPSAF4ACwJ/AGARA7aAE9IAEQALAoIAP6ECg4CggCfSAF4ACwKGAGARA7eAE9IAEQALAokAP6ICigKLgKKAo4An0gBeAAsCjgBgEQO4gBPSAF4ACwKRAGARA7mAE9IAEQALApQAP6EClYClgCfSAF4ACwKYAGARA7qAE9IAEQALApsAP6ECnICngCfSAF4ACwKfAGARA7uAE9IAEQALAqIAP6ICowKkgKmAqoAn0gBeAAsCpwBgEQO8gBPSAF4ACwKqAGARA72AE9IAEQALAq0AP6ICrgKvgKyArYAn0gBeAAsCsgBgEQO+gBPSAF4ACwK1AGARA7+AE9IAEQALArgAP6MCuQK6AruAr4CwgLGAJ9IAXgALAr4AYBEDwIAT0gBeAAsCwQBgEQPBgBPSAF4ACwLEAGARA8KAE9IAEQALAscAP6ICyALJgLOAtIAn0gBeAAsCzABgEQPDgBPSAF4ACwLPAGARA8SAE9IAEQALAtIAP6QC0wLUAtUC1oC2gLeAuIC5gCfSAF4ACwLZAGARA8WAE9IAXgALAtwAYBEDxoAT0gBeAAsC3wBgEQPHgBPSAF4ACwLiAGARA8iAE9IAEQALAuUAP6cC5gLnAugC6QLqAusC7IC7gLyAvYC+gL+AwIDBgCfSAF4ACwLvAGARA8mAE9IAXgALAvIAYBEDyoAT0gBeAAsC9QBgEQPLgBPSAF4ACwL4AGARA8yAE9IAXgALAvsAYBEDzYAT0gBeAAsC/gBgEQPOgBPSAF4ACwMBAGARA8+AE9IAEQALAwQAP6EDBYDDgCfSAF4ACwMIAGARA9CAE9IAEQALAwsAP6MDDAMNAw6AxYDGgMeAJ9IAXgALAxEAYBED0YAT0gBeAAsDFABgEQPSgBPSAF4ACwMXAGARA9OAE9IAEQALAxoAP6IDGwMcgMmAyoAn0gBeAAsDHwBgEQPUgBPSAF4ACwMiAGARA9WAE9IAEQALAyUAP6CAJ9IAEQALAygAP68QFAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzyAzYDjgPmBAQ+BASWBATuBAVGBAWeBAX2BAZOBAamBAb+BAdWBAeuBAgGBAheBAi2BAkOBAlmBAm+AJ9MAQQBCAAsDPwNAAEWAzhBVgCjSABEACwNDAD+vEBQDRANFA0YDRwNIA0kDSgNLA0wDTQNOA08DUANRA1IDUwNUA1UDVgNXgM+A0IDRgNKA04DUgNWA1oDXgNiA2YDagNuA3IDdgN6A34DggOGA4oAn0gBeAAsDWgBgEQIcgBPSAF4ACwNdAGARAh2AE9IAXgALA2AAYBECHoAT0gBeAAsDYwBgEQIfgBPSAF4ACwNmAGARAiCAE9IAXgALA2kAYBECIYAT0gBeAAsDbABgEQIigBPSAF4ACwNvAGARAiOAE9IAXgALA3IAYBECJIAT0gBeAAsDdQBgEQIlgBPSAF4ACwN4AGARAiaAE9IAXgALA3sAYBECJ4AT0gBeAAsDfgBgEQIogBPSAF4ACwOBAGARAimAE9IAXgALA4QAYBECKoAT0gBeAAsDhwBgEQIrgBPSAF4ACwOKAGARAiyAE9IAXgALA40AYBECLYAT0gBeAAsDkABgEQIugBPSAF4ACwOTAGARAi+AE9MAQQBCAAsDlgOXAEWA5BBWgCjSABEACwOaAD+vEBQDmwOcA50DngOfA6ADoQOiA6MDpAOlA6YDpwOoA6kDqgOrA6wDrQOugOWA5oDngOiA6YDqgOuA7IDtgO6A74DwgPGA8oDzgPSA9YD2gPeA+IAn0gBeAAsDsQBgEQIwgBPSAF4ACwO0AGARAjGAE9IAXgALA7cAYBECMoAT0gBeAAsDugBgEQIzgBPSAF4ACwO9AGARAjSAE9IAXgALA8AAYBECNYAT0gBeAAsDwwBgEQI2gBPSAF4ACwPGAGARAjeAE9IAXgALA8kAYBECOIAT0gBeAAsDzABgEQI5gBPSAF4ACwPPAGARAjqAE9IAXgALA9IAYBECO4AT0gBeAAsD1QBgEQI8gBPSAF4ACwPYAGARAj2AE9IAXgALA9sAYBECPoAT0gBeAAsD3gBgEQI/gBPSAF4ACwPhAGARAkCAE9IAXgALA+QAYBECQYAT0gBeAAsD5wBgEQJCgBPSAF4ACwPqAGARAkOAE9MAQQBCAAsD7QPuAEWA+hBXgCjSABEACwPxAD+vEBQD8gPzA/QD9QP2A/cD+AP5A/oD+wP8A/0D/gP/BAAEAQQCBAMEBAQFgPuA/ID9gP6A/4EBAIEBAYEBAoEBA4EBBIEBBYEBBoEBB4EBCIEBCYEBCoEBC4EBDIEBDYEBDoAn0gBeAAsECABgEQJEgBPSAF4ACwQLAGARAkWAE9IAXgALBA4AYBECRoAT0gBeAAsEEQBgEQJHgBPSAF4ACwQUAGARAkiAE9IAXgALBBcAYBECSYAT0gBeAAsEGgBgEQJKgBPSAF4ACwQdAGARAkuAE9IAXgALBCAAYBECTIAT0gBeAAsEIwBgEQJNgBPSAF4ACwQmAGARAk6AE9IAXgALBCkAYBECT4AT0gBeAAsELABgEQJQgBPSAF4ACwQvAGARAlGAE9IAXgALBDIAYBECUoAT0gBeAAsENQBgEQJTgBPSAF4ACwQ4AGARAlSAE9IAXgALBDsAYBECVYAT0gBeAAsEPgBgEQJWgBPSAF4ACwRBAGARAleAE9MAQQBCAAsERARFAEWBARAQWIAo0gARAAsESAA/rxAUBEkESgRLBEwETQROBE8EUARRBFIEUwRUBFUEVgRXBFgEWQRaBFsEXIEBEYEBEoEBE4EBFIEBFYEBFoEBF4EBGIEBGYEBGoEBG4EBHIEBHYEBHoEBH4EBIIEBIYEBIoEBI4EBJIAn0gBeAAsEXwBgEQJYgBPSAF4ACwRiAGARAlmAE9IAXgALBGUAYBECWoAT0gBeAAsEaABgEQJbgBPSAF4ACwRrAGARAlyAE9IAXgALBG4AYBECXYAT0gBeAAsEcQBgEQJegBPSAF4ACwR0AGARAl+AE9IAXgALBHcAYBECYIAT0gBeAAsEegBgEQJhgBPSAF4ACwR9AGARAmKAE9IAXgALBIAAYBECY4AT0gBeAAsEgwBgEQJkgBPSAF4ACwSGAGARAmWAE9IAXgALBIkAYBECZoAT0gBeAAsEjABgEQJngBPSAF4ACwSPAGARAmiAE9IAXgALBJIAYBECaYAT0gBeAAsElQBgEQJqgBPSAF4ACwSYAGARAmuAE9MAQQBCAAsEmwScAEWBASYQWYAo0gARAAsEnwA/rxAUBKAEoQSiBKMEpASlBKYEpwSoBKkEqgSrBKwErQSuBK8EsASxBLIEs4EBJ4EBKIEBKYEBKoEBK4EBLIEBLYEBLoEBL4EBMIEBMYEBMoEBM4EBNIEBNYEBNoEBN4EBOIEBOYEBOoAn0gBeAAsEtgBgEQJsgBPSAF4ACwS5AGARAm2AE9IAXgALBLwAYBECboAT0gBeAAsEvwBgEQJvgBPSAF4ACwTCAGARAnCAE9IAXgALBMUAYBECcYAT0gBeAAsEyABgEQJygBPSAF4ACwTLAGARAnOAE9IAXgALBM4AYBECdIAT0gBeAAsE0QBgEQJ1gBPSAF4ACwTUAGARAnaAE9IAXgALBNcAYBECd4AT0gBeAAsE2gBgEQJ4gBPSAF4ACwTdAGARAnmAE9IAXgALBOAAYBECeoAT0gBeAAsE4wBgEQJ7gBPSAF4ACwTmAGARAnyAE9IAXgALBOkAYBECfYAT0gBeAAsE7ABgEQJ+gBPSAF4ACwTvAGARAn+AE9MAQQBCAAsE8gTzAEWBATwQWoAo0gARAAsE9gA/rxAUBPcE+AT5BPoE+wT8BP0E/gT/BQAFAQUCBQMFBAUFBQYFBwUIBQkFCoEBPYEBPoEBP4EBQIEBQYEBQoEBQ4EBRIEBRYEBRoEBR4EBSIEBSYEBSoEBS4EBTIEBTYEBToEBT4EBUIAn0gBeAAsFDQBgEQKAgBPSAF4ACwUQAGARAoGAE9IAXgALBRMAYBECgoAT0gBeAAsFFgBgEQKDgBPSAF4ACwUZAGARAoSAE9IAXgALBRwAYBEChYAT0gBeAAsFHwBgEQKGgBPSAF4ACwUiAGARAoeAE9IAXgALBSUAYBECiIAT0gBeAAsFKABgEQKJgBPSAF4ACwUrAGARAoqAE9IAXgALBS4AYBECi4AT0gBeAAsFMQBgEQKMgBPSAF4ACwU0AGARAo2AE9IAXgALBTcAYBECjoAT0gBeAAsFOgBgEQKPgBPSAF4ACwU9AGARApCAE9IAXgALBUAAYBECkYAT0gBeAAsFQwBgEQKSgBPSAF4ACwVGAGARApOAE9MAQQBCAAsFSQVKAEWBAVIQW4Ao0gARAAsFTQA/rxAUBU4FTwVQBVEFUgVTBVQFVQVWBVcFWAVZBVoFWwVcBV0FXgVfBWAFYYEBU4EBVIEBVYEBVoEBV4EBWIEBWYEBWoEBW4EBXIEBXYEBXoEBX4EBYIEBYYEBYoEBY4EBZIEBZYEBZoAn0gBeAAsFZABgEQKUgBPSAF4ACwVnAGARApWAE9IAXgALBWoAYBECloAT0gBeAAsFbQBgEQKXgBPSAF4ACwVwAGARApiAE9IAXgALBXMAYBECmYAT0gBeAAsFdgBgEQKagBPSAF4ACwV5AGARApuAE9IAXgALBXwAYBECnIAT0gBeAAsFfwBgEQKdgBPSAF4ACwWCAGARAp6AE9IAXgALBYUAYBECn4AT0gBeAAsFiABgEQKggBPSAF4ACwWLAGARAqGAE9IAXgALBY4AYBECooAT0gBeAAsFkQBgEQKjgBPSAF4ACwWUAGARAqSAE9IAXgALBZcAYBECpYAT0gBeAAsFmgBgEQKmgBPSAF4ACwWdAGARAqeAE9MAQQBCAAsFoAWhAEWBAWgQXIAo0gARAAsFpAA/rxAUBaUFpgWnBagFqQWqBasFrAWtBa4FrwWwBbEFsgWzBbQFtQW2BbcFuIEBaYEBaoEBa4EBbIEBbYEBboEBb4EBcIEBcYEBcoEBc4EBdIEBdYEBdoEBd4EBeIEBeYEBeoEBe4EBfIAn0gBeAAsFuwBgEQKogBPSAF4ACwW+AGARAqmAE9IAXgALBcEAYBECqoAT0gBeAAsFxABgEQKrgBPSAF4ACwXHAGARAqyAE9IAXgALBcoAYBECrYAT0gBeAAsFzQBgEQKugBPSAF4ACwXQAGARAq+AE9IAXgALBdMAYBECsIAT0gBeAAsF1gBgEQKxgBPSAF4ACwXZAGARArKAE9IAXgALBdwAYBECs4AT0gBeAAsF3wBgEQK0gBPSAF4ACwXiAGARArWAE9IAXgALBeUAYBECtoAT0gBeAAsF6ABgEQK3gBPSAF4ACwXrAGARAriAE9IAXgALBe4AYBECuYAT0gBeAAsF8QBgEQK6gBPSAF4ACwX0AGARAruAE9MAQQBCAAsF9wX4AEWBAX4QXYAo0gARAAsF+wA/rxAUBfwF/QX+Bf8GAAYBBgIGAwYEBgUGBgYHBggGCQYKBgsGDAYNBg4GD4EBf4EBgIEBgYEBgoEBg4EBhIEBhYEBhoEBh4EBiIEBiYEBioEBi4EBjIEBjYEBjoEBj4EBkIEBkYEBkoAn0gBeAAsGEgBgEQK8gBPSAF4ACwYVAGARAr2AE9IAXgALBhgAYBECvoAT0gBeAAsGGwBgEQK/gBPSAF4ACwYeAGARAsCAE9IAXgALBiEAYBECwYAT0gBeAAsGJABgEQLCgBPSAF4ACwYnAGARAsOAE9IAXgALBioAYBECxIAT0gBeAAsGLQBgEQLFgBPSAF4ACwYwAGARAsaAE9IAXgALBjMAYBECx4AT0gBeAAsGNgBgEQLIgBPSAF4ACwY5AGARAsmAE9IAXgALBjwAYBECyoAT0gBeAAsGPwBgEQLLgBPSAF4ACwZCAGARAsyAE9IAXgALBkUAYBECzYAT0gBeAAsGSABgEQLOgBPSAF4ACwZLAGARAs+AE9MAQQBCAAsGTgZPAEWBAZQQXoAo0gARAAsGUgA/rxAUBlMGVAZVBlYGVwZYBlkGWgZbBlwGXQZeBl8GYAZhBmIGYwZkBmUGZoEBlYEBloEBl4EBmIEBmYEBmoEBm4EBnIEBnYEBnoEBn4EBoIEBoYEBooEBo4EBpIEBpYEBpoEBp4EBqIAn0gBeAAsGaQBgEQLQgBPSAF4ACwZsAGARAtGAE9IAXgALBm8AYBEC0oAT0gBeAAsGcgBgEQLTgBPSAF4ACwZ1AGARAtSAE9IAXgALBngAYBEC1YAT0gBeAAsGewBgEQLWgBPSAF4ACwZ+AGARAteAE9IAXgALBoEAYBEC2IAT0gBeAAsGhABgEQLZgBPSAF4ACwaHAGARAtqAE9IAXgALBooAYBEC24AT0gBeAAsGjQBgEQLcgBPSAF4ACwaQAGARAt2AE9IAXgALBpMAYBEC3oAT0gBeAAsGlgBgEQLfgBPSAF4ACwaZAGARAuCAE9IAXgALBpwAYBEC4YAT0gBeAAsGnwBgEQLigBPSAF4ACwaiAGARAuOAE9MAQQBCAAsGpQamAEWBAaoQX4Ao0gARAAsGqQA/rxAUBqoGqwasBq0GrgavBrAGsQayBrMGtAa1BrYGtwa4BrkGuga7BrwGvYEBq4EBrIEBrYEBroEBr4EBsIEBsYEBsoEBs4EBtIEBtYEBtoEBt4EBuIEBuYEBuoEBu4EBvIEBvYEBvoAn0gBeAAsGwABgEQLkgBPSAF4ACwbDAGARAuWAE9IAXgALBsYAYBEC5oAT0gBeAAsGyQBgEQLngBPSAF4ACwbMAGARAuiAE9IAXgALBs8AYBEC6YAT0gBeAAsG0gBgEQLqgBPSAF4ACwbVAGARAuuAE9IAXgALBtgAYBEC7IAT0gBeAAsG2wBgEQLtgBPSAF4ACwbeAGARAu6AE9IAXgALBuEAYBEC74AT0gBeAAsG5ABgEQLwgBPSAF4ACwbnAGARAvGAE9IAXgALBuoAYBEC8oAT0gBeAAsG7QBgEQLzgBPSAF4ACwbwAGARAvSAE9IAXgALBvMAYBEC9YAT0gBeAAsG9gBgEQL2gBPSAF4ACwb5AGARAveAE9MAQQBCAAsG/Ab9AEWBAcAQYIAo0gARAAsHAAA/rxAUBwEHAgcDBwQHBQcGBwcHCAcJBwoHCwcMBw0HDgcPBxAHEQcSBxMHFIEBwYEBwoEBw4EBxIEBxYEBxoEBx4EByIEByYEByoEBy4EBzIEBzYEBzoEBz4EB0IEB0YEB0oEB04EB1IAn0gBeAAsHFwBgEQL4gBPSAF4ACwcaAGARAvmAE9IAXgALBx0AYBEC+oAT0gBeAAsHIABgEQL7gBPSAF4ACwcjAGARAvyAE9IAXgALByYAYBEC/YAT0gBeAAsHKQBgEQL+gBPSAF4ACwcsAGARAv+AE9IAXgALBy8AYBEDAIAT0gBeAAsHMgBgEQMBgBPSAF4ACwc1AGARAwKAE9IAXgALBzgAYBEDA4AT0gBeAAsHOwBgEQMEgBPSAF4ACwc+AGARAwWAE9IAXgALB0EAYBEDBoAT0gBeAAsHRABgEQMHgBPSAF4ACwdHAGARAwiAE9IAXgALB0oAYBEDCYAT0gBeAAsHTQBgEQMKgBPSAF4ACwdQAGARAwuAE9MAQQBCAAsHUwdUAEWBAdYQYYAo0gARAAsHVwA/rxAUB1gHWQdaB1sHXAddB14HXwdgB2EHYgdjB2QHZQdmB2cHaAdpB2oHa4EB14EB2IEB2YEB2oEB24EB3IEB3YEB3oEB34EB4IEB4YEB4oEB44EB5IEB5YEB5oEB54EB6IEB6YEB6oAn0gBeAAsHbgBgEQMMgBPSAF4ACwdxAGARAw2AE9IAXgALB3QAYBEDDoAT0gBeAAsHdwBgEQMPgBPSAF4ACwd6AGARAxCAE9IAXgALB30AYBEDEYAT0gBeAAsHgABgEQMSgBPSAF4ACweDAGARAxOAE9IAXgALB4YAYBEDFIAT0gBeAAsHiQBgEQMVgBPSAF4ACweMAGARAxaAE9IAXgALB48AYBEDF4AT0gBeAAsHkgBgEQMYgBPSAF4ACweVAGARAxmAE9IAXgALB5gAYBEDGoAT0gBeAAsHmwBgEQMbgBPSAF4ACweeAGARAxyAE9IAXgALB6EAYBEDHYAT0gBeAAsHpABgEQMegBPSAF4ACwenAGARAx+AE9MAQQBCAAsHqgerAEWBAewQYoAo0gARAAsHrgA/rxAUB68HsAexB7IHswe0B7UHtge3B7gHuQe6B7sHvAe9B74HvwfAB8EHwoEB7YEB7oEB74EB8IEB8YEB8oEB84EB9IEB9YEB9oEB94EB+IEB+YEB+oEB+4EB/IEB/YEB/oEB/4ECAIAn0gBeAAsHxQBgEQMggBPSAF4ACwfIAGARAyGAE9IAXgALB8sAYBEDIoAT0gBeAAsHzgBgEQMjgBPSAF4ACwfRAGARAySAE9IAXgALB9QAYBEDJYAT0gBeAAsH1wBgEQMmgBPSAF4ACwfaAGARAyeAE9IAXgALB90AYBEDKIAT0gBeAAsH4ABgEQMpgBPSAF4ACwfjAGARAyqAE9IAXgALB+YAYBEDK4AT0gBeAAsH6QBgEQMsgBPSAF4ACwfsAGARAy2AE9IAXgALB+8AYBEDLoAT0gBeAAsH8gBgEQMvgBPSAF4ACwf1AGARAzCAE9IAXgALB/gAYBEDMYAT0gBeAAsH+wBgEQMygBPSAF4ACwf+AGARAzOAE9MAQQBCAAsIAQgCAEWBAgIQY4Ao0gARAAsIBQA/rxAUCAYIBwgICAkICggLCAwIDQgOCA8IEAgRCBIIEwgUCBUIFggXCBgIGYECA4ECBIECBYECBoECB4ECCIECCYECCoECC4ECDIECDYECDoECD4ECEIECEYECEoECE4ECFIECFYECFoAn0gBeAAsIHABgEQM0gBPSAF4ACwgfAGARAzWAE9IAXgALCCIAYBEDNoAT0gBeAAsIJQBgEQM3gBPSAF4ACwgoAGARAziAE9IAXgALCCsAYBEDOYAT0gBeAAsILgBgEQM6gBPSAF4ACwgxAGARAzuAE9IAXgALCDQAYBEDPIAT0gBeAAsINwBgEQM9gBPSAF4ACwg6AGARAz6AE9IAXgALCD0AYBEDP4AT0gBeAAsIQABgEQNAgBPSAF4ACwhDAGARA0GAE9IAXgALCEYAYBEDQoAT0gBeAAsISQBgEQNDgBPSAF4ACwhMAGARA0SAE9IAXgALCE8AYBEDRYAT0gBeAAsIUgBgEQNGgBPSAF4ACwhVAGARA0eAE9MAQQBCAAsIWAhZAEWBAhgQZIAo0gARAAsIXAA/rxAUCF0IXghfCGAIYQhiCGMIZAhlCGYIZwhoCGkIaghrCGwIbQhuCG8IcIECGYECGoECG4ECHIECHYECHoECH4ECIIECIYECIoECI4ECJIECJYECJoECJ4ECKIECKYECKoECK4ECLIAn0gBeAAsIcwBgEQNIgBPSAF4ACwh2AGARA0mAE9IAXgALCHkAYBEDSoAT0gBeAAsIfABgEQNLgBPSAF4ACwh/AGARA0yAE9IAXgALCIIAYBEDTYAT0gBeAAsIhQBgEQNOgBPSAF4ACwiIAGARA0+AE9IAXgALCIsAYBEDUIAT0gBeAAsIjgBgEQNRgBPSAF4ACwiRAGARA1KAE9IAXgALCJQAYBEDU4AT0gBeAAsIlwBgEQNUgBPSAF4ACwiaAGARA1WAE9IAXgALCJ0AYBEDVoAT0gBeAAsIoABgEQNXgBPSAF4ACwijAGARA1iAE9IAXgALCKYAYBEDWYAT0gBeAAsIqQBgEQNagBPSAF4ACwisAGARA1uAE9MAQQBCAAsIrwiwAEWBAi4QZYAo0gARAAsIswA/rxAUCLQItQi2CLcIuAi5CLoIuwi8CL0Ivgi/CMAIwQjCCMMIxAjFCMYIx4ECL4ECMIECMYECMoECM4ECNIECNYECNoECN4ECOIECOYECOoECO4ECPIECPYECPoECP4ECQIECQYECQoAn0gBeAAsIygBgEQNcgBPSAF4ACwjNAGARA12AE9IAXgALCNAAYBEDXoAT0gBeAAsI0wBgEQNfgBPSAF4ACwjWAGARA2CAE9IAXgALCNkAYBEDYYAT0gBeAAsI3ABgEQNigBPSAF4ACwjfAGARA2OAE9IAXgALCOIAYBEDZIAT0gBeAAsI5QBgEQNlgBPSAF4ACwjoAGARA2aAE9IAXgALCOsAYBEDZ4AT0gBeAAsI7gBgEQNogBPSAF4ACwjxAGARA2mAE9IAXgALCPQAYBEDaoAT0gBeAAsI9wBgEQNrgBPSAF4ACwj6AGARA2yAE9IAXgALCP0AYBEDbYAT0gBeAAsJAABgEQNugBPSAF4ACwkDAGARA2+AE9MAQQBCAAsJBgkHAEWBAkQQZoAo0gARAAsJCgA/rxAUCQsJDAkNCQ4JDwkQCREJEgkTCRQJFQkWCRcJGAkZCRoJGwkcCR0JHoECRYECRoECR4ECSIECSYECSoECS4ECTIECTYECToECT4ECUIECUYECUoECU4ECVIECVYECVoECV4ECWIAn0gBeAAsJIQBgEQNwgBPSAF4ACwkkAGARA3GAE9IAXgALCScAYBEDcoAT0gBeAAsJKgBgEQNzgBPSAF4ACwktAGARA3SAE9IAXgALCTAAYBEDdYAT0gBeAAsJMwBgEQN2gBPSAF4ACwk2AGARA3eAE9IAXgALCTkAYBEDeIAT0gBeAAsJPABgEQN5gBPSAF4ACwk/AGARA3qAE9IAXgALCUIAYBEDe4AT0gBeAAsJRQBgEQN8gBPSAF4ACwlIAGARA32AE9IAXgALCUsAYBEDfoAT0gBeAAsJTgBgEQN/gBPSAF4ACwlRAGARA4CAE9IAXgALCVQAYBEDgYAT0gBeAAsJVwBgEQOCgBPSAF4ACwlaAGARA4OAE9MAQQBCAAsJXQleAEWBAloQZ4Ao0gARAAsJYQA/rxAUCWIJYwlkCWUJZglnCWgJaQlqCWsJbAltCW4JbwlwCXEJcglzCXQJdYECW4ECXIECXYECXoECX4ECYIECYYECYoECY4ECZIECZYECZoECZ4ECaIECaYECaoECa4ECbIECbYECboAn0gBeAAsJeABgEQOEgBPSAF4ACwl7AGARA4WAE9IAXgALCX4AYBEDhoAT0gBeAAsJgQBgEQOHgBPSAF4ACwmEAGARA4iAE9IAXgALCYcAYBEDiYAT0gBeAAsJigBgEQOKgBPSAF4ACwmNAGARA4uAE9IAXgALCZAAYBEDjIAT0gBeAAsJkwBgEQONgBPSAF4ACwmWAGARA46AE9IAXgALCZkAYBEDj4AT0gBeAAsJnABgEQOQgBPSAF4ACwmfAGARA5GAE9IAXgALCaIAYBEDkoAT0gBeAAsJpQBgEQOTgBPSAF4ACwmoAGARA5SAE9IAXgALCasAYBEDlYAT0gBeAAsJrgBgEQOWgBPSAF4ACwmxAGARA5eAE9MAQQBCAAsJtAm1AEWBAnAQaIAo0gARAAsJuAA/rxAUCbkJugm7CbwJvQm+Cb8JwAnBCcIJwwnECcUJxgnHCcgJyQnKCcsJzIECcYECcoECc4ECdIECdYECdoECd4ECeIECeYECeoECe4ECfIECfYECfoECf4ECgIECgYECgoECg4EChIAn0gBeAAsJzwBgEQOYgBPSAF4ACwnSAGARA5mAE9IAXgALCdUAYBEDmoAT0gBeAAsJ2ABgEQObgBPSAF4ACwnbAGARA5yAE9IAXgALCd4AYBEDnYAT0gBeAAsJ4QBgEQOegBPSAF4ACwnkAGARA5+AE9IAXgALCecAYBEDoIAT0gBeAAsJ6gBgEQOhgBPSAF4ACwntAGARA6KAE9IAXgALCfAAYBEDo4AT0gBeAAsJ8wBgEQOkgBPSAF4ACwn2AGARA6WAE9IAXgALCfkAYBEDpoAT0gBeAAsJ/ABgEQOngBPSAF4ACwn/AGARA6iAE9IAXgALCgIAYBEDqYAT0gBeAAsKBQBgEQOqgBPSAF4ACwoIAGARA6uAE9IAEQALCgsAP68QFQoMCg0AOwoPChAKEQoSChMKFAoVChYKFwoYADwKGgobAD0APgoeCh8KIIEChoECiYAQgQKOgQKQgQKSgQKWgQKogQK9gQLBgQLEgQLJgQLLgCmBAs+BAtSAP4BVgQLmgQLsgQLwgCfTAEEAQgALCiMDlwBFgQKHgCjSABEACwomAD+vEBQKJwObAlADnQOeA58DoAOhA6IDowOkA6UDpgOnA6kCUQOrA6wDrQOugQKIgOWAkoDngOiA6YDqgOuA7IDtgO6A74DwgPGA84CTgPWA9oD3gPiAJ9IAXgALCj0AYBEEYoAT0wBBAEIACwpAA+4ARYECioAo0gARAAsKQwA/rxAXA/ID8wpGA/QD9QP2A/cD+AP5A/oD+wJbA/0D/gP/BAAEAQQCBAMEBApYClkEBYD7gPyBAouA/YD+gP+BAQCBAQGBAQKBAQOBAQSAlYEBBoEBB4EBCIEBCYEBCoEBC4EBDIEBDYECjIECjYEBDoAn0gBeAAsKXQBgEQRjgBPSAF4ACwpgAGARBGSAE9IAXgALCmMAYBEEZYAT0wBBAEIACwpmBEUARYECj4Ao0gARAAsKaQA/rxASBEkESgRLBE0ETgJiBFAEUQRSBFMEVARVBFcEWARZBFoEWwRcgQERgQESgQETgQEVgQEWgJeBARiBARmBARqBARuBARyBAR2BAR+BASCBASGBASKBASOBASSAJ9MAQQBCAAsKfgScAEWBApGAKNIAEQALCoEAP68QEgSgAmkEowSkBKUEpgSnBKgEqQSqBKsErAStBK8EsASxAmoEs4EBJ4CZgQEqgQErgQEsgQEtgQEugQEvgQEwgQExgQEygQEzgQE0gQE2gQE3gQE4gJqBATqAJ9MAQQBCAAsKlgTzAEWBApOAKNIAEQALCpkAP68QFQT3BPgE+QT6AnQE/AJ1BP4E/wUAAnYFAgUDBQQFBgUHCqoFCAUJCq0FCoEBPYEBPoEBP4EBQICcgQFCgJ2BAUSBAUWBAUaAnoEBSIEBSYEBSoEBTIEBTYEClIEBToEBT4EClYEBUIAn0gBeAAsKsQBgEQRmgBPSAF4ACwq0AGARBGeAE9MAQQBCAAsKtwq4AEWBApcQpYAo0gARAAsKuwA/rxAQCrwKvQq+Cr8KwArBCsIKwwrECsUKxgrHCsgKyQrKCsuBApiBApmBApqBApuBApyBAp2BAp6BAp+BAqCBAqGBAqKBAqOBAqSBAqWBAqaBAqeAJ9IAXgALCs4AYBED1oAT0gBeAAsK0QBgEQPXgBPSAF4ACwrUAGARA9iAE9IAXgALCtcAYBED2oAT0gBeAAsK2gBgEQPbgBPSAF4ACwrdAGARA9yAE9IAXgALCuAAYBED3oAT0gBeAAsK4wBgEQPfgBPSAF4ACwrmAGARA+CAE9IAXgALCukAYBED4YAT0gBeAAsK7ABgEQPigBPSAF4ACwrvAGARA+SAE9IAXgALCvIAYBED5YAT0gBeAAsK9QBgEQPmgBPSAF4ACwr4AGARA+eAE9IAXgALCvsAYBED6IAT0wBBAEIACwr+Cv8ARYECqRCmgCjSABEACwsCAD+vEBMLAwsECwULBgsHCwgLCQsKCwsLDAsNCw4LDwsQCxELEgsTCxQLFYECqoECq4ECrIECrYECroECr4ECsIECsYECsoECs4ECtIECtYECtoECt4ECuIECuYECuoECu4ECvIAn0gBeAAsLGABgEQPqgBPSAF4ACwsbAGARA+uAE9IAXgALCx4AYBED7IAT0gBeAAsLIQBgEQPtgBPSAF4ACwskAGARA+6AE9IAXgALCycAYBED74AT0gBeAAsLKgBgEQPwgBPSAF4ACwstAGARA/GAE9IAXgALCzAAYBED8oAT0gBeAAsLMwBgEQPzgBPSAF4ACws2AGARA/SAE9IAXgALCzkAYBED9YAT0gBeAAsLPABgEQP2gBPSAF4ACws/AGARA/eAE9IAXgALC0IAYBED+IAT0gBeAAsLRQBgEQP5gBPSAF4ACwtIAGARA/uAE9IAXgALC0sAYBED/IAT0gBeAAsLTgBgEQP9gBPTAEEAQgALC1EF+ABFgQK+gCjSABEACwtUAD+vEBYF/AX9Bf4LWAX/BgAGAQYCApUGBAYFBgYLYQYHBggGCQYKBgsGDAYNBg4GD4EBf4EBgIEBgYECv4EBgoEBg4EBhIEBhYClgQGHgQGIgQGJgQLAgQGKgQGLgQGMgQGNgQGOgQGPgQGQgQGRgQGSgCfSAF4ACwttAGARBGiAE9IAXgALC3AAYBEEaYAT0wBBAEIACwtzBqYARYECwoAo0gARAAsLdgA/rxAUBqoGqwKjBq4GrwawBrELfgayBrMGtAa1BrYCpAa4BrkGuga7BrwGvYEBq4EBrICpgQGvgQGwgQGxgQGygQLDgQGzgQG0gQG1gQG2gQG3gKqBAbmBAbqBAbuBAbyBAb2BAb6AJ9IAXgALC40AYBEEaoAT0wBBAEIACwuQBv0ARYECxYAo0gARAAsLkwA/rxAXBwEHAgcDBwQHBQcGBwcHCAcJBwoHCwKuC6ALoQcNBw4HDwcQBxECrwuoBxMHFIEBwYEBwoEBw4EBxIEBxYEBxoEBx4EByIEByYEByoEBy4CsgQLGgQLHgQHNgQHOgQHPgQHQgQHRgK2BAsiBAdOBAdSAJ9IAXgALC60AYBEEa4AT0gBeAAsLsABgEQRsgBPSAF4ACwuzAGARBG2AE9MAQQBCAAsLtgerAEWBAsqAKNIAEQALC7kAP68QEQevB7AHsgezB7QHtQe2B7cHuALIB7sHvAe9B78HwALJB8KBAe2BAe6BAfCBAfGBAfKBAfOBAfSBAfWBAfaAs4EB+YEB+oEB+4EB/YEB/oC0gQIAgCfTAEEAQgALC80IAgBFgQLMgCjSABEACwvQAD+vEBQIBggHCAgICQgLAtMIDggPAtQIEQLVCBML3QgUC98IFQgWCBcIGALWgQIDgQIEgQIFgQIGgQIIgLaBAguBAgyAt4ECDoC4gQIQgQLNgQIRgQLOgQISgQITgQIUgQIVgLmAJ9IAXgALC+cAYBEEboAT0gBeAAsL6gBgEQRvgBPTAEEAQgALC+0IWQBFgQLQgCjSABEACwvwAD+vEBYIXQLmC/MIXwLnCGEIYghjCGQIZQhmAugC6QhpCGoMAAhrDAIIbALqAusC7IECGYC7gQLRgQIbgLyBAh2BAh6BAh+BAiCBAiGBAiKAvYC+gQIlgQImgQLSgQIngQLTgQIogL+AwIDBgCfSAF4ACwwJAGARBHCAE9IAXgALDAwAYBEEcYAT0gBeAAsMDwBgEQRygBPTAEEAQgALDBIMEwBFgQLVEKeAKNIAEQALDBYAP68QEAwXDBgMGQwaDBsMHAwdDB4MHwwgDCEMIgwjDCQMJQwmgQLWgQLXgQLYgQLZgQLagQLbgQLcgQLdgQLegQLfgQLggQLhgQLigQLjgQLkgQLlgCfSAF4ACwwpAGARA/+AE9IAXgALDCwAYBEEAYAT0gBeAAsMLwBgEQQCgBPSAF4ACwwyAGARBAOAE9IAXgALDDUAYBEEBIAT0gBeAAsMOABgEQQFgBPSAF4ACww7AGARBAiAE9IAXgALDD4AYBEECYAT0gBeAAsMQQBgEQQKgBPSAF4ACwxEAGARBAuAE9IAXgALDEcAYBEEDIAT0gBeAAsMSgBgEQQNgBPSAF4ACwxNAGARBA6AE9IAXgALDFAAYBEED4AT0gBeAAsMUwBgEQQQgBPSAF4ACwxWAGARBBGAE9MAQQBCAAsMWQkHAEWBAueAKNIAEQALDFwAP68QFAxdCQsMXwkMCQ0JDgMMDGQJEgxmCRMJFAkVCRYJFwMNCRkJGgMOCR6BAuiBAkWBAumBAkaBAkeBAkiAxYEC6oECTIEC64ECTYECToECT4ECUIECUYDGgQJTgQJUgMeBAliAJ9IAXgALDHMAYBEEc4AT0gBeAAsMdgBgEQR0gBPSAF4ACwx5AGARBHWAE9IAXgALDHwAYBEEdoAT0wBBAEIACwx/CV4ARYEC7YAo0gARAAsMggA/rxASCWIJYwMbCWUJZglnCWgMiglqCWsMjQlsCW4JbwlxCXIJcwMcgQJbgQJcgMmBAl6BAl+BAmCBAmGBAu6BAmOBAmSBAu+BAmWBAmeBAmiBAmqBAmuBAmyAyoAn0gBeAAsMlwBgEQR3gBPSAF4ACwyaAGARBHiAE9MAQQBCAAsMnQm1AEWBAvGAKNIAEQALDKAAP68QFAm5CboJuwm8Cb0Mpgm+Cb8JwAnBCcIJwwnECcUJxgnHCcgJyQnLCcyBAnGBAnKBAnOBAnSBAnWBAvKBAnaBAneBAniBAnmBAnqBAnuBAnyBAn2BAn6BAn+BAoCBAoGBAoOBAoSAJ9IAXgALDLcAYBEEeYAT0wGwAAsBsQIQAbMMu4BugQL00gG2AAsMvQG4RgIBDQEQAoBt0gARAAsMwAA/rxAUDMEMwgzDDMQMxQzGDMcMyAzJDMoMywzMDM0MzgzPDNAM0QzSDNMM1IEC9oEC94EC+IEC+YEC+4EC/YEC/oEC/4EDAIEDAYEDAoEDA4EDBIEDBYEDB4EDCYEDCoEDC4EDDYEDD4An0gGwAAsB3gGzgG7UAdwACwGwAd0M2QGzAA0ADRANgG7SAbAACwHeAbOAbtMBsAALAbEBsgGzDN+AboEC+tIBtgALDOEBuEQDAQ0BgG3TAbAACwGxAbIBswzlgG6BAvzSAbYACwznAbhEAgEOAYBt1AHcAAsBsAHdDOoBswANAA0QDoBu0gGwAAsB3gGzgG7SAbAACwHeAbOAbtIBsAALAd4Bs4Bu0gGwAAsB3gGzgG7UAdwACwGwAd0CEAGzAA0ADYBu0gGwAAsB3gGzgG7SAbAACwHeAbOAbtMBsAALAbECEAGzDPyAboEDBtIBtgALDP4BuEYCAQoBDwGAbdMBsAALAbEBsgGzDQKAboEDCNIBtgALDQQBuEQEAQYBgG3UAdwACwGwAd0NBwGzAA0ADRATgG7SAbAACwHeAbOAbtMBsAALAbECEAGzDQ2AboEDDNIBtgALDQ8BuEYEAQYBEQKAbdMBsAALAbECHQGzDROAboEDDtIBtgALDRUBuEgHAQsBDgESAYBt1AHcAAsBsAHdDRgBswANAA0QEYBu0gARAAsNGwA/rxAUDRwNHQ0eDR8NIA0hDSINIw0kDSUNJg0nDSgNKQ0qDSsNLA0tDS4NL4EDEYEDEoEDFIEDFYEDFoEDGIEDGoEDG4EDHIEDHYEDHoEDIIEDIoEDI4EDJYEDJ4EDKYEDKoEDLIEDLoAn0gGwAAsB3gGzgG7TAbAACwGxAbIBsw01gG6BAxPSAbYACw03AbhEAQEPAYBt1AHcAAsBsAHdDToBswANAA0QCoBu1AHcAAsBsAHdDT0BswANAA0QBoBu0wGwAAsBsQGyAbMNQYBugQMX0gG2AAsNQwG4RAEBEgGAbdMBsAALAbECEAGzDUeAboEDGdIBtgALDUkBuEYEAQYBCgGAbdIBsAALAd4Bs4Bu0gGwAAsB3gGzgG7UAdwACwGwAd0B/QGzAA0ADYBu0gGwAAsB3gGzgG7TAbAACwGxAbIBsw1VgG6BAx/SAbYACw1XAbhEAgENAYBt0wGwAAsBsQGyAbMNW4BugQMh0gG2AAsNXQG4RAsBEQGAbdIBsAALAd4Bs4Bu0wGwAAsBsQGyAbMNY4BugQMk0gG2AAsNZQG4RAsBEgGAbdMBsAALAbECHQGzDWmAboEDJtIBtgALDWsBuEgHAQoBDAETAYBt0wGwAAsBsQIdAbMNb4BugQMo0gG2AAsNcQG4SAEBAwEKAhADgG3SAbAACwHeAbOAbtMBsAALAbECEAGzDXeAboEDK9IBtgALDXkBuEYFAQ0BEAGAbdMBsAALAbEBsgGzDX2AboEDLdIBtgALDX8BuEQCARMBgG3SAbAACwHeAbOAbtIAEQALDYQAP68QFQ2FDYYNhw2IDYkNig2HDYcNjQ2ODY8NkA2RDYcNkw2HDYcNhw2XDZgNmYEDMIEDMYEDMoEDNIEDNYEDNoEDMoEDMoEDN4EDOIEDOYEDOoEDO4EDMoEDPIEDMoEDMoEDMoEDPYEDPoEDP4An0gARAAsNnAA/oQongQKIgCfSABEACw2gAD+jCkYKWApZgQKLgQKMgQKNgCfSABEACw2mDaeggQMz0gBiAGMApQ2pogClAGfSABEACw2rAD+ggCfSABEACw2uAD+ggCfSABEACw2xAD+iCqoKrYEClIEClYAn0gARAAsNtgA/ogtYC2GBAr+BAsCAJ9IAEQALDbsAP6ELfoECw4An0gARAAsNvwA/owugC6ELqIECxoECx4ECyIAn0gARAAsNxQA/oIAn0gARAAsNyAA/ogvdC9+BAs2BAs6AJ9IAEQALDc0AP6ML8wwADAKBAtGBAtKBAtOAJ9IAEQALDdMAP6QMXQxfDGQMZoEC6IEC6YEC6oEC64An0gARAAsN2gA/ogyKDI2BAu6BAu+AJ9IAEQALDd8AP6EMpoEC8oAn0wGwAAsBsQIQAbMN5IBugQNB0gG2AAsN5gG4RgABCQEMAYBt0gARAAsN6QA/owoSChMKG4ECloECqIEC1IAn0gBiAGMN7w3wXE5TRGljdGlvbmFyeaIN8QBnXE5TRGljdGlvbmFyedIAYgBjDfMN9F5BU1RocmFzaFVwZGF0ZaIN9QBnXkFTVGhyYXNoVXBkYXRlXxAPTlNLZXllZEFyY2hpdmVy0Q34DflUcm9vdIABAAgAGQAiACsANQA6AD8GzQbTBuAG5gbvBvYG+Ab6Bv0HCgcSBx0HNgc4BzoHPAc+B0AHQgdEB0YHSAdKB0wHTgdnB2kHawdtB28HcQd0B3cHegd9B4AHgweGB4kHnAe1B8sH2gfiB+cIAAgVCCsIOQhRCGUIbgh3CHkIewh9CH8IgQiOCJQIngigCKIIpAitCNgI2gjcCN4I4AjiCOQI5gjoCOoI7AjuCPAI8gj0CPYI+Aj6CPwI/gkACQIJCwkSCRUJFwkgCSsJNAlHCUwJXwloCXEJdAl2CX8JggmECY0JkAmSCZsJngmgCakJrAmuCbcJugm8CcUJyAnKCdMJ1gnYCeEJ5AnmCe8J8gn0Cf0KAAoCCgsKDgoQChkKHAoeCicKKgosCjUKOAo6CkMKRgpIClEKVApWCl8KYgpkCm0KcApyCnsKigqRCqAKqAqxCscKzAriCu8K8QrzCvUK/gspCysLLQsvCzELMws1CzcLOQs7Cz0LPwtBC0MLRQtHC0kLSwtNC08LUQtTC1wLXwthC2oLbQtvC3gLewt9C4YLiQuLC5QLlwuZC6ILpQunC7ALswu1C74LwQvDC8wLzwvRC9oL3QvfC+gL6wvtC/YL+Qv7DAQMBwwJDBIMFQwXDCAMIwwlDC4MMQwzDDwMPwxBDEoMTQxPDFgMWwxdDGYMaQxrDHgMegx8DH4MhwyyDLQMtgy4DLoMvAy+DMAMwgzEDMYMyAzKDMwMzgzQDNIM1AzWDNgM2gzcDOUM6AzqDPMM9gz4DQENBA0GDQ8NEg0UDR0NIA0iDSsNLg0wDTkNPA0+DUcNSg1MDVUNWA1aDWMNZg1oDXENdA12DX8Ngg2EDY0NkA2SDZsNng2gDakNrA2uDbcNug28DcUNyA3KDdMN1g3YDeEN5A3mDe8N8g30DgEOAw4FDgcOEA47Dj0OPw5BDkMORQ5HDkkOSw5NDk8OUQ5TDlUOVw5ZDlsOXQ5fDmEOYw5lDm4OcQ5zDnwOfw6BDooOjQ6PDpgOmw6dDqYOqQ6rDrQOtw65DsIOxQ7HDtAO0w7VDt4O4Q7jDuwO7w7xDvoO/Q7/DwgPCw8NDxYPGQ8bDyQPJw8pDzIPNQ83D0APQw9FD04PUQ9TD1wPXw9hD2oPbQ9vD3gPew99D4oPlw+jD6UPpw+pD7IPug+/D8EPyg/YD98P7Q/0D/0QERAYECwQNxBAEG0QbxBxEHMQdRB3EHkQexB9EH8QgRCDEIUQhxCJEIsQjRCPEJEQkxCVEJcQmRCqELUQvhDAEMIQzxDRENMQ3BDhEOMQ7BDuEPcQ+RECEQQRERETERURHhEjESURLhEwETkROxFIEUoRTBFVEVoRXBFtEW8RcRF+EYARghGLEZARkhGbEZ0RqhGsEa4RtxG8Eb4RxxHJEdYR2BHaEdwR5RHsEe4R9xH5EgISBBINEg8SHBIeEiASIhIrEjQSNhJDEkUSRxJQElUSVxJoEmoSbBJ1EqASohKkEqYSqBKqEqwSrhKwErIStBK2ErgSuhK8Er4SwBLCEsQSxhLIEsoS0xLYEtoS3BLeEucS6hLsEvUS+BL6EwMTCBMKEwwTDhMXExoTHBMlEygTKhMzEzYTOBM6E0MTRhNIE1ETVBNWE1gTYRNkE2YTbxN0E3YTeBN6E4MThhOIE5ETlBOWE58TphOoE6oTrBOuE7cTuhO8E8UTyBPKE9MT1hPYE+ET5BPmE+gT8RP0E/YT/xQEFAYUCBQKFBMUFhQYFCEUJBQmFC8UMhQ0FDYUPxRCFEQUTRRQFFIUVBRdFGAUYhRrFHAUchR0FHYUfxSCFIQUjRSQFJIUmxSgFKIUpBSmFK8UshS0FL0UwBTCFMsU0hTUFNYU2BTaFOMU5hToFPEU9BT2FP8VAhUEFQ0VEhUUFRYVGBUhFSQVJhUvFTIVNBU9FUYVSBVKFUwVThVQFVkVXBVeFWcVahVsFXUVeBV6FYMVhhWIFZEVoBWiFaQVphWoFaoVrBWuFbAVuRW8Fb4VxxXKFcwV1RXYFdoV4xXmFegV8RX0FfYV/xYCFgQWDRYQFhIWGxYeFiAWIhYrFi4WMBY5FkAWQhZEFkYWSBZRFlQWVhZfFmIWZBZtFnAWchZ7FoAWghaEFoYWjxaSFpQWnRagFqIWqxasFq4WtxbiFuQW5hboFusW7hbxFvQW9xb6Fv0XABcDFwYXCRcMFw8XEhcVFxgXGxcdFyoXLBcuFzAXORdkF2YXaBdqF2wXbhdwF3IXdBd2F3gXehd8F34XgBeCF4QXhheIF4oXjBeOF5cXmhecF6UXqBeqF7MXthe4F8EXxBfGF88X0hfUF90X4BfiF+sX7hfwF/kX/Bf+GAcYChgMGBUYGBgaGCMYJhgoGDEYNBg2GD8YQhhEGE0YUBhSGFsYXhhgGGkYbBhuGHcYehh8GIUYiBiKGJMYlhiYGKEYpBimGLMYtRi3GLkYwhjtGO8Y8RjzGPUY9xj5GPsY/Rj/GQEZAxkFGQcZCRkLGQ0ZDxkRGRMZFRkXGSAZIxklGS4ZMRkzGTwZPxlBGUoZTRlPGVgZWxldGWYZaRlrGXQZdxl5GYIZhRmHGZAZkxmVGZ4ZoRmjGawZrxmxGboZvRm/GcgZyxnNGdYZ2RnbGeQZ5xnpGfIZ9Rn3GgAaAxoFGg4aERoTGhwaHxohGioaLRovGjwaPhpAGkIaSxp2Gngaehp8Gn4agBqDGoYaiRqMGo8akhqVGpgamxqeGqEapBqnGqoarRqvGrgauxq9GsYayRrLGtQa1xrZGuIa5RrnGvAa8xr1Gv4bARsDGwwbDxsRGxobHRsfGygbKxstGzYbORs7G0QbRxtJG1IbVRtXG2AbYxtlG24bcRtzG3wbfxuBG4objRuPG5gbmxudG6YbqRurG7Qbtxu5G8IbxRvHG9Qb1xvZG9sb5BwPHBIcFRwYHBscHhwhHCQcJxwqHC0cMBwzHDYcORw8HD8cQhxFHEgcSxxNHFYcWRxbHGQcZxxpHHIcdRx3HIAcgxyFHI4ckRyTHJwcnxyhHKocrRyvHLgcuxy9HMYcyRzLHNQc1xzZHOIc5RznHPAc8xz1HP4dAR0DHQwdDx0RHRodHR0fHSgdKx0tHTYdOR07HUQdRx1JHVIdVR1XHWAdYx1lHXIddR13HXkdgh2tHbAdsx22HbkdvB2/HcIdxR3IHcsdzh3RHdQd1x3aHd0d4B3jHeYd6R3rHfQd9x35HgIeBR4HHhAeEx4VHh4eIR4jHiweLx4xHjoePR4/HkgeSx5NHlYeWR5bHmQeZx5pHnIedR53HoAegx6FHo4ekR6THpwenx6hHqoerR6vHrgeux69HsYeyR7LHtQe1x7ZHuIe5R7nHvAe8x71Hv4fAR8DHxAfEx8VHxcfIB9LH04fUR9UH1cfWh9dH2AfYx9mH2kfbB9vH3IfdR94H3sffh+BH4Qfhx+JH5IflR+XH6Afox+lH64fsR+zH7wfvx/BH8ofzR/PH9gf2x/dH+Yf6R/rH/Qf9x/5IAIgBSAHIBAgEyAVIB4gISAjICwgLyAxIDogPSA/IEggSyBNIFYgWSBbIGQgZyBpIHIgdSB3IIAggyCFII4gkSCTIJwgnyChIK4gsSCzILUgviDpIOwg7yDyIPUg+CD7IP4hASEEIQchCiENIRAhEyEWIRkhHCEfISIhJSEnITAhMyE1IT4hQSFDIUwhTyFRIVohXSFfIWghayFtIXYheSF7IYQhhyGJIZIhlSGXIaAhoyGlIa4hsSGzIbwhvyHBIcohzSHPIdgh2yHdIeYh6SHrIfQh9yH5IgIiBSIHIhAiEyIVIh4iISIjIiwiLyIxIjoiPSI/IkwiTyJRIlMiXCKHIooijSKQIpMiliKZIpwinyKiIqUiqCKrIq4isSK0IrciuiK9IsAiwyLFIs4i0SLTItwi3yLhIuoi7SLvIvgi+yL9IwYjCSMLIxQjFyMZIyIjJSMnIzAjMyM1Iz4jQSNDI0wjTyNRI1ojXSNfI2gjayNtI3YjeSN7I4QjhyOJI5IjlSOXI6AjoyOlI64jsSOzI7wjvyPBI8ojzSPPI9gj2yPdI+oj7SPvI/Ej+iQlJCgkKyQuJDEkNCQ3JDokPSRAJEMkRiRJJEwkTyRSJFUkWCRbJF4kYSRjJGwkbyRxJHokfSR/JIgkiySNJJYkmSSbJKQkpySpJLIktSS3JMAkwyTFJM4k0STTJNwk3yThJOok7STvJPgk+yT9JQYlCSULJRQlFyUZJSIlJSUnJTAlMyU1JT4lQSVDJUwlTyVRJVolXSVfJWglayVtJXYleSV7JYgliyWNJY8lmCXDJcYlySXMJc8l0iXVJdgl2yXeJeEl5CXnJeol7SXwJfMl9iX5Jfwl/yYBJgomDSYPJhgmGyYdJiYmKSYrJjQmNyY5JkImRSZHJlAmUyZVJl4mYSZjJmwmbyZxJnomfSZ/JogmiyaNJpYmmSabJqQmpyapJrImtSa3JsAmwybFJs4m0SbTJtwm3ybhJuom7SbvJvgm+yb9JwYnCScLJxQnFycZJyYnKScrJy0nNidhJ2QnZydqJ20ncCdzJ3YneSd8J38ngieFJ4gniyeOJ5EnlCeXJ5onnSefJ6gnqyetJ7YnuSe7J8QnxyfJJ9In1SfXJ+An4yflJ+4n8SfzJ/wn/ygBKAooDSgPKBgoGygdKCYoKSgrKDQoNyg5KEIoRShHKFAoUyhVKF4oYShjKGwobyhxKHoofSh/KIgoiyiNKJYomSibKKQopyipKLIotSi3KMQoxyjJKMso1Cj/KQIpBSkIKQspDikRKRQpFykaKR0pICkjKSYpKSksKS8pMik1KTgpOyk9KUYpSSlLKVQpVylZKWIpZSlnKXApcyl1KX4pgSmDKYwpjymRKZopnSmfKagpqymtKbYpuSm7KcQpxynJKdIp1SnXKeAp4ynlKe4p8SnzKfwp/yoBKgoqDSoPKhgqGyodKiYqKSorKjQqNyo5KkIqRSpHKlAqUypVKmIqZSpnKmkqciqdKqAqoyqmKqkqrCqvKrIqtSq4KrsqvirBKsQqxyrKKs0q0CrTKtYq2SrbKuQq5yrpKvIq9Sr3KwArAysFKw4rESsTKxwrHyshKyorLSsvKzgrOys9K0YrSStLK1QrVytZK2IrZStnK3Arcyt1K34rgSuDK4wrjyuRK5ornSufK6grqyutK7YruSu7K8QrxyvJK9Ir1SvXK+Ar4yvlK+4r8SvzLAAsAywFLAcsECw7LD4sQSxELEcsSixNLFAsUyxWLFksXCxfLGIsZSxoLGssbixxLHQsdyx5LIIshSyHLJAskyyVLJ4soSyjLKwsryyxLLosvSy/LMgsyyzNLNYs2SzbLOQs5yzpLPIs9Sz3LQAtAy0FLQ4tES0TLRwtHy0hLSotLS0vLTgtOy09LUYtSS1LLVQtVy1ZLWItZS1nLXAtcy11LX4tgS2DLYwtjy2RLZ4toS2jLaUtri3ZLdwt3y3iLeUt6C3rLe4t8S30Lfct+i39LgAuAy4GLgkuDC4PLhIuFS4XLiAuIy4lLi4uMS4zLjwuPy5BLkouTS5PLlguWy5dLmYuaS5rLnQudy55LoIuhS6HLpAuky6VLp4uoS6jLqwury6xLrouvS6/Lsguyy7NLtYu2S7bLuQu5y7pLvIu9S73LwAvAy8FLw4vES8TLxwvHy8hLyovLS8vLzwvPy9BL0MvTC93L3ovfS+AL4Mvhi+JL4wvjy+SL5UvmC+bL54voS+kL6cvqi+tL7Avsy+1L74vwS/DL8wvzy/RL9ov3S/fL+gv6y/tL/Yv+S/7MAQwBzAJMBIwFTAXMCAwIzAlMC4wMTAzMDwwPzBBMEowTTBPMFgwWzBdMGYwaTBrMHQwdzB5MIIwhTCHMJAwkzCVMJ4woTCjMKwwrzCxMLowvTC/MMgwyzDNMNow3TDfMOEw6jEVMRgxGzEeMSExJDEnMSoxLTEwMTMxNjE5MTwxPzFCMUUxSDFLMU4xUTFTMVwxXzFhMWoxbTFvMXgxezF9MYYxiTGLMZQxlzGZMaIxpTGnMbAxszG1Mb4xwTHDMcwxzzHRMdox3THfMegx6zHtMfYx+TH7MgQyBzIJMhIyFTIXMiAyIzIlMi4yMTIzMjwyPzJBMkoyTTJPMlgyWzJdMmYyaTJrMngyezJ9Mn8yiDKzMrYyuTK8Mr8ywjLFMsgyyzLOMtEy1DLXMtoy3TLgMuMy5jLpMuwy7zLxMvoy/TL/MwgzCzMNMxYzGTMbMyQzJzMpMzIzNTM3M0AzQzNFM04zUTNTM1wzXzNhM2ozbTNvM3gzezN9M4YziTOLM5QzlzOZM6IzpTOnM7AzszO1M74zwTPDM8wzzzPRM9oz3TPfM+gz6zPtM/Yz+TP7NAQ0BzQJNBY0GTQbNB00JjRRNFQ0VzRaNF00YDRjNGY0aTRsNG80cjR1NHg0ezR+NIE0hDSHNIo0jTSPNJg0mzSdNKY0qTSrNLQ0tzS5NMI0xTTHNNA00zTVNN404TTjNOw07zTxNPo0/TT/NQg1CzUNNRY1GTUbNSQ1JzUpNTI1NTU3NUA1QzVFNU41UTVTNVw1XzVhNWo1bTVvNXg1ezV9NYY1iTWLNZQ1lzWZNaI1pTWnNbQ1tzW5Nbs1xDXvNfI19TX4Nfs1/jYBNgQ2BzYKNg02EDYTNhY2GTYcNh82IjYlNig2KzYtNjY2OTY7NkQ2RzZJNlI2VTZXNmA2YzZlNm42cTZzNnw2fzaBNoo2jTaPNpg2mzadNqY2qTarNrQ2tza5NsI2xTbHNtA20zbVNt424TbjNuw27zbxNvo2/Tb/Nwg3CzcNNxY3GTcbNyQ3JzcpNzI3NTc3N0A3QzdFN043ezd+N4E3gzeGN4k3jDePN5I3lTeYN5s3njehN6M3pjepN6s3rTewN7M3tje4N8U3yDfKN9M3/jgBOAM4BTgHOAk4CzgNOA84ETgTOBU4FzgZOBs4HTgfOCE4IzglOCc4KTgyODU4NzhEOEc4SThSOIM4hTiHOIo4jDiOOJA4kziWOJk4nDifOKE4pDinOKo4rTiwOLM4tji5OLw4vzjCOMQ4zTjQONI42zjeOOA46TjsOO44+zj+OQA5CTkwOTM5Njk5OTw5PzlBOUQ5RzlKOU05UDlTOVY5WTlcOV85YjllOWc5dDl3OXk5gjmpOaw5rjmxObQ5tzm6Ob05wDnDOcY5yTnMOc850jnVOdg52jndOd857DnvOfE5+jonOio6LTowOjM6NTo4Ojo6PTpAOkM6RTpIOks6TjpROlQ6VzpaOl06YDpjOmU6bjpxOnM6fDp/OoE6jjqROpM6lTqeOsE6xDrHOso6zTrQOtM61jrZOtw63zriOuU66DrrOu468TrzOvw6/zsBOwo7DTsPOxg7GzsdOyY7KTsrOzQ7Nzs5O0I7RTtHO1A7UztVO147YTtjO2w7bztxO3o7fTt/O4g7izuNO5Y7mTubO6Q7pzupO7I7tTu3O8A7wzvFO8470TvTO+A74zvlO+c78DwZPBw8HzwiPCU8KDwrPC48MTw0PDc8Ojw9PEA8QzxGPEk8TDxPPFI8VDxdPGA8YjxrPG48cDx5PHw8fjyHPIo8jDyVPJg8mjyjPKY8qDyxPLQ8tjy/PMI8xDzNPNA80jzbPN484DzpPOw87jz3PPo8/D0FPQg9Cj0TPRY9GD0hPSQ9Jj0vPTI9ND09PUA9Qj1LPU49UD1ZPVw9Xj1rPW49cD15Pag9qz2uPbE9tD23Pbo9vT3APcI9xT3IPcs9zj3RPdQ91z3aPd094D3jPeY96T3rPfQ99z35PgI+BT4HPhQ+Fz4ZPiI+TT5QPlM+VT5YPls+Xj5hPmQ+Zz5qPm0+cD5zPnU+eD57Pn4+gT6EPoc+iT6SPpU+lz6kPqc+qT6yPuM+5j7pPuw+7z7yPvU++D77Pv4/AT8EPwY/CT8MPw8/Ej8VPxg/Gz8dPyA/Iz8mPyg/MT80PzY/Pz9CP0Q/TT9QP1I/Xz9iP2Q/bT+SP5U/mD+bP54/oT+kP6c/qj+tP68/sj+1P7g/uz++P8A/wz/FP9I/1T/XP+BAC0AOQBFAFEAXQBpAHEAfQCJAJEAnQClALEAvQDJANUA4QDtAPkBBQENARUBOQFFAU0BcQF9AYUBuQHFAc0B8QKtArkCwQLNAtkC4QLtAvkDBQMRAx0DKQMxAzkDRQNRA10DaQN1A4EDiQORA5kDoQPFA9ED2QP9BAkEEQQ1BEEESQR9BIkEkQSZBL0FSQVVBWEFbQV5BYUFkQWdBakFtQXBBc0F2QXlBfEF/QYJBhEGNQZBBkkGbQZ5BoEGpQaxBrkG3QbpBvEHFQchBykHTQdZB2EHhQeRB5kHvQfJB9EH9QgBCAkILQg5CEEIZQhxCHkInQipCLEI1QjhCOkJDQkZCSEJRQlRCVkJfQmJCZEJxQnRCdkJ/QqpCrUKwQrNCtkK5QrxCvkLBQsRCx0LKQs1C0ELTQtZC2ELbQt5C4ELjQuVC7kLxQvNC/EL/QwFDCkMNQw9DGEMbQx1DKkMtQy9DOENfQ2JDZUNnQ2pDbUNwQ3NDdkN5Q3xDf0OCQ4VDiEOLQ45DkUOTQ5VDnkOhQ6NDrEOvQ7FDvkPBQ8NDzEP3Q/pD/UQARANEBkQJRAxED0QSRBVEGEQbRB5EIUQkRCdEKkQtRDBEM0Q1RD5EQURDRFBEUkRVRF5EZURnRHBEm0SeRKFEpESnRKpErUSwRLNEtkS5RLxEv0TCRMVEyETLRM5E0UTURNdE2UTiRORE9UT3RPlFAkUERRFFE0UWRR9FJEUmRTNFNUU4RUFFRkVIRVlFW0VdRWZFaEVxRXNFfEV+RYdFiUWaRZxFpUWnRbBFskW/RcFFxEXNRdRF1kXjReVF6EXxRfZF+EYJRgtGDUYWRhhGJUYnRipGM0Y6RjxGSUZLRk5GV0ZgRmJGc0Z1RndGgEarRq5GsUa0RrdGuka9RsBGw0bGRslGzEbPRtJG1UbYRttG3kbhRuRG50bpRvJG9EcBRwNHBkcPRxRHFkcnRylHK0c8Rz5HQEdNR09HUkdbR2BHYkdvR3FHdEd9R4RHhkePR5FHmkecR61Hr0e4R7pHx0fJR8xH1UfaR9xH6UfrR+5H90f8R/5IB0gJSBZIGEgbSCRIKUgrSDhIOkg9SEZIT0hRSF5IYEhjSGxIdUh3SIBIgkiPSJFIlEidSKRIpkizSLVIuEjBSMZIyEjRSNNI3EkJSQxJD0kSSRVJGEkbSR5JIUkkSSdJKkktSTBJM0k2STlJPEk/SUJJRUlISUpJU0lWSVlJW0lkSWtJbklxSXRJdkl/SYBJg0mMSZFJmkmbSZ1JpkmnSalJskm3SbpJvUm/SchJzUnQSdNJ1UneSeFJ5EnmSe9J9kn5SfxJ/0oBSgpKC0oNShZKG0oeSiFKI0osSjNKNko5SjxKPkpHSlBKU0pWSllKXEpeSmdKbEpvSnJKdEp9SoBKg0qFSpJKlEqXSqBKp0qpSrJKuUq8Sr9KwkrESs1K2krfSuxK9UsESwlLGEsqSy9LNAAAAAAAAAICAAAAAAAADfoAAAAAAAAAAAAAAAAAAEs2 \ No newline at end of file +YnBsaXN0MDDUAAEAAgADAAQABQAGAagBqVgkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRvcBIAAYagrxBrAAcACAAPAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAPABEAEoATwBWAFkAXABeAGEAaQBsAG8AcgB1AHgAgACGAI4AkgCWAJkAnACfAKIApgCqALIAtQC4ALsAvgDBAMUAzQDQANMA1gDZANwA4ADoAOsA7gDxAPQA9wD7AQMBBgEJAQwBDwESARQBGwEeAScBKgEuATYBOQE8AT8BQgFFAUgBUAFTAVwBXwFhAWkBawFtAW8BcQFzAXsBfQF/AYEBgwGFAYwBkAGTAZYBmgGcAaABpFUkbnVsbNMACQAKAAsADAANAA5VX2RpY3RYX3ZlcnNpb25WJGNsYXNzgAIQAYBq0wAQABEACwASAB8ALFdOUy5rZXlzWk5TLm9iamVjdHOsABMAFAAVABYAFwAYABkAGgAbABwAHQAegAOABIAFgAaAB4AIgAmACoALgAyADYAOrAAgACEAIgAjACQAJQAmACcAKAApACoAK4APgBGAE4AYgB6ARYBVgFaAXIBigGeAaIBpXxAQaW5zZXJ0ZWRTZWN0aW9uc18QFnJlcGxhY2VkU2VjdGlvbkluZGV4ZXNfEBNpbnNlcnRlZEl0ZW1JbmRleGVzXnJlcGxhY2luZ0l0ZW1zV29sZERhdGFUZGF0YV8QFmluc2VydGVkU2VjdGlvbkluZGV4ZXNfEBJkZWxldGVkSXRlbUluZGV4ZXNfEBNyZXBsYWNlZEl0ZW1JbmRleGVzXWluc2VydGVkSXRlbXNfEBVkZWxldGVkU2VjdGlvbkluZGV4ZXNfEBFyZXBsYWNpbmdTZWN0aW9uc9IAEQALADoAO6CAENIAPQA+AD8AQFokY2xhc3NuYW1lWCRjbGFzc2VzXk5TTXV0YWJsZUFycmF5owBBAEIAQ15OU011dGFibGVBcnJheVdOU0FycmF5WE5TT2JqZWN01ABFAAsARgBHAEgASQANAA1aTlNMb2NhdGlvblxOU1JhbmdlQ291bnRYTlNMZW5ndGgQAoAS0gA9AD4ASwBMXxARTlNNdXRhYmxlSW5kZXhTZXSjAE0ATgBDXxARTlNNdXRhYmxlSW5kZXhTZXRaTlNJbmRleFNldNIAEQALAFAAO6QAUQBSAFMAVIAUgBWAFoAXgBDUAEUACwBGAEcAVwBJAA0ADRADgBLSAEYACwBaAEkQAIAS0gBGAAsAWgBJgBLUAEUACwBGAEcAXwBJAA0ADRAEgBLSABEACwBiADulAGMAZABlAGYAZ4AZgBqAG4AcgB2AENIAEQALAGoAO6CAENIAEQALAG0AO6CAENIAEQALAHAAO6CAENIAEQALAHMAO6CAENIAEQALAHYAO6CAENIAEQALAHkAf6UAegB7AHwAfQB+gB+AKIAvgDaAPYBE0wCBAIIACwCDAIQAhVVpdGVtc1lzZWN0aW9uSUSAIBEBhYAn0gARAAsAhwA7pQCIAIkAigCLAIyAIYAjgCSAJYAmgBDSAI8ACwCQAJFWaXRlbUlEEQJogCLSAD0APgCTAJRfEBBBU1RocmFzaFRlc3RJdGVtogCVAENfEBBBU1RocmFzaFRlc3RJdGVt0gCPAAsAlwCREQJpgCLSAI8ACwCaAJERAmqAItIAjwALAJ0AkRECa4Ai0gCPAAsAoACREQJsgCLSAD0APgCjAKRfEBNBU1RocmFzaFRlc3RTZWN0aW9uogClAENfEBNBU1RocmFzaFRlc3RTZWN0aW9u0wCBAIIACwCnAKgAhYApEQGGgCfSABEACwCrADulAKwArQCuAK8AsIAqgCuALIAtgC6AENIAjwALALMAkRECbYAi0gCPAAsAtgCREQJugCLSAI8ACwC5AJERAm+AItIAjwALALwAkRECcIAi0gCPAAsAvwCREQJxgCLTAIEAggALAMIAwwCFgDARAYeAJ9IAEQALAMYAO6UAxwDIAMkAygDLgDGAMoAzgDSANYAQ0gCPAAsAzgCREQJygCLSAI8ACwDRAJERAnOAItIAjwALANQAkRECdIAi0gCPAAsA1wCREQJ1gCLSAI8ACwDaAJERAnaAItMAgQCCAAsA3QDeAIWANxEBiIAn0gARAAsA4QA7pQDiAOMA5ADlAOaAOIA5gDqAO4A8gBDSAI8ACwDpAJERAneAItIAjwALAOwAkRECeIAi0gCPAAsA7wCREQJ5gCLSAI8ACwDyAJERAnqAItIAjwALAPUAkRECe4Ai0wCBAIIACwD4APkAhYA+EQGJgCfSABEACwD8ADulAP0A/gD/AQABAYA/gECAQYBCgEOAENIAjwALAQQAkRECfIAi0gCPAAsBBwCREQJ9gCLSAI8ACwEKAJERAn6AItIAjwALAQ0AkRECf4Ai0gCPAAsBEACREQKAgCLSAD0APgBCAROiAEIAQ9IAEQALARUAO6QBFgEXARgBGYBGgEmAUIBSgBDTAIEAggALARwAqACFgEeAJ9IAEQALAR8AO6YArACtAK4BIwCvALCAKoArgCyASIAtgC6AENIAjwALASgAkREChoAi0wCBAIIACwErASwAhYBKEQGZgCfSABEACwEvADulATABMQEyATMBNIBLgEyATYBOgE+AENIAjwALATcAkRECgYAi0gCPAAsBOgCREQKCgCLSAI8ACwE9AJERAoOAItIAjwALAUAAkREChIAi0gCPAAsBQwCREQKFgCLTAIEAggALAUYA3gCFgFGAJ9IAEQALAUkAO6UA4gDjAOQA5QDmgDiAOYA6gDuAPIAQ0wCBAIIACwFRAPkAhYBTgCfSABEACwFUADumAP0A/gD/AQABWQEBgD+AQIBBgEKAVIBDgBDSAI8ACwFdAJERAoeAItIARgALAFoASYAS0gARAAsBYgA7pQFjAWQBZQFmAWeAV4BYgFmAWoBbgBDSAEYACwBaAEmAEtIARgALAFoASYAS0gBGAAsAWgBJgBLSAEYACwBaAEmAEtIARgALAFoASYAS0gARAAsBdAA7pQF1AXYBdwF4AXmAXYBegF+AYIBhgBDSAEYACwBaAEmAEtIARgALAFoASYAS0gBGAAsAWgBJgBLSAEYACwBaAEmAEtIARgALAFoASYAS0gARAAsBhgA7pAGHAYgBiQGKgGOAZIBlgGaAENIAEQALAY0AO6EBI4BIgBDSABEACwGRAH+ggETSABEACwGUADuggBDSABEACwGXADuhAVmAVIAQ1ABFAAsARgBHAFoASQANAA2AEtIAEQALAZ0AO6EBF4BJgBDSAD0APgGhAaJcTlNEaWN0aW9uYXJ5ogGjAENcTlNEaWN0aW9uYXJ50gA9AD4BpQGmXkFTVGhyYXNoVXBkYXRlogGnAENeQVNUaHJhc2hVcGRhdGVfEA9OU0tleWVkQXJjaGl2ZXLRAaoBq1Ryb290gAEACAAZACIAKwA1ADoAPwEYAR4BKwExAToBQQFDAUUBRwFUAVwBZwGAAYIBhAGGAYgBigGMAY4BkAGSAZQBlgGYAbEBswG1AbcBuQG7Ab0BvwHBAcMBxQHHAckBywHeAfcCDQIcAiQCKQJCAlcCbQJ7ApMCpwKwArECswK8AscC0ALfAuYC9QL9AwYDFwMiAy8DOAM6AzwDRQNZA2ADdAN/A4gDkQOTA5UDlwOZA5sDrAOuA7ADuQO7A70DxgPIA9kD2wPdA+YD8QPzA/UD9wP5A/sD/QQGBAcECQQSBBMEFQQeBB8EIQQqBCsELQQ2BDcEOQRCBE0ETwRRBFMEVQRXBFkEZgRsBHYEeAR7BH0EhgSRBJMElQSXBJkEmwSdBKYErQSwBLIEuwTOBNME5gTvBPIE9AT9BQAFAgULBQ4FEAUZBRwFHgUnBT0FQgVYBWUFZwVqBWwFdQWABYIFhAWGBYgFigWMBZUFmAWaBaMFpgWoBbEFtAW2Bb8FwgXEBc0F0AXSBd8F4QXkBeYF7wX6BfwF/gYABgIGBAYGBg8GEgYUBh0GIAYiBisGLgYwBjkGPAY+BkcGSgZMBlkGWwZeBmAGaQZ0BnYGeAZ6BnwGfgaABokGjAaOBpcGmgacBqUGqAaqBrMGtga4BsEGxAbGBtMG1QbYBtoG4wbuBvAG8gb0BvYG+Ab6BwMHBgcIBxEHFAcWBx8HIgckBy0HMAcyBzsHPgdAB0kHTgdXB2AHYgdkB2YHaAdqB3cHeQd7B4QHkQeTB5UHlweZB5sHnQefB6gHqwetB7oHvAe/B8EHygfVB9cH2QfbB90H3wfhB+oH7QfvB/gH+wf9CAYICQgLCBQIFwgZCCIIJQgnCDQINgg4CEEITAhOCFAIUghUCFYIWAhlCGcIaQhyCH8IgQiDCIUIhwiJCIsIjQiWCJkImwikCKYIrwi6CLwIvgjACMIIxAjGCM8I0QjaCNwI5QjnCPAI8gj7CP0JBgkRCRMJFQkXCRkJGwkdCSYJKAkxCTMJPAk+CUcJSQlSCVQJXQlmCWgJaglsCW4JcAl5CXwJfgmACYkJigmMCZUJlgmYCaEJpAmmCagJuQm7CcQJxwnJCcsJ1AnhCeYJ8wn8CgsKEAofCjEKNgo7AAAAAAAAAgIAAAAAAAABrAAAAAAAAAAAAAAAAAAACj0= \ No newline at end of file diff --git a/Base/ASAssert.h b/Base/ASAssert.h index d3f88a44d3..2df66b37a7 100644 --- a/Base/ASAssert.h +++ b/Base/ASAssert.h @@ -48,8 +48,8 @@ #define ASDisplayNodeAssertFalse(condition) ASDisplayNodeAssertWithSignal(!(condition), nil, nil) #define ASDisplayNodeCAssertFalse(condition) ASDisplayNodeCAssertWithSignal(!(condition), nil, nil) -#define ASDisplayNodeFailAssert(description, ...) ASDisplayNodeAssertWithSignal(NO, nil, (description), ##__VA_ARGS__) -#define ASDisplayNodeCFailAssert(description, ...) ASDisplayNodeCAssertWithSignal(NO, nil, (description), ##__VA_ARGS__) +#define ASDisplayNodeFailAssert(description, ...) ASDisplayNodeAssertWithSignal(NO, (description), ##__VA_ARGS__) +#define ASDisplayNodeCFailAssert(description, ...) ASDisplayNodeCAssertWithSignal(NO, (description), ##__VA_ARGS__) #define ASDisplayNodeConditionalAssert(shouldTestCondition, condition, description, ...) ASDisplayNodeAssert((!(shouldTestCondition) || (condition)), nil, (description), ##__VA_ARGS__) #define ASDisplayNodeConditionalCAssert(shouldTestCondition, condition, description, ...) ASDisplayNodeCAssert((!(shouldTestCondition) || (condition)), nil, (description), ##__VA_ARGS__) diff --git a/Base/ASBaseDefines.h b/Base/ASBaseDefines.h index dfc02c1230..1800011d90 100755 --- a/Base/ASBaseDefines.h +++ b/Base/ASBaseDefines.h @@ -143,3 +143,11 @@ #define ASDISPLAYNODE_REQUIRES_SUPER #endif #endif + +#ifndef AS_UNAVAILABLE +#if __has_attribute(unavailable) +#define AS_UNAVAILABLE(message) __attribute__((unavailable(message))) +#else +#define AS_UNAVAILABLE(message) +#endif +#endif diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c96a002f8c..2861dc49e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,8 +12,9 @@ please open an issue on GitHub. 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 7. Make sure that any new files conform to the correct file header style below -*ASDK FILES:* +#### ASDK files header style +``` // // ASPagerFlowLayout.h // AsyncDisplayKit @@ -25,9 +26,11 @@ please open an issue on GitHub. // 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. // +``` -*EXAMPLE PROJECT FILES:* +#### Example project files header style +``` // // PhotoCellNode.m // Sample @@ -46,6 +49,7 @@ please open an issue on GitHub. // 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. // +``` ## Contributor License Agreement ("CLA") In order to accept your pull request, we need you to submit a CLA. You only need diff --git a/Default-568h@2x.png b/Default-568h@2x.png index 0891b7aabf..1547a98454 100644 Binary files a/Default-568h@2x.png and b/Default-568h@2x.png differ diff --git a/README.md b/README.md index 10f28e5005..6631041e76 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -![AsyncDisplayKit](https://github.com/facebook/AsyncDisplayKit/blob/master/docs/assets/logo.png) +![AsyncDisplayKit](https://github.com/facebook/AsyncDisplayKit/blob/gh-pages/static/images/logo.png) -[![Apps Using](https://img.shields.io/badge/Apps%20Using%20ASDK-%3E3,658-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) -[![Downloads](https://img.shields.io/badge/Total%20Downloads-%3E377,749-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) +[![Apps Using](https://img.shields.io/badge/Apps%20Using%20ASDK-%3E4,974-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) +[![Downloads](https://img.shields.io/badge/Total%20Downloads-%3E512,000-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) [![Platform](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-orange.svg)](http://AsyncDisplayKit.org) [![Languages](https://img.shields.io/badge/languages-ObjC%20%7C%20Swift-orange.svg)](http://AsyncDisplayKit.org) @@ -10,114 +10,37 @@ [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-59C939.svg?style=flat)](https://github.com/Carthage/Carthage) [![Build Status](https://travis-ci.org/facebook/AsyncDisplayKit.svg)](https://travis-ci.org/facebook/AsyncDisplayKit) [![License](https://img.shields.io/cocoapods/l/AsyncDisplayKit.svg)](https://github.com/facebook/AsyncDisplayKit/blob/master/LICENSE) + +## Installation +ASDK is available via CocoaPods or Carthage. See our [Installation](http://asyncdisplaykit.org/docs/installation.html) guide for instructions. -AsyncDisplayKit is an iOS framework that keeps even the most complex user -interfaces smooth and responsive. It was originally built to make Facebook's -[Paper](https://facebook.com/paper) possible, and goes hand-in-hand with -[pop](https://github.com/facebook/pop)'s physics-based animations — but -it's just as powerful with UIKit Dynamics and conventional app designs. +## Performance Gains -### Quick start +AsyncDisplayKit's basic unit is the `node`. An ASDisplayNode is an abstraction over `UIView`, which in turn is an abstraction over `CALayer`. Unlike views, which can only be used on the main thread, nodes are thread-safe: you can instantiate and configure entire hierarchies of them in parallel on background threads. -ASDK is available on [CocoaPods](http://cocoapods.org). Add the following to your Podfile: +To keep its user interface smooth and responsive, your app should render at 60 frames per second — the gold standard on iOS. This means the main thread has one-sixtieth of a second to push each frame. That's 16 milliseconds to execute all layout and drawing code! And because of system overhead, your code usually has less than ten milliseconds to run before it causes a frame drop. -```ruby -pod 'AsyncDisplayKit' -``` +AsyncDisplayKit lets you move image decoding, text sizing and rendering, layout, and other expensive UI operations off the main thread, to keep the main thread available to respond to user interaction. -(ASDK can also be used as a regular static library: Copy the project to your -codebase manually, adding `AsyncDisplayKit.xcodeproj` to your workspace. Add -`libAsyncDisplayKit.a`, MapKit, AssetsLibrary, and Photos to the "Link Binary With -Libraries" build phase. Include `-lc++ -ObjC` in your project linker flags.) +## Advanced Developer Features -Import the framework header, or create an [Objective-C bridging -header](https://developer.apple.com/library/ios/documentation/swift/conceptual/buildingcocoaapps/MixandMatch.html) -if you're using Swift: +As the framework has grown, many features have been added that can save developers tons of time by eliminating common boilerplate style structures common in modern iOS apps. If you've ever dealt with cell reuse bugs, tried to performantly preload data for a page or scroll style interface or even just tried to keep your app from dropping too many frames you can benefit from integrating ASDK. -```objective-c -#import -``` +## Learn More -AsyncDisplayKit Nodes are a thread-safe abstraction layer over UIViews and -CALayers: - -![node-view-layer diagram](https://github.com/facebook/AsyncDisplayKit/blob/master/docs/assets/node-view-layer.png) - -You can construct entire node hierarchies in parallel, or instantiate and size -a single node on a background thread — for example, you could do -something like this in a UIViewController: - -```objective-c -dispatch_async(_backgroundQueue, ^{ - ASTextNode *node = [[ASTextNode alloc] init]; - node.attributedString = [[NSAttributedString alloc] initWithString:@"hello!" - attributes:nil]; - [node measure:CGSizeMake(screenWidth, FLT_MAX)]; - node.frame = (CGRect){ CGPointZero, node.calculatedSize }; - - // self.view isn't a node, so we can only use it on the main thread - dispatch_async(dispatch_get_main_queue(), ^{ - [self.view addSubview:node.view]; - }); -}); -``` - -In Swift: - -```swift -dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) { - let node = ASTextNode() - node.attributedString = NSAttributedString(string: "hello") - node.measure(CGSize(width: screenWidth, height: CGFloat.max)) - node.frame = CGRect(origin: CGPointZero, size: node.calculatedSize) - - // self.view isn't a node, so we can only use it on the main thread - dispatch_async(dispatch_get_main_queue()) { - self.view.addSubview(node.view) - } -} -``` - -AsyncDisplayKit at a glance: - -* `ASImageNode` and `ASTextNode` are drop-in replacements for UIImageView and - UITextView. -* `ASMultiplexImageNode` can load and display progressively higher-quality - variants of an image over a slow cell network, letting you quickly show a - low-resolution photo while the full size downloads. -* `ASNetworkImageNode` is a simpler, single-image counterpart to the Multiplex - node. -* `ASTableView` and `ASCollectionView` are a node-aware UITableView and - UICollectionView, respectively, that can asynchronously preload cell nodes - — from loading network data to rendering — all without blocking - the main thread. - -You can also easily [create your own -nodes](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASDisplayNode%2BSubclasses.h) -to implement node hierarchies or custom drawing. - -## Learn more - -* Read the [Getting Started guide](http://asyncdisplaykit.org/docs/getting-started.html) +* Read the our [Getting Started](http://asyncdisplaykit.org/docs/getting-started.html) guide * Get the [sample projects](https://github.com/facebook/AsyncDisplayKit/tree/master/examples) * Browse the [API reference](http://asyncdisplaykit.org/appledocs.html) -* Watch the [NSLondon talk](http://vimeo.com/103589245) or the [NSSpain talk](https://www.youtube.com/watch?v=RY_X7l1g79Q) ## Getting Help -We use Slack for real-time debugging, community updates, and general talk about ASDK. Signup at http://asdk-slack-auto-invite.herokuapp.com or email AsyncDisplayKit(at)gmail.com to get an invite. - -## Testing - -AsyncDisplayKit has extensive unit test coverage. You'll need to run `pod install` in the root AsyncDisplayKit directory to set up OCMock. +We use Slack for real-time debugging, community updates, and general talk about ASDK. [Signup](http://asdk-slack-auto-invite.herokuapp.com) youself or email AsyncDisplayKit(at)gmail.com to get an invite. ## Contributing -See the CONTRIBUTING file for how to help out. +We welcome any contributions. See the [CONTRIBUTING](https://github.com/facebook/AsyncDisplayKit/blob/master/CONTRIBUTING.md) file for how to get involved. ## License -AsyncDisplayKit is BSD-licensed. We also provide an additional patent grant. - -The files in the /examples directory are licensed under a separate license as specified in each file; documentation is licensed CC-BY-4.0. +AsyncDisplayKit is BSD-licensed. We also provide an additional patent grant. The files in the `/examples` directory are licensed under a separate license as specified in each file; documentation is licensed CC-BY-4.0. diff --git a/build.sh b/build.sh index 02a2ddb4ab..b9b5dfc2b4 100755 --- a/build.sh +++ b/build.sh @@ -36,14 +36,66 @@ if [ "$MODE" = "tests" ]; then exit 0 fi +if [ "$MODE" = "examples" ]; then + echo "Verifying that all AsyncDisplayKit examples compile." + #Update cocoapods repo + pod repo update master + + for example in examples/*/; do + echo "Building (examples) $example." + + if [ -f "${example}/Podfile" ]; then + echo "Using CocoaPods" + if [ -f "${example}/Podfile.lock" ]; then + rm "$example/Podfile.lock" + fi + rm -rf "$example/Pods" + pod install --project-directory=$example + + set -o pipefail && xcodebuild \ + -workspace "${example}/Sample.xcworkspace" \ + -scheme Sample \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + -derivedDataPath ~/ \ + build | xcpretty $FORMATTER + elif [ -f "${example}/Cartfile" ]; then + echo "Using Carthage" + local_repo=`pwd` + current_branch=`git rev-parse --abbrev-ref HEAD` + cd $example + + echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile" + carthage update --platform iOS + + set -o pipefail && xcodebuild \ + -project "Sample.xcodeproj" \ + -scheme Sample \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + build | xcpretty $FORMATTER + + cd ../.. + fi + done + trap - EXIT + exit 0 +fi + if [ "$MODE" = "examples-pt1" ]; then echo "Verifying that all AsyncDisplayKit examples compile." + #Update cocoapods repo + pod repo update master for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -6 | head); do echo "Building (examples-pt1) $example." if [ -f "${example}/Podfile" ]; then echo "Using CocoaPods" + if [ -f "${example}/Podfile.lock" ]; then + rm "$example/Podfile.lock" + fi + rm -rf "$example/Pods" pod install --project-directory=$example set -o pipefail && xcodebuild \ @@ -78,12 +130,18 @@ fi if [ "$MODE" = "examples-pt2" ]; then echo "Verifying that all AsyncDisplayKit examples compile." + #Update cocoapods repo + pod repo update master for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -12 | tail -6 | head); do echo "Building $example (examples-pt2)." if [ -f "${example}/Podfile" ]; then echo "Using CocoaPods" + if [ -f "${example}/Podfile.lock" ]; then + rm "$example/Podfile.lock" + fi + rm -rf "$example/Pods" pod install --project-directory=$example set -o pipefail && xcodebuild \ @@ -118,12 +176,18 @@ fi if [ "$MODE" = "examples-pt3" ]; then echo "Verifying that all AsyncDisplayKit examples compile." + #Update cocoapods repo + pod repo update master for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -7 | head); do echo "Building $example (examples-pt3)." if [ -f "${example}/Podfile" ]; then echo "Using CocoaPods" + if [ -f "${example}/Podfile.lock" ]; then + rm "$example/Podfile.lock" + fi + rm -rf "$example/Pods" pod install --project-directory=$example set -o pipefail && xcodebuild \ diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index a295f461e5..0000000000 --- a/docs/CNAME +++ /dev/null @@ -1,2 +0,0 @@ -asyncdisplaykit.org - diff --git a/docs/LICENSE.md b/docs/LICENSE.md deleted file mode 100644 index 4bb498ec63..0000000000 --- a/docs/LICENSE.md +++ /dev/null @@ -1,420 +0,0 @@ ---- -layout: page -title: License -permalink: /license/ ---- - -AsyncDisplayKit is free software under the BSD -license. - -Code examples and sample -projects are licensed as follows: - - This file provided by Facebook is for non-commercial testing and evaluation - purposes only. Facebook reserves all rights not expressly granted. - - 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. - -All other AsyncDisplayKit documentation is licensed CC-BY-4.0. - - Attribution 4.0 International - - ======================================================================= - - Creative Commons Corporation ("Creative Commons") is not a law firm and - does not provide legal services or legal advice. Distribution of - Creative Commons public licenses does not create a lawyer-client or - other relationship. Creative Commons makes its licenses and related - information available on an "as-is" basis. Creative Commons gives no - warranties regarding its licenses, any material licensed under their - terms and conditions, or any related information. Creative Commons - disclaims all liability for damages resulting from their use to the - fullest extent possible. - - Using Creative Commons Public Licenses - - Creative Commons public licenses provide a standard set of terms and - conditions that creators and other rights holders may use to share - original works of authorship and other material subject to copyright - and certain other rights specified in the public license below. The - following considerations are for informational purposes only, are not - exhaustive, and do not form part of our licenses. - - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors - - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More_considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees - - ======================================================================= - - Creative Commons Attribution 4.0 International Public License - - By exercising the Licensed Rights (defined below), You accept and agree - to be bound by the terms and conditions of this Creative Commons - Attribution 4.0 International Public License ("Public License"). To the - extent this Public License may be interpreted as a contract, You are - granted the Licensed Rights in consideration of Your acceptance of - these terms and conditions, and the Licensor grants You such rights in - consideration of benefits the Licensor receives from making the - Licensed Material available under these terms and conditions. - - - Section 1 -- Definitions. - - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - - c. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - - d. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - - e. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - - f. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - - g. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - - h. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - - i. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - - j. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - - k. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - - - Section 2 -- Scope. - - a. License grant. - - 1. Subject to the terms and conditions of this Public License, - the Licensor hereby grants You a worldwide, royalty-free, - non-sublicensable, non-exclusive, irrevocable license to - exercise the Licensed Rights in the Licensed Material to: - - a. reproduce and Share the Licensed Material, in whole or - in part; and - - b. produce, reproduce, and Share Adapted Material. - - 2. Exceptions and Limitations. For the avoidance of doubt, where - Exceptions and Limitations apply to Your use, this Public - License does not apply, and You do not need to comply with - its terms and conditions. - - 3. Term. The term of this Public License is specified in Section - 6(a). - - 4. Media and formats; technical modifications allowed. The - Licensor authorizes You to exercise the Licensed Rights in - all media and formats whether now known or hereafter created, - and to make technical modifications necessary to do so. The - Licensor waives and/or agrees not to assert any right or - authority to forbid You from making technical modifications - necessary to exercise the Licensed Rights, including - technical modifications necessary to circumvent Effective - Technological Measures. For purposes of this Public License, - simply making modifications authorized by this Section 2(a) - (4) never produces Adapted Material. - - 5. Downstream recipients. - - a. Offer from the Licensor -- Licensed Material. Every - recipient of the Licensed Material automatically - receives an offer from the Licensor to exercise the - Licensed Rights under the terms and conditions of this - Public License. - - b. No downstream restrictions. You may not offer or impose - any additional or different terms or conditions on, or - apply any Effective Technological Measures to, the - Licensed Material if doing so restricts exercise of the - Licensed Rights by any recipient of the Licensed - Material. - - 6. No endorsement. Nothing in this Public License constitutes or - may be construed as permission to assert or imply that You - are, or that Your use of the Licensed Material is, connected - with, or sponsored, endorsed, or granted official status by, - the Licensor or others designated to receive attribution as - provided in Section 3(a)(1)(A)(i). - - b. Other rights. - - 1. Moral rights, such as the right of integrity, are not - licensed under this Public License, nor are publicity, - privacy, and/or other similar personality rights; however, to - the extent possible, the Licensor waives and/or agrees not to - assert any such rights held by the Licensor to the limited - extent necessary to allow You to exercise the Licensed - Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this - Public License. - - 3. To the extent possible, the Licensor waives any right to - collect royalties from You for the exercise of the Licensed - Rights, whether directly or through a collecting society - under any voluntary or waivable statutory or compulsory - licensing scheme. In all other cases the Licensor expressly - reserves any right to collect such royalties. - - - Section 3 -- License Conditions. - - Your exercise of the Licensed Rights is expressly made subject to the - following conditions. - - a. Attribution. - - 1. If You Share the Licensed Material (including in modified - form), You must: - - a. retain the following if it is supplied by the Licensor - with the Licensed Material: - - i. identification of the creator(s) of the Licensed - Material and any others designated to receive - attribution, in any reasonable manner requested by - the Licensor (including by pseudonym if - designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of - warranties; - - v. a URI or hyperlink to the Licensed Material to the - extent reasonably practicable; - - b. indicate if You modified the Licensed Material and - retain an indication of any previous modifications; and - - c. indicate the Licensed Material is licensed under this - Public License, and include the text of, or the URI or - hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any - reasonable manner based on the medium, means, and context in - which You Share the Licensed Material. For example, it may be - reasonable to satisfy the conditions by providing a URI or - hyperlink to a resource that includes the required - information. - - 3. If requested by the Licensor, You must remove any of the - information required by Section 3(a)(1)(A) to the extent - reasonably practicable. - - 4. If You Share Adapted Material You produce, the Adapter's - License You apply must not prevent recipients of the Adapted - Material from complying with this Public License. - - - Section 4 -- Sui Generis Database Rights. - - Where the Licensed Rights include Sui Generis Database Rights that - apply to Your use of the Licensed Material: - - a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database; - - b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material; and - - c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. - - For the avoidance of doubt, this Section 4 supplements and does not - replace Your obligations under this Public License where the Licensed - Rights include other Copyright and Similar Rights. - - - Section 5 -- Disclaimer of Warranties and Limitation of Liability. - - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - - c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - - - Section 6 -- Term and Termination. - - a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. - - b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided - it is cured within 30 days of Your discovery of the - violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any - right the Licensor may have to seek remedies for Your violations - of this Public License. - - c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. - - - Section 7 -- Other Terms and Conditions. - - a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. - - - Section 8 -- Interpretation. - - a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - - c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - - d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. - - - ======================================================================= - - Creative Commons is not a party to its public licenses. - Notwithstanding, Creative Commons may elect to apply one of its public - licenses to material it publishes and in those instances will be - considered the "Licensor." Except for the limited purpose of indicating - that material is shared under a Creative Commons public license or as - otherwise permitted by the Creative Commons policies published at - creativecommons.org/policies, Creative Commons does not authorize the - use of the trademark "Creative Commons" or any other trademark or logo - of Creative Commons without its prior written consent including, - without limitation, in connection with any unauthorized modifications - to any of its public licenses or any other arrangements, - understandings, or agreements concerning use of licensed material. For - the avoidance of doubt, this paragraph does not form part of the public - licenses. - - Creative Commons may be contacted at creativecommons.org. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 05980d3818..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Documentation - -## Building - -You need Jekyll and appledoc. See `build.sh`. - -## License - -See LICENSE.md. diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 0d5f309f67..0000000000 --- a/docs/_config.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Site settings -title: AsyncDisplayKit -description: Smooth asynchronous user interfaces for iOS apps. -baseurl: "" -url: "http://asyncdisplaykit.org" - -# Build settings -highlighter: pygments -markdown: redcarpet - -exclude: -- README.md -- build.sh diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html deleted file mode 100644 index 2714963def..0000000000 --- a/docs/_includes/footer.html +++ /dev/null @@ -1,18 +0,0 @@ -
- -
- - -
- -
diff --git a/docs/_includes/head.html b/docs/_includes/head.html deleted file mode 100644 index a9e7efbdea..0000000000 --- a/docs/_includes/head.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - {% if page.title %}{{ page.title }} — {% endif %}AsyncDisplayKit - - - - - - - diff --git a/docs/_includes/header.html b/docs/_includes/header.html deleted file mode 100644 index aebb67cdf1..0000000000 --- a/docs/_includes/header.html +++ /dev/null @@ -1,25 +0,0 @@ - diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html deleted file mode 100644 index bdf5a388da..0000000000 --- a/docs/_layouts/default.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - {% include head.html %} - - - - {% include header.html %} - -
-
- {{ content }} -
-
- - {% include footer.html %} - - - - diff --git a/docs/_layouts/docs.html b/docs/_layouts/docs.html deleted file mode 100644 index 65c07040aa..0000000000 --- a/docs/_layouts/docs.html +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: default -sectionid: docs ---- -
- -
-

- {{ page.title }} - [edit] -

-
- -
- {{ content }} -
- -
- {% if page.prev %} - ← prev - {% endif %} - {% if page.next %} - next → - {% endif %} -
- -
diff --git a/docs/_layouts/page.html b/docs/_layouts/page.html deleted file mode 100644 index 8e7ccf7a15..0000000000 --- a/docs/_layouts/page.html +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: default ---- -
- - {% if page.shouldDisplayTitle %} -
-

{{ page.title }}

-
- {% endif %} - -
- {{ content }} -
- -
diff --git a/docs/_layouts/post.html b/docs/_layouts/post.html deleted file mode 100644 index 675596fb1c..0000000000 --- a/docs/_layouts/post.html +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: default ---- -
- -
-

{{ page.title }}

- -
- -
- {{ content }} -
- -
diff --git a/docs/_sass/_base.scss b/docs/_sass/_base.scss deleted file mode 100644 index 7d353be258..0000000000 --- a/docs/_sass/_base.scss +++ /dev/null @@ -1,205 +0,0 @@ -/** - * Reset some basic elements - */ -body, h1, h2, h3, h4, h5, h6, -p, blockquote, pre, hr, -dl, dd, ol, ul, figure { - margin: 0; - padding: 0; -} - - - -/** - * Basic styling - */ -body { - font-family: $base-font-family; - font-size: $base-font-size; - line-height: $base-line-height; - font-weight: 300; - color: $text-color; - background-color: $background-color; - -webkit-text-size-adjust: 100%; -} - - - -/** - * Set `margin-bottom` to maintain vertical rhythm - */ -h1, h2, h3, h4, h5, h6, -p, blockquote, pre, -ul, ol, dl, figure, -%vertical-rhythm { - margin-bottom: $spacing-unit / 2; -} - - - -/** - * Images - */ -img { - max-width: 100%; - vertical-align: middle; -} - - - -/** - * Figures - */ -figure > img { - display: block; -} - -figcaption { - font-size: $small-font-size; -} - - - -/** - * Lists - */ -ul, ol { - margin-left: $spacing-unit; -} - -li { - > ul, - > ol { - margin-bottom: 0; - } -} - - - -/** - * Headings - */ -h1, h2, h3, h4, h5, h6 { - font-weight: 300; -} - - - -/** - * Links - */ -a { - color: $brand-color; - text-decoration: none; - - &:visited { - color: darken($brand-color, 15%); - } - - &:hover { - color: $text-color; - text-decoration: underline; - } -} - - - -/** - * Blockquotes - */ -blockquote { - color: $grey-color; - border-left: 4px solid $grey-color-light; - padding-left: $spacing-unit / 2; - font-size: 18px; - letter-spacing: -1px; - font-style: italic; - - > :last-child { - margin-bottom: 0; - } -} - - - -/** - * Code formatting - */ -pre, -code { - font-family: Monaco, monospace; - font-size: 14px; - border: 1px solid #afe4ff; - border-radius: 3px; - background-color: #fafdff; -} - -code { - padding: 1px 5px; -} - -pre { - padding: 8px 12px; - overflow-x: scroll; - - > code { - border: 0; - padding-right: 0; - padding-left: 0; - } -} - - - -/** - * Wrapper - */ -.wrapper { - max-width: -webkit-calc(800px - (#{$spacing-unit} * 2)); - max-width: calc(800px - (#{$spacing-unit} * 2)); - margin-right: auto; - margin-left: auto; - padding-right: $spacing-unit; - padding-left: $spacing-unit; - @extend %clearfix; - - @include media-query($on-laptop) { - max-width: -webkit-calc(800px - (#{$spacing-unit})); - max-width: calc(800px - (#{$spacing-unit})); - padding-right: $spacing-unit / 2; - padding-left: $spacing-unit / 2; - } -} - - - -/** - * Clearfix - */ -%clearfix { - - &:after { - content: ""; - display: table; - clear: both; - } -} - - - -/** - * Icons - */ -.icon { - - > svg { - display: inline-block; - width: 16px; - height: 16px; - vertical-align: middle; - - path { - fill: $grey-color; - } - } -} diff --git a/docs/_sass/_layout.scss b/docs/_sass/_layout.scss deleted file mode 100644 index 2eb740fa67..0000000000 --- a/docs/_sass/_layout.scss +++ /dev/null @@ -1,265 +0,0 @@ -/** - * Site header - */ -.site-header { - border-top: 5px solid $grey-color-dark; - border-bottom: 1px solid $grey-color-light; - min-height: 56px; - background-color: #f8f8f8; - - // Positioning context for the mobile navigation icon - position: relative; -} - -.site-title { - font-size: 26px; - line-height: 56px; - letter-spacing: -1px; - margin-bottom: 0; - float: left; - - &, - &:visited { - color: $grey-color-dark; - } - &:hover { - text-decoration: none; - } -} - -.site-nav { - float: right; - line-height: 56px; - - .menu-icon { - display: none; - } - - .page-link { - color: $grey-color; - line-height: $base-line-height; - - // Gaps between nav items, but not on the first one - &:not(:first-child) { - margin-left: 20px; - } - - &:hover { - color: $text-color; - } - } - - .page-link-active { - color: $text-color; - } - - @include media-query($on-palm) { - position: absolute; - top: 9px; - right: 30px; - background-color: $background-color; - border: 1px solid $grey-color-light; - border-radius: 5px; - text-align: right; - - .menu-icon { - display: block; - float: right; - width: 36px; - height: 26px; - line-height: 0; - padding-top: 10px; - text-align: center; - - > svg { - width: 18px; - height: 15px; - - path { - fill: $grey-color-dark; - } - } - } - - .trigger { - clear: both; - display: none; - } - - &:hover .trigger { - display: block; - padding-bottom: 5px; - } - - .page-link { - display: block; - padding: 5px 10px; - } - } -} - - - -/** - * Site footer - */ -.site-footer { - border-top: 1px solid $grey-color-light; - padding: $spacing-unit 0; -} - -.footer-heading { - font-size: 18px; - margin-bottom: $spacing-unit / 2; -} - -.contact-list, -.social-media-list { - list-style: none; - margin-left: 0; -} - -.footer-col-wrapper { - font-size: 11px; - color: $grey-color; - margin-left: -$spacing-unit / 2; - @extend %clearfix; -} - -.footer-col { - float: left; - margin-bottom: $spacing-unit / 2; - padding-left: $spacing-unit / 2; -} - -.footer-col-left { - width: -webkit-calc(50% - (#{$spacing-unit} / 2)); - width: calc(50% - (#{$spacing-unit} / 2)); -} - -.footer-col-right { - text-align: right; - width: -webkit-calc(50% - (#{$spacing-unit} / 2)); - width: calc(50% - (#{$spacing-unit} / 2)); -} - -@include media-query($on-laptop) { - .footer-col-left { - width: -webkit-calc(50% - (#{$spacing-unit} / 2)); - width: calc(50% - (#{$spacing-unit} / 2)); - } - - .footer-col-right { - width: -webkit-calc(100% - (#{$spacing-unit} / 2)); - width: calc(100% - (#{$spacing-unit} / 2)); - } -} - -@include media-query($on-palm) { - .footer-col { - float: none; - width: -webkit-calc(100% - (#{$spacing-unit} / 2)); - width: calc(100% - (#{$spacing-unit} / 2)); - } -} - - - -/** - * Page content - */ -.page-content { - padding: $spacing-unit 0; - background-color: white; -} - -.page-heading { - font-size: 20px; -} - -.post-list { - margin-left: 0; - list-style: none; - - > li { - margin-bottom: $spacing-unit; - } -} - -.post-meta { - font-size: $small-font-size; - color: $grey-color; -} - -.post-link { - display: block; - font-size: 24px; -} - - - -/** - * Posts - */ -.post-header { - margin-bottom: $spacing-unit; -} - -.post-title { - font-size: 42px; - letter-spacing: -1px; - line-height: 1; - - @include media-query($on-laptop) { - font-size: 36px; - } - - .edit-page-link { - font-size: 18px; - } -} - -.post-content { - margin-bottom: $spacing-unit; - - h2 { - font-size: 32px; - - @include media-query($on-laptop) { - font-size: 28px; - } - } - - h3 { - font-size: 26px; - - @include media-query($on-laptop) { - font-size: 22px; - } - } - - h4 { - font-size: 20px; - - @include media-query($on-laptop) { - font-size: 18px; - } - } -} - - - -/** - * Docs - */ -.docs-prevnext { - @extend %clearfix; -} - -.docs-prev { - float: left; -} - -.docs-next { - float: right; -} diff --git a/docs/_sass/_syntax-highlighting.scss b/docs/_sass/_syntax-highlighting.scss deleted file mode 100644 index 3758fdb458..0000000000 --- a/docs/_sass/_syntax-highlighting.scss +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Syntax highlighting styles - */ - -/* not official Xcode colors, but looks better on the web */ -$xc-black: black; -$xc-green: #008d14; -$xc-red: #b72748; -$xc-blue: #103ffb; -$xc-turquoise: #3a95ba; - -.highlight { - background: #fff; - @extend %vertical-rhythm; - - .c { color: $xc-green; font-style: italic } // Comment - .err { color: #a61717; background-color: #e3d2d2 } // Error - .k { color: $xc-blue} // Keyword - .o { } // Operator - .cm { color: $xc-green; font-style: italic } // Comment.Multiline - .cp { color: $xc-red} // Comment.Preproc - .c1 { color: $xc-green; font-style: italic } // Comment.Single - .cs { color: $xc-green; font-weight: bold; font-style: italic } // Comment.Special - .gd { color: #000; background-color: #fdd } // Generic.Deleted - .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific - .ge { font-style: italic } // Generic.Emph - .gr { color: #a00 } // Generic.Error - .gh { color: #999 } // Generic.Heading - .gi { color: #000; background-color: #dfd } // Generic.Inserted - .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific - .go { color: #888 } // Generic.Output - .gp { color: #555 } // Generic.Prompt - .gs { font-weight: bold } // Generic.Strong - .gu { color: #aaa } // Generic.Subheading - .gt { color: #a00 } // Generic.Traceback - .kc { color: orange} // Keyword.Constant - .kd { color: orange} // Keyword.Declaration - .kp { color: $xc-green} // Keyword.Pseudo - .kr { color: $xc-green} // Keyword.Reserved - .kt { color: $xc-blue} // Keyword.Type - .m { color: orange } // Literal.Number - .s { color: $xc-red } // Literal.String - .na { color: orange } // Name.Attribute - .nb { color: $xc-blue } // Name.Builtin - .nc { color: $xc-turquoise } // Name.Class - .no { color: orange } // Name.Constant - .ni { color: orange } // Name.Entity - .ne { color: orange } // Name.Exception - .nf { } // Name.Function - .nn { color: orange } // Name.Namespace - .nt { color: orange } // Name.Tag - .nv { } // Name.Variable - .ow { } // Operator.Word - .w { color: #bbb } // Text.Whitespace - .mf {} // Literal.Number.Float - .mh { color: $xc-black } // Literal.Number.Hex - .mi { color: $xc-black } // Literal.Number.Integer - .mo { color: $xc-black } // Literal.Number.Oct - .il { color: $xc-black } // Literal.Number.Integer.Long - .sb { color: #d14 } // Literal.String.Backtick - .sc { color: #d14 } // Literal.String.Char - .sd { color: #d14 } // Literal.String.Doc - .s2 { color: #d14 } // Literal.String.Double - .se { color: #d14 } // Literal.String.Escape - .sh { color: #d14 } // Literal.String.Heredoc - .si { color: #d14 } // Literal.String.Interpol - .sx { color: #d14 } // Literal.String.Other - .sr { color: orange } // Literal.String.Regex - .s1 { color: $xc-red } // Literal.String.Single - .ss { color: $xc-red } // Literal.String.Symbol - .bp { color: $xc-turquoise } // Name.Builtin.Pseudo - .vc { color: $xc-turquoise } // Name.Variable.Class - .vg { color: $xc-black } // Name.Variable.Global - .vi { color: orange } // Name.Variable.Instance - .nl { color: $xc-turquoise } -} diff --git a/docs/assets/guide/1-shuffle-crop.png b/docs/assets/guide/1-shuffle-crop.png deleted file mode 100644 index d1e0a83b0c..0000000000 Binary files a/docs/assets/guide/1-shuffle-crop.png and /dev/null differ diff --git a/docs/assets/guide/1-shuffle.png b/docs/assets/guide/1-shuffle.png deleted file mode 100644 index 9188eebf8a..0000000000 Binary files a/docs/assets/guide/1-shuffle.png and /dev/null differ diff --git a/docs/assets/logo-square.png b/docs/assets/logo-square.png deleted file mode 100755 index 82ad66c69e..0000000000 Binary files a/docs/assets/logo-square.png and /dev/null differ diff --git a/docs/assets/logo.png b/docs/assets/logo.png deleted file mode 100755 index dce1ebc950..0000000000 Binary files a/docs/assets/logo.png and /dev/null differ diff --git a/docs/assets/node-view-layer.png b/docs/assets/node-view-layer.png deleted file mode 100755 index 544294af8f..0000000000 Binary files a/docs/assets/node-view-layer.png and /dev/null differ diff --git a/docs/build.sh b/docs/build.sh deleted file mode 100755 index c532bb0d6b..0000000000 --- a/docs/build.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -set -e - -HEADERS=`ls ../AsyncDisplayKit/*.h ../AsyncDisplayKit/Details/ASRangeController.h ../AsyncDisplayKit/Layout/*.h` - -rm -rf htdocs appledoc - -jekyll build --destination htdocs - -appledoc \ - --no-create-docset \ - --create-html \ - --exit-threshold 2 \ - --no-repeat-first-par \ - --no-merge-categories \ - --explicit-crossref \ - --warn-missing-output-path \ - --warn-missing-company-id \ - --warn-undocumented-object \ - --warn-undocumented-member \ - --warn-empty-description \ - --warn-unknown-directive \ - --warn-invalid-crossref \ - --warn-missing-arg \ - --project-name AsyncDisplayKit \ - --project-company Facebook \ - --company-id "com.facebook" \ - --output appledoc \ - $HEADERS - -mv appledoc/html htdocs/appledoc - -rmdir appledoc diff --git a/docs/css/main.scss b/docs/css/main.scss deleted file mode 100755 index 4417ff0713..0000000000 --- a/docs/css/main.scss +++ /dev/null @@ -1,49 +0,0 @@ ---- -# Only the main Sass file needs front matter (the dashes are enough) ---- -@charset "utf-8"; - - - -// Our variables -$base-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -$base-font-size: 16px; -$small-font-size: $base-font-size * 0.875; -$base-line-height: 1.5; - -$spacing-unit: 30px; - -$text-color: #111; -$background-color: #f8f8f8; -$brand-color: #21b6ff; - -$grey-color: #828282; -$grey-color-light: lighten($grey-color, 40%); -$grey-color-dark: darken($grey-color, 25%); - -$on-palm: 600px; -$on-laptop: 800px; - - - -// Using media queries with like this: -// @include media-query($palm) { -// .wrapper { -// padding-right: $spacing-unit / 2; -// padding-left: $spacing-unit / 2; -// } -// } -@mixin media-query($device) { - @media screen and (max-width: $device) { - @content; - } -} - - - -// Import partials from `sass_dir` (defaults to `_sass`) -@import - "base", - "layout", - "syntax-highlighting" -; diff --git a/docs/guide/1-introduction.md b/docs/guide/1-introduction.md deleted file mode 100644 index d923c0a215..0000000000 --- a/docs/guide/1-introduction.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -layout: docs -title: Getting started -permalink: /guide/ -next: guide/2/ ---- - -## Concepts - -AsyncDisplayKit's basic unit is the *node*. ASDisplayNode is an abstraction -over UIView, which in turn is an abstraction over CALayer. Unlike views, which -can only be used on the main thread, nodes are thread-safe: you can -instantiate and configure entire hierarchies of them in parallel on background -threads. - -To keep its user interface smooth and responsive, your app should render at 60 -frames per second — the gold standard on iOS. This means the main thread -has one-sixtieth of a second to push each frame. That's 16 milliseconds to -execute all layout and drawing code! And because of system overhead, your code -usually has less than ten milliseconds to run before it causes a frame drop. - -AsyncDisplayKit lets you move image decoding, text sizing and rendering, and -other expensive UI operations off the main thread. It has other tricks up its -sleeve too... but we'll get to that later. :] - -## Nodes as drop-in view replacements - -If you're used to working with views, you already know how to use nodes. The -node API is similar to UIView's, with some additional conveniences — for -example, you can access common CALayer properties directly. To add a node to -an existing view or layer hierarchy, use its `node.view` or `node.layer`. - -AsyncDisplayKit's core components include: - -* *ASDisplayNode*. Counterpart to UIView — subclass to make custom nodes. -* *ASControlNode*. Analogous to UIControl — subclass to make buttons. -* *ASImageNode*. Like UIImageView — decodes images asynchronously. -* *ASTextNode*. Like UITextView — built on TextKit with full-featured - rich text support. -* *ASTableView* and *ASCollectionView*. UITableView and UICollectionView - subclasses that support nodes. - -You can use these as drop-in replacements for their UIKit counterparts. While -ASDK works most effectively with fully node-based hierarchies, even replacing -individual views with nodes can improve performance. - -Let's look at an example. - -We'll start out by using nodes synchronously on the main thread — the -same way you already use views. This code is a familiar sight in custom view -controller `-loadView` implementations: - -```objective-c -_imageView = [[UIImageView alloc] init]; -_imageView.image = [UIImage imageNamed:@"hello"]; -_imageView.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f); -[self.view addSubview:_imageView]; -``` - -We can replace it with the following node-based code: - -```objective-c -_imageNode = [[ASImageNode alloc] init]; -_imageNode.backgroundColor = [UIColor lightGrayColor]; -_imageNode.image = [UIImage imageNamed:@"hello"]; -_imageNode.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f); -[self.view addSubview:_imageNode.view]; -``` - -This doesn't take advantage of ASDK's asynchronous sizing and layout -functionality, but it's already an improvement. The first block of code -synchronously decodes `hello.png` on the main thread; the second starts -decoding the image on a background thread, possibly on a different CPU core. - -(Note that we're setting a placeholder background colour on the node, "holding -its place" onscreen until the real content appears. This works well with -images but less so with text — people expect text to appear instantly, -with images loading in after a slight delay. We'll discuss techniques to -improve this later on.) - -## Button nodes - -ASImageNode and ASTextNode both inherit from ASControlNode, so you can use them -as buttons. Let's say we're making a music player and we want to add a -(non-skeuomorphic, iOS 7-style) shuffle button: - -[![shuffle]({{ site.baseurl }}/assets/guide/1-shuffle-crop.png)]({{ site.baseurl }}/assets/guide/1-shuffle.png) - -Our view controller will look something like this: - -```objective-c -- (void)viewDidLoad -{ - [super viewDidLoad]; - - // attribute a string - NSDictionary *attrs = @{ - NSFontAttributeName: [UIFont systemFontOfSize:12.0f], - NSForegroundColorAttributeName: [UIColor redColor], - }; - NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"shuffle" - attributes:attrs]; - - // create the node - _shuffleNode = [[ASTextNode alloc] init]; - _shuffleNode.attributedString = string; - - // configure the button - _shuffleNode.userInteractionEnabled = YES; // opt into touch handling - [_shuffleNode addTarget:self - action:@selector(buttonTapped:) - forControlEvents:ASControlNodeEventTouchUpInside]; - - // size all the things - CGRect b = self.view.bounds; // convenience - CGSize size = [_shuffleNode measure:CGSizeMake(b.size.width, FLT_MAX)]; - CGPoint origin = CGPointMake(roundf( (b.size.width - size.width) / 2.0f ), - roundf( (b.size.height - size.height) / 2.0f )); - _shuffleNode.frame = (CGRect){ origin, size }; - - // add to our view - [self.view addSubview:_shuffleNode.view]; -} - -- (void)buttonTapped:(id)sender -{ - NSLog(@"tapped!"); -} -``` - -This works as you would expect. Unfortunately, this button is only 14½ -points tall — nowhere near the standard 44×44 minimum tap target -size — and it's very difficult to tap. We could solve this by -subclassing the text node and overriding `-hitTest:withEvent:`. We could even -force the text view to have a minimum height during layout. But wouldn't it be -nice if there were a more elegant way? - -```objective-c - // size all the things - /* ... */ - - // make the tap target taller - CGFloat extendY = roundf( (44.0f - size.height) / 2.0f ); - _shuffleNode.hitTestSlop = UIEdgeInsetsMake(-extendY, 0.0f, -extendY, 0.0f); -``` - -Et voilà! *Hit-test slops* work on all nodes, and are a nice example of what -this new abstraction enables. - -Next up, making your own nodes! diff --git a/docs/guide/2-custom-nodes.md b/docs/guide/2-custom-nodes.md deleted file mode 100644 index 6d67ed3a05..0000000000 --- a/docs/guide/2-custom-nodes.md +++ /dev/null @@ -1,211 +0,0 @@ ---- -layout: docs -title: Custom nodes -permalink: /guide/2/ -prev: guide/ -next: guide/3/ ---- - -## View hierarchies - -Sizing and layout of custom view hierarchies are typically done all at once on -the main thread. For example, a custom UIView that minimally encloses a text -view and an image view might look like this: - -```objective-c -- (CGSize)sizeThatFits:(CGSize)size -{ - // size the image - CGSize imageSize = [_imageView sizeThatFits:size]; - - // size the text view - CGSize maxTextSize = CGSizeMake(size.width - imageSize.width, size.height); - CGSize textSize = [_textView sizeThatFits:maxTextSize]; - - // make sure everything fits - CGFloat minHeight = MAX(imageSize.height, textSize.height); - return CGSizeMake(size.width, minHeight); -} - -- (void)layoutSubviews -{ - CGSize size = self.bounds.size; // convenience - - // size and layout the image - CGSize imageSize = [_imageView sizeThatFits:size]; - _imageView.frame = CGRectMake(size.width - imageSize.width, 0.0f, - imageSize.width, imageSize.height); - - // size and layout the text view - CGSize maxTextSize = CGSizeMake(size.width - imageSize.width, size.height); - CGSize textSize = [_textView sizeThatFits:maxTextSize]; - _textView.frame = (CGRect){ CGPointZero, textSize }; -} -``` - -This isn't ideal. We're sizing our subviews twice — once to figure out -how big our view needs to be and once when laying it out — and while our -layout arithmetic is cheap and quick, we're also blocking the main thread on -expensive text sizing. - -We could improve the situation by manually cacheing our subviews' sizes, but -that solution comes with its own set of problems. Just adding `_imageSize` and -`_textSize` ivars wouldn't be enough: for example, if the text were to change, -we'd need to recompute its size. The boilerplate would quickly become -untenable. - -Further, even with a cache, we'll still be blocking the main thread on sizing -*sometimes*. We could try to shift sizing to a background thread with -`dispatch_async()`, but even if our own code is thread-safe, UIView methods are -documented to [only work on the main -thread](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/index.html): - -> Manipulations to your application’s user interface must occur on the main -> thread. Thus, you should always call the methods of the UIView class from -> code running in the main thread of your application. The only time this may -> not be strictly necessary is when creating the view object itself but all -> other manipulations should occur on the main thread. - -This is a pretty deep rabbit hole. We could attempt to work around the fact -that UILabels and UITextViews cannot safely be sized on background threads by -manually creating a TextKit stack and sizing the text ourselves... but that's a -laborious duplication of work. Further, if UITextView's layout behaviour -changes in an iOS update, our sizing code will break. (And did we mention that -TextKit isn't thread-safe either?) - -## Node hierarchies - -Enter AsyncDisplayKit. Our custom node looks like this: - -```objective-c -#import - -... - -// perform expensive sizing operations on a background thread -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - // size the image - CGSize imageSize = [_imageNode measure:constrainedSize]; - - // size the text node - CGSize maxTextSize = CGSizeMake(constrainedSize.width - imageSize.width, - constrainedSize.height); - CGSize textSize = [_textNode measure:maxTextSize]; - - // make sure everything fits - CGFloat minHeight = MAX(imageSize.height, textSize.height); - return CGSizeMake(constrainedSize.width, minHeight); -} - -// do as little work as possible in main-thread layout -- (void)layout -{ - // layout the image using its cached size - CGSize imageSize = _imageNode.calculatedSize; - _imageNode.frame = CGRectMake(self.bounds.size.width - imageSize.width, 0.0f, - imageSize.width, imageSize.height); - - // layout the text view using its cached size - CGSize textSize = _textNode.calculatedSize; - _textNode.frame = (CGRect){ CGPointZero, textSize }; -} -``` - -ASImageNode and ASTextNode, like the rest of AsyncDisplayKit, are thread-safe, -so we can size them on background threads. The `-measure:` method is like -`-sizeThatFits:`, but with side effects: it caches both the argument -(`constrainedSizeForCalculatedSize`) and the result (`calculatedSize`) for -quick access later on — like in our now-snappy `-layout` implementation. - -As you can see, node hierarchies are sized and laid out in much the same way as -their view counterparts. Custom nodes do need to be written with a few things -in mind: - -* Nodes must recursively measure all of their subnodes in their - `-calculateSizeThatFits:` implementations. Note that the `-measure:` - machinery will only call `-calculateSizeThatFits:` if a new measurement pass - is needed (e.g., if the constrained size has changed). - -* Nodes should perform any other expensive pre-layout calculations in - `-calculateSizeThatFits:`, cacheing useful intermediate results in ivars as - appropriate. - -* Nodes should call `[self invalidateCalculatedSize]` when necessary. For - example, ASTextNode invalidates its calculated size when its - `attributedString` property is changed. - -For more examples of custom sizing and layout, along with a demo of -ASTextNode's features, check out `BlurbNode` and `KittenNode` in the -[Kittens](https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens) -sample project. - -## Custom drawing - -To guarantee thread safety in its highly-concurrent drawing system, the node -drawing API diverges substantially from UIView's. Instead of implementing -`-drawRect:`, you must: - -1. Define an internal "draw parameters" class for your custom node. This - class should be able to store any state your node needs to draw itself - — it can be a plain old NSObject or even a dictionary. - -2. Return a configured instance of your draw parameters class in - `-drawParametersForAsyncLayer:`. This method will always be called on the - main thread. - -3. Implement either `+drawRect:withParameters:isCancelled:isRasterizing:` or - `+displayWithParameters:isCancelled:`. Note that these are *class* methods - that will not have access to your node's state — only the draw - parameters object. They can be called on any thread and must be - thread-safe. - -For example, this node will draw a rainbow: - -```objective-c -@interface RainbowNode : ASDisplayNode -@end - -@implementation RainbowNode - -+ (void)drawRect:(CGRect)bounds - withParameters:(id)parameters - isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock - isRasterizing:(BOOL)isRasterizing -{ - // clear the backing store, but only if we're not rasterising into another layer - if (!isRasterizing) { - [[UIColor whiteColor] set]; - UIRectFill(bounds); - } - - // UIColor sadly lacks +indigoColor and +violetColor methods - NSArray *colors = @[ [UIColor redColor], - [UIColor orangeColor], - [UIColor yellowColor], - [UIColor greenColor], - [UIColor blueColor], - [UIColor purpleColor] ]; - CGFloat stripeHeight = roundf(bounds.size.height / (float)colors.count); - - // draw the stripes - for (UIColor *color in colors) { - CGRect stripe = CGRectZero; - CGRectDivide(bounds, &stripe, &bounds, stripeHeight, CGRectMinYEdge); - [color set]; - UIRectFill(stripe); - } -} - -@end -``` - -This could easily be extended to support vertical rainbows too, by adding a -`vertical` property to the node, exporting it in -`-drawParametersForAsyncLayer:`, and referencing it in -`+drawRect:withParameters:isCancelled:isRasterizing:`. More-complex nodes can -be supported in much the same way. - -For more on custom nodes, check out the [subclassing -header](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASDisplayNode%2BSubclasses.h) -or read on! diff --git a/docs/guide/3-asynchronous-display.md b/docs/guide/3-asynchronous-display.md deleted file mode 100644 index acc9f37911..0000000000 --- a/docs/guide/3-asynchronous-display.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -layout: docs -title: Asynchronous display -permalink: /guide/3/ -prev: guide/2/ -next: guide/4/ ---- - -## Realistic placeholders - -Nodes need to complete both a *measurement pass* and a *display pass* before -they're fully rendered. It's possible to force either step to happen -synchronously: call `-measure:` in `-layoutSubviews` to perform sizing on the -main thread, or set a node's `displaysAsynchronously` flag to NO to disable -ASDK's async display machinery. (AsyncDisplayKit can still improve your app's -performance even when rendering fully synchronously — more on that -later!) - -The recommended way to use ASDK is to only add nodes to your view hierarchy -once they've been sized. This avoids unsightly layout changes as the -measurement pass completes, but if you enable asynchronous display, it will -always be possible for a node to appear onscreen before its content has fully -rendered. We'll discuss techniques to minimise this shortly, but you should -take it into account and include *realistic placeholders* in your app designs. - -Once its measurement pass has completed, a node can accurately place all of its -subnodes onscreen — they'll just be blank. The easiest way to make a -realistic placeholder is to set static background colours on your subnodes. -This effect looks better than generic placeholder images because it varies -based on the content being loaded, and it works particularly well for opaque -images. You can also create visually-appealing placeholder nodes, like the -shimmery lines representing text in Paper as its stories are loaded, and swap -them out with your content nodes once they've finished displaying. - -## Working range - -So far, we've only discussed asynchronous sizing: toss a "create a node -hierarchy and measure it" block onto a background thread, then trampoline to -the main thread to add it to the view hierarchy when that's done. Ideally, as -much content as possible should be fully-rendered and ready to go as soon as -the user scrolls to it. This requires triggering display passes in advance. - -If your app's content is in a scroll view or can be paged through, like -Instagram's main feed or Paper's story strip, the solution is a *working -range*. A working range controller tracks the *visible range*, the subset of -content that's currently visible onscreen, and enqueues asynchronous rendering -for the next few screenfuls of content. As the user scrolls, a screenful or -two of previous content is preserved; the rest is cleared to conserve memory. -If she starts scrolling in the other direction, the working range trashes its -render queue and starts pre-rendering in the new direction of scroll — -and because of the buffer of previous content, this entire process will -typically be invisible. - -AsyncDisplayKit includes a generic working range controller, -`ASRangeController`. Its working range size can be tuned depending on your -app: if your nodes are simple, even an iPhone 4 can maintain a substantial -working range, but heavyweight nodes like Facebook stories are expensive and -need to be pruned quickly. - -```objective-c -ASRangeController *rangeController = [[ASRangeController alloc] init]; -rangeController.tuningParameters = (ASRangeTuningParameters){ - .leadingBufferScreenfuls = 2.0f; // two screenfuls in the direction of scroll - .trailingBufferScreenfuls = 0.5f; // one-half screenful in the other direction -}; -``` - -If you use a working range, you should profile your app and consider tuning it -differently on a per-device basis. iPhone 4 has 512MB of RAM and a single-core -A4 chipset, while iPhone 6 has 1GB of RAM and the orders-of-magnitude-faster -multicore A8 — and if your app supports iOS 7, it will be used on both. - -## ASTableView - -ASRangeController manages working ranges, but doesn't actually display content. -If your content is currently rendered in a UITableView, you can convert it to -use `ASTableView` and custom nodes — just subclass `ASCellNode` instead -of ASDisplayNode. ASTableView is a UITableView subclass that integrates -node-based cells and a working range. - -ASTableView doesn't let cells onscreen until their underlying nodes have been -sized, and as such can fully benefit from realistic placeholders. Its API is -very similar to UITableView (see the -[Kittens](https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens) -sample project for an example), with some key changes: - -* Rather than setting the table view's `.delegate` and `.dataSource`, you set - its `.asyncDelegate` and `.asyncDataSource`. See - [ASTableView.h](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASTableView.h) - for how its delegate and data source protocols differ from UITableView's. - -* Instead of implementing `-tableView:cellForRowAtIndexPath:`, your data - source must implement `-tableView:nodeForRowAtIndexPath:`. This method must - be thread-safe and should not implement reuse. Unlike the UITableView - version, it won't be called when the row is about to display. - -* `-tableView:heightForRowAtIndexPath:` has been removed — ASTableView - lets your cell nodes size themselves. This means you no longer have to - manually duplicate or factor out layout and sizing logic for - dynamically-sized UITableViewCells! - -Next up, how to get the most out of ASDK in your app. diff --git a/docs/guide/4-making-the-most-of-asdk.md b/docs/guide/4-making-the-most-of-asdk.md deleted file mode 100644 index f35ad900b4..0000000000 --- a/docs/guide/4-making-the-most-of-asdk.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -layout: docs -title: Making the most of AsyncDisplayKit -permalink: /guide/4/ -prev: guide/3/ -next: guide/5/ ---- - -## A note on optimisation - -AsyncDisplayKit is powerful and flexible, but it is not a panacea. If your app -has a complex image- or text-heavy user interface, ASDK can definitely help -improve its performance — but if you're blocking the main thread on -network requests, you should consider rearchitecting a few things first. :] - -So why is it worthwhile to change the way we do view layout and rendering, -given that UIKit has always been locked to the main thread and performant iOS -apps have been shipping since iPhone's launch? - -### Modern animations - -Until iOS 7, static animations (à la `+[UIView -animateWithDuration:animations:]`) were the standard. The post-skeuomorphism -redesign brought with it highly-interactive, physics-based animations, with -springs joining the ranks of constant animation functions like -`UIViewAnimationOptionCurveEaseInOut`. - -Classic animations aren't actually executed in your app. They're executed -out-of-process, in the high-priority Core Animation render server. Thanks to -pre-emptive multitasking, an app can block its main thread continuously without -causing the animation to drop a single frame. - -Critically, dynamic animations can't be offloaded the same way, and both -[pop](https://github.com/facebook/pop) and UIKit Dynamics execute physics -simulations on your app's main thread. This is because executing arbitrary -code in the render server would introduce unacceptable latency, even if it -could be done securely. - -Physics-based animations are often interactive, letting you start an animation -and interrupt it before it completes. Paper lets you fling objects across the -screen and catch them before they land, or grab a view that's being pulled by a -spring and tear it off. This requires processing touch events and updating -animation targets in realtime — even short delays for inter-process -communication would shatter the illusion. - -(Fun fact: Inertial scrolling is also a physics animation! UIScrollView has -always implemented its animations on the main thread, which is why stuttery -scrolling is the hallmark of a slow app.) - -### The main-thread bottleneck - -Physics animations aren't the only thing that need to happen on the main -thread. The main thread's [run -loop](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/multithreading/runloopmanagement/runloopmanagement.html) -is responsible for handling touch events and initiating drawing operations -— and with UIKit in the mix, it also has to render text, decode images, -and do any other custom drawing (e.g., using Core Graphics). - -If an iteration of the main thread's run loop takes too long, it will drop an -animation frame and may fail to handle touch events in time. Each iteration of -the run loop must complete within 16ms in order to drive 60fps animations, and -your own code typically has less than 10ms to execute. This means that the -best way to keep your app smooth and responsive is to do as little work on the -main thread as possible. - -What's more, the main thread only executes on one core! Single-threaded view -hierarchies can't take advantage of the multicore CPUs in all modern iOS -devices. This is important for more than just performance reasons — it's -also critical for battery life. Running the CPU on all cores for a short time -is preferable to running one core for an extended amount of time: if the -processor can *race to sleep* by finishing its work and idling faster, it can -spend more time in a low-power mode, improving battery life. - -## When to go asynchronous - -AsyncDisplayKit really shines when used fully asynchronously, shifting both -measurement and rendering passes off the main thread and onto multiple cores. -This requires a completely node-based hierarchy. Just as degrading from -UIViews to CALayers disables view-specific functionality like touch handling -from that point on, degrading from nodes to views disables async behaviour. - -You don't, however, need to convert your app's entire view hierarchy to nodes. -In fact, you shouldn't! Asynchronously bringing up your app's core UI, like -navigation elements or tab bars, would be a very confusing experience. Those -elements of your apps can still be nodes, but should be fully synchronous to -guarantee a fully-usable interface as quickly as possible. (This is why -UIWindow has no node equivalent.) - -There are two key situations where asynchronous hierarchies excel: - -1. *Parallelisation*. Measuring and rendering UITableViewCells (or your app's - equivalent, e.g., story cards in Paper) are embarrassingly parallel - problems. Table cells typically have a fixed width and variable height - determined by their contents — the argument to `-measure:` for one - cell doesn't depend on any other cells' calculations, so we can enqueue an - arbitrary number of cell measurement passes at once. - -2. *Preloading*. An app with five tabs should synchronously load the first - one so content is ready to go as quickly as possible. Once this is - complete and the CPU is idle, why not asynchronously prepare the other tabs - so the user doesn't have to wait? This is inconvenient with views, but - very easy with nodes. - -Paper's asynchronous rendering starts at the story strip. You should profile -your app and watch how people use it in the wild to decide what combination of -synchrony and asynchrony yields the best user experience. - -## Additional optimisations - -Complex hierarchies — even when rendered asynchronously — can -impose a cost because of the sheer number of views involved. Working around -this can be painful, but AsyncDisplayKit makes it easy! - -* *Layer-backing*. In some cases, you can substantially improve your app's - performance by using layers instead of views. Manually converting - view-based code to layers is laborious due to the difference in APIs. - Worse, if at some point you need to enable touch handling or other - view-specific functionality, you have to manually convert everything back - (and risk regressions!). - - With nodes, converting an entire subtree from views to layers is as simple - as... - - rootNode.layerBacked = YES; - - ...and if you need to go back, it's as simple as deleting one line. We - recommend enabling layer-backing as a matter of course in any custom node - that doesn't need touch handling. - -* *Precompositing*. Flattening an entire view hierarchy into a single layer - also improves performance, but comes with a hit to maintainability and - hierarchy-based reasoning. Nodes can do this for you too! - - rootNode.shouldRasterizeDescendants = YES; - - ...will cause the entire node hierarchy from that point on to be rendered - into one layer. - -Next up: AsyncDisplayKit, under the hood. diff --git a/docs/guide/5-under-the-hood.md b/docs/guide/5-under-the-hood.md deleted file mode 100644 index 01229ec3ff..0000000000 --- a/docs/guide/5-under-the-hood.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -layout: docs -title: Under the hood -permalink: /guide/5/ -prev: guide/4/ ---- - -## Node architecture - -*(Skip to the next section if you're not interested in AsyncDisplayKit implementation details.)* - -We've described nodes as an abstraction over views and layers, and shown how to -interact with the underlying UIViews and CALayers when necessary. Nodes don't -wrap or vend their UIKit counterparts, though — an ASImageNode's `.view` -is not a UIImageView! So how do nodes work? - -**NOTE:** Classes whose names begin with `_` are private. Don't use them -directly! - -Creating a node doesn't create its underlying view-layer pair. This is why you -can create nodes cheaply and on background threads. When you use a UIView or -CALayer property on a node, you're actually interacting with a proxy object, -[`_ASPendingState`](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Private/_ASPendingState.h), -that's preconfigured to match UIView and CALayer defaults. - -The first access to a node's `.view` or `.layer` property causes both to be -initialised and configured with the node's current state. If it has subnodes, -they are recursively loaded as well. Once a node has been loaded, the proxy -object is destroyed and the node becomes main-thread-affined — its -properties will update the underlying view directly. (Layer-backed nodes do -the same, not loading views.) - -Nodes are powered by -[`_ASDisplayLayer`](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Details/_ASDisplayLayer.h) -and -[`_ASDisplayView`](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Details/_ASDisplayView.h). -These are lightweight to create and add to their respective hierarchies, and -provide integration points that allow nodes to act as full-fledged views or -layers. It's possible to create nodes that are backed by custom view or layer -classes, but doing so is strongly discouraged as it disables the majority of -ASDK's functionality. - -When Core Animation asks an `_ASDisplayLayer` to draw itself, the request is -forwarded to its node. Unless asynchronous display has been disabled, the -actual draw call won't happen immediately or on the main thread. Instead, a -display block will be added to a background queue. These blocks are executed -in parallel, but you can enable `ASDISPLAYNODE_DELAY_DISPLAY` in -[`ASDisplayNode(AsyncDisplay)`](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Private/ASDisplayNode%2BAsyncDisplay.mm) -to serialise the render system for debugging. - -Common UIView subclass hooks are forwarded from `_ASDisplayView` to its -underlying node, including touch handling, hit-testing, and gesture recogniser -delegate calls. Because an `_ASDisplayView`'s layer is an `_ASDisplayLayer`, -view-backed nodes also participate in asynchronous display. - -## In practice - -What does this mean for your custom nodes? - -You can implement methods like `-touchesBegan:withEvent:` / -`touchesMoved:withEvent:` / `touchesEnded:withEvent:` / -`touchesCancelled:withEvent:` in your nodes exactly as you would in a UIView -subclass. If you find you need a subclass hook that hasn't already been -provided, please file an issue on GitHub — or add it yourself and submit a -pull request! - -If you need to interact or configure your node's underlying view or layer, -don't do so in `-init`. Instead, override `-didLoad` and check if you're -layer-backed: - -```objective-c -- (void)didLoad -{ - [super didLoad]; - - // add a gesture recogniser, if we have a view to add it to - if (!self.layerBacked) { - _gestureRecogniser = [[UITapGestureRecognizer alloc] initWithTarget:self - action:@selector(_tap:)]; - [self.view addGestureRecognizer:_gestureRecogniser]; - } -} -``` - -## *fin.* - -Thanks for reading! If you have any questions, please file a GitHub issue or -post in the [Facebook group](https://www.facebook.com/groups/551597518288687). -We'd love to hear from you. diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index ff8065921a..0000000000 --- a/docs/index.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -layout: page -title: Smooth asynchronous user interfaces for iOS apps ---- - -![logo]({{ site.baseurl }}/assets/logo.png) - -AsyncDisplayKit is an iOS framework that keeps even the most complex user -interfaces smooth and responsive. It was originally built to make Facebook's -[Paper](https://facebook.com/paper) possible, and goes hand-in-hand with -[pop](https://github.com/facebook/pop)'s physics-based animations — but -it's just as powerful with UIKit Dynamics and conventional app designs. - - -
-### Quick start - -ASDK is available on [CocoaPods](http://cocoapods.org). Add the following to your Podfile: - -```ruby -pod 'AsyncDisplayKit' -``` - -(ASDK can also be used as a regular static library: Copy the project to your -codebase manually, adding `AsyncDisplayKit.xcodeproj` to your workspace. Add -`libAsyncDisplayKit.a`, AssetsLibrary, and Photos to the "Link Binary With -Libraries" build phase. Include `-lc++ -ObjC` in your project linker flags.) - -Import the framework header, or create an [Objective-C bridging -header](https://developer.apple.com/library/ios/documentation/swift/conceptual/buildingcocoaapps/MixandMatch.html) -if you're using Swift: - -```objective-c -#import -``` - -AsyncDisplayKit Nodes are a thread-safe abstraction layer over UIViews and -CALayers: - -![logo]({{ site.baseurl }}/assets/node-view-layer.png) - -You can construct entire node hierarchies in parallel, or instantiate and size -a single node on a background thread — for example, you could do -something like this in a UIViewController: - -```objective-c -dispatch_async(_backgroundQueue, ^{ - ASTextNode *node = [[ASTextNode alloc] init]; - node.attributedString = [[NSAttributedString alloc] initWithString:@"hello!" - attributes:nil]; - [node measure:CGSizeMake(screenWidth, FLT_MAX)]; - node.frame = (CGRect){ CGPointZero, node.calculatedSize }; - - // self.view isn't a node, so we can only use it on the main thread - dispatch_async(dispatch_get_main_queue(), ^{ - [self.view addSubview:node.view]; - }); -}); -``` - -AsyncDisplayKit at a glance: - -* `ASImageNode` and `ASTextNode` are drop-in replacements for UIImageView and - UITextView. -* `ASMultiplexImageNode` can load and display progressively higher-quality - variants of an image over a slow cell network, letting you quickly show a - low-resolution photo while the full size downloads. -* `ASNetworkImageNode` is a simpler, single-image counterpart to the Multiplex - node. -* `ASTableView` and `ASCollectionView` are a node-aware UITableView and - UICollectionView, respectively, that can asynchronously preload cell nodes - — from loading network data to rendering — all without blocking - the main thread. - -You can also easily [create your own -nodes](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASDisplayNode%2BSubclasses.h) -to implement node hierarchies or custom drawing. - - -
-### Learn more - -* Read the [Getting Started guide]({{ site.baseurl }}/guide) -* Get the [sample projects](https://github.com/facebook/AsyncDisplayKit/tree/master/examples) -* Browse the [API reference]({{ site.baseurl }}/appledoc) -* Watch the [NSLondon talk](http://vimeo.com/103589245) or the [NSSpain talk](https://www.youtube.com/watch?v=RY_X7l1g79Q) diff --git a/examples/ASDKgram/Sample/PhotoFeedNodeController.m b/examples/ASDKgram/Sample/PhotoFeedNodeController.m index f99e6356d2..2a44028fdc 100644 --- a/examples/ASDKgram/Sample/PhotoFeedNodeController.m +++ b/examples/ASDKgram/Sample/PhotoFeedNodeController.m @@ -38,6 +38,8 @@ #pragma mark - Lifecycle +// -init is often called off the main thread in ASDK. Therefore it is imperative that no UIKit objects are accessed. +// Examples of common errors include accessing the node’s view or creating a gesture recognizer. - (instancetype)init { _tableNode = [[ASTableNode alloc] init]; @@ -50,17 +52,19 @@ _tableNode.dataSource = self; _tableNode.delegate = self; - _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; } return self; } -// do any ASDK view stuff in loadView +// -loadView is guaranteed to be called on the main thread and is the appropriate place to +// set up an UIKit objects you may be using. - (void)loadView { [super loadView]; + _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + _photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]]; [self refreshFeed]; diff --git a/examples/ASMapNode/Podfile b/examples/ASMapNode/Podfile new file mode 100644 index 0000000000..5c30ce798e --- /dev/null +++ b/examples/ASMapNode/Podfile @@ -0,0 +1,6 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '7.0' +target 'Sample' do + pod 'AsyncDisplayKit', :path => '../..' +end + diff --git a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..b7da6bb298 --- /dev/null +++ b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,377 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5CF3EF5E344946731D4F13F2 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */; }; + 5E5E62841D13F39400D81E38 /* MapHandlerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */; }; + 694993D21C8B334F00491CA5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D11C8B334F00491CA5 /* main.m */; }; + 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D41C8B334F00491CA5 /* AppDelegate.m */; }; + 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 */; }; +/* 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 = ""; }; + 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 = ""; }; + 694993D41C8B334F00491CA5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 694993D61C8B334F00491CA5 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 694993D71C8B334F00491CA5 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 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 = ""; }; + 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 */ + +/* Begin PBXFrameworksBuildPhase section */ + 694993CA1C8B334F00491CA5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5CF3EF5E344946731D4F13F2 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0DFDB4376BA084DAC7C1976E /* Pods */ = { + isa = PBXGroup; + children = ( + 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */, + 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 478C8D7C412DCBDFE14640D8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 694993C41C8B334F00491CA5 = { + isa = PBXGroup; + children = ( + 694993CF1C8B334F00491CA5 /* Sample */, + 694993CE1C8B334F00491CA5 /* Products */, + 0DFDB4376BA084DAC7C1976E /* Pods */, + 478C8D7C412DCBDFE14640D8 /* Frameworks */, + ); + sourceTree = ""; + }; + 694993CE1C8B334F00491CA5 /* Products */ = { + isa = PBXGroup; + children = ( + 694993CD1C8B334F00491CA5 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 694993CF1C8B334F00491CA5 /* Sample */ = { + isa = PBXGroup; + children = ( + 694993D31C8B334F00491CA5 /* AppDelegate.h */, + 694993D41C8B334F00491CA5 /* AppDelegate.m */, + 694993D61C8B334F00491CA5 /* ViewController.h */, + 694993D71C8B334F00491CA5 /* ViewController.m */, + 5E5E62821D13F39400D81E38 /* MapHandlerNode.h */, + 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */, + 694993DC1C8B334F00491CA5 /* Assets.xcassets */, + 694993D01C8B334F00491CA5 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 694993D01C8B334F00491CA5 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 694993E11C8B334F00491CA5 /* Info.plist */, + 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */, + 694993D11C8B334F00491CA5 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 694993CC1C8B334F00491CA5 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 80035273449C25F4B2E1454F /* [CP] Check Pods Manifest.lock */, + 694993C91C8B334F00491CA5 /* Sources */, + 694993CA1C8B334F00491CA5 /* Frameworks */, + 694993CB1C8B334F00491CA5 /* Resources */, + 06EE2E0ABEB6289D4775A867 /* [CP] Copy Pods Resources */, + 23FC03B282CBD9014D868DF6 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 694993CD1C8B334F00491CA5 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 694993C51C8B334F00491CA5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = AsyncDisplayKit; + TargetAttributes = { + 694993CC1C8B334F00491CA5 = { + CreatedOnToolsVersion = 7.2.1; + }; + }; + }; + buildConfigurationList = 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 694993C41C8B334F00491CA5; + productRefGroup = 694993CE1C8B334F00491CA5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 694993CC1C8B334F00491CA5 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 694993CB1C8B334F00491CA5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */, + 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 06EE2E0ABEB6289D4775A867 /* [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; + }; + 23FC03B282CBD9014D868DF6 /* [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; + }; + 80035273449C25F4B2E1454F /* [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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 694993C91C8B334F00491CA5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E5E62841D13F39400D81E38 /* MapHandlerNode.m in Sources */, + 694993D81C8B334F00491CA5 /* ViewController.m in Sources */, + 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */, + 694993D21C8B334F00491CA5 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 694993DF1C8B334F00491CA5 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 694993E21C8B334F00491CA5 /* 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; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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 = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 694993E31C8B334F00491CA5 /* 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 = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + 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 = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 694993E51C8B334F00491CA5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 694993E61C8B334F00491CA5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 694993E21C8B334F00491CA5 /* Debug */, + 694993E31C8B334F00491CA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 694993E51C8B334F00491CA5 /* Debug */, + 694993E61C8B334F00491CA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 694993C51C8B334F00491CA5 /* Project object */; +} diff --git a/examples/ASMapNode/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/ASMapNode/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..c00064c54d --- /dev/null +++ b/examples/ASMapNode/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASMapNode/Sample/AppDelegate.h b/examples/ASMapNode/Sample/AppDelegate.h new file mode 100644 index 0000000000..4591d34854 --- /dev/null +++ b/examples/ASMapNode/Sample/AppDelegate.h @@ -0,0 +1,26 @@ +// +// 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/ASMapNode/Sample/AppDelegate.m b/examples/ASMapNode/Sample/AppDelegate.m new file mode 100644 index 0000000000..e56bcd4ec0 --- /dev/null +++ b/examples/ASMapNode/Sample/AppDelegate.m @@ -0,0 +1,36 @@ +// +// 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" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[ViewController new]]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/ASMapNode/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from examples/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json rename to examples/ASMapNode/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/examples/ASMapNode/Sample/Base.lproj/LaunchScreen.storyboard b/examples/ASMapNode/Sample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..f4fc7f7736 --- /dev/null +++ b/examples/ASMapNode/Sample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASMapNode/Sample/Info.plist b/examples/ASMapNode/Sample/Info.plist new file mode 100644 index 0000000000..6105445463 --- /dev/null +++ b/examples/ASMapNode/Sample/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/ASMapNode/Sample/MapHandlerNode.h b/examples/ASMapNode/Sample/MapHandlerNode.h new file mode 100644 index 0000000000..f51924419f --- /dev/null +++ b/examples/ASMapNode/Sample/MapHandlerNode.h @@ -0,0 +1,24 @@ +// +// MapHandlerNode.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 MapHandlerNode : ASDisplayNode + + +@end + diff --git a/examples/ASMapNode/Sample/MapHandlerNode.m b/examples/ASMapNode/Sample/MapHandlerNode.m new file mode 100644 index 0000000000..8a78445bb2 --- /dev/null +++ b/examples/ASMapNode/Sample/MapHandlerNode.m @@ -0,0 +1,247 @@ +// +// MapHandlerNode.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 "MapHandlerNode.h" + +#import + +#import +#import +#import + +#import +#import + +@interface MapHandlerNode () + +@property (nonatomic, strong) ASEditableTextNode * latEditableNode; +@property (nonatomic, strong) ASEditableTextNode * lonEditableNode; +@property (nonatomic, strong) ASEditableTextNode * deltaLatEditableNode; +@property (nonatomic, strong) ASEditableTextNode * deltaLonEditableNode; +@property (nonatomic, strong) ASButtonNode * updateRegionButton; +@property (nonatomic, strong) ASButtonNode * liveMapToggleButton; +@property (nonatomic, strong) ASMapNode * mapNode; + +@end + +@implementation MapHandlerNode + +#pragma mark - Lifecycle + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + _latEditableNode = [[ASEditableTextNode alloc] init]; + _lonEditableNode = [[ASEditableTextNode alloc] init]; + _deltaLatEditableNode = [[ASEditableTextNode alloc] init]; + _deltaLonEditableNode = [[ASEditableTextNode alloc] init]; + + _updateRegionButton = [[ASButtonNode alloc] init]; + _liveMapToggleButton = [[ASButtonNode alloc] init]; + _mapNode = [[ASMapNode alloc] init]; + + [self addSubnode:_latEditableNode]; + [self addSubnode:_lonEditableNode]; + [self addSubnode:_deltaLatEditableNode]; + [self addSubnode:_deltaLonEditableNode]; + + [self addSubnode:_updateRegionButton]; + [self addSubnode:_liveMapToggleButton]; + [self addSubnode:_mapNode]; + + return self; +} + +- (void)didLoad +{ + [super didLoad]; + + _latEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", _mapNode.region.center.latitude]]; + _lonEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", _mapNode.region.center.longitude]]; + _deltaLatEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", _mapNode.region.span.latitudeDelta]]; + _deltaLonEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", _mapNode.region.span.longitudeDelta]]; + + [self configureEditableNodes:_latEditableNode]; + [self configureEditableNodes:_lonEditableNode]; + [self configureEditableNodes:_deltaLatEditableNode]; + [self configureEditableNodes:_deltaLonEditableNode]; + + _mapNode.mapDelegate = self; + + [_updateRegionButton setTitle:@"Update Region" withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal]; + [_updateRegionButton setTitle:@"Update Region" withFont:[UIFont systemFontOfSize:14] withColor:[UIColor blueColor] forState:ASControlStateHighlighted]; + [_updateRegionButton addTarget:self action:@selector(updateRegion) forControlEvents:ASControlNodeEventTouchUpInside]; + [_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]; +} + +#pragma mark - Layout + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ +#define SPACING 5 +#define HEIGHT 30 + CGSize preferredSize = CGSizeMake(constrainedSize.max.width * 0.3, HEIGHT); + + _latEditableNode.preferredFrameSize = _lonEditableNode.preferredFrameSize = preferredSize; + _deltaLatEditableNode.preferredFrameSize = _deltaLonEditableNode.preferredFrameSize = preferredSize; + _updateRegionButton.preferredFrameSize = _liveMapToggleButton.preferredFrameSize = preferredSize; + + _latEditableNode.flexGrow = _lonEditableNode.flexGrow = true; + _deltaLatEditableNode.flexGrow = _deltaLonEditableNode.flexGrow = true; + _updateRegionButton.flexGrow = _liveMapToggleButton.flexGrow = true; + + _mapNode.flexGrow = true; + + ASStackLayoutSpec * lonlatSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:SPACING + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:@[_latEditableNode, _lonEditableNode]]; + lonlatSpec.flexGrow = true; + + ASStackLayoutSpec * deltaLonlatSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:SPACING + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:@[_deltaLatEditableNode, _deltaLonEditableNode]]; + deltaLonlatSpec.flexGrow = true; + + ASStackLayoutSpec * lonlatConfigSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:SPACING + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStretch + children:@[lonlatSpec, deltaLonlatSpec]]; + lonlatConfigSpec.flexGrow = true; + + ASStackLayoutSpec * buttonsSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:SPACING + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStretch + children:@[_updateRegionButton, _liveMapToggleButton]]; + buttonsSpec.flexGrow = true; + + ASStackLayoutSpec * dashboardSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:SPACING + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStretch + children:@[lonlatConfigSpec, buttonsSpec]]; + dashboardSpec.flexGrow = true; + + ASInsetLayoutSpec * insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(20, 10, 0, 10) child:dashboardSpec]; + + ASStackLayoutSpec * layoutSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:SPACING + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStretch + children:@[insetSpec, _mapNode ]]; + return layoutSpec; +} + +#pragma mark - Button actions + +-(void)updateRegion +{ + NSNumberFormatter *f = [[NSNumberFormatter alloc] init]; + f.numberStyle = NSNumberFormatterDecimalStyle; + + double const lat = [f numberFromString:_latEditableNode.attributedText.string].doubleValue; + double const lon = [f numberFromString:_lonEditableNode.attributedText.string].doubleValue; + double const deltaLat = [f numberFromString:_deltaLatEditableNode.attributedText.string].doubleValue; + double const deltaLon = [f numberFromString:_deltaLonEditableNode.attributedText.string].doubleValue; + + MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(lat, lon), + MKCoordinateSpanMake(deltaLat, deltaLon)); + + _mapNode.region = region; +} + +-(void)toggleLiveMap +{ + _mapNode.liveMap = !_mapNode.liveMap; + NSString * const liveMapStr = [self liveMapStr]; + [_liveMapToggleButton setTitle:liveMapStr withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal]; + [_liveMapToggleButton setTitle:liveMapStr withFont:[UIFont systemFontOfSize:14] withColor:[UIColor blueColor] forState:ASControlStateHighlighted]; +} + +#pragma mark - Helpers + +-(NSString *)liveMapStr +{ + return _mapNode.liveMap ? @"Live Map is ON" : @"Live Map is OFF"; +} + +-(void)configureEditableNodes:(ASEditableTextNode *)node +{ + node.returnKeyType = node == _deltaLonEditableNode ? UIReturnKeyDone : UIReturnKeyNext; + node.delegate = self; +} + +#pragma mark - ASEditableTextNodeDelegate + +- (BOOL)editableTextNode:(ASEditableTextNode *)editableTextNode shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text +{ + if([text isEqualToString:@"\n"]) { + if(editableTextNode == _latEditableNode) + [_lonEditableNode becomeFirstResponder]; + else if(editableTextNode == _lonEditableNode) + [_deltaLatEditableNode becomeFirstResponder]; + else if(editableTextNode == _deltaLatEditableNode) + [_deltaLonEditableNode becomeFirstResponder]; + else if(editableTextNode == _deltaLonEditableNode) { + [_deltaLonEditableNode resignFirstResponder]; + [self updateRegion]; + } + return NO; + } + + NSMutableCharacterSet * s = [NSMutableCharacterSet characterSetWithCharactersInString:@".-"]; + [s formUnionWithCharacterSet:[NSCharacterSet decimalDigitCharacterSet]]; + [s invert]; + + NSRange r = [text rangeOfCharacterFromSet:s]; + if(r.location != NSNotFound) { + return NO; + } + + if([editableTextNode.attributedText.string rangeOfString:@"."].location != NSNotFound && + [text rangeOfString:@"."].location != NSNotFound) { + return NO; + } + + if ([editableTextNode.attributedText.string rangeOfString:@"-"].location != NSNotFound && + [text rangeOfString:@"-"].location != NSNotFound && + range.location > 0) { + return NO; + } + + return YES; +} + +#pragma mark - MKMapViewDelegate + +- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { + _latEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", mapView.region.center.latitude]]; + _lonEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", mapView.region.center.longitude]]; + _deltaLatEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", mapView.region.span.latitudeDelta]]; + _deltaLonEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", mapView.region.span.longitudeDelta]]; +} + +@end diff --git a/examples/ASMapNode/Sample/ViewController.h b/examples/ASMapNode/Sample/ViewController.h new file mode 100644 index 0000000000..c2a4b5f166 --- /dev/null +++ b/examples/ASMapNode/Sample/ViewController.h @@ -0,0 +1,24 @@ +// +// 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 : ASViewController + + +@end + diff --git a/examples/ASMapNode/Sample/ViewController.m b/examples/ASMapNode/Sample/ViewController.m new file mode 100644 index 0000000000..53967e2035 --- /dev/null +++ b/examples/ASMapNode/Sample/ViewController.m @@ -0,0 +1,53 @@ +// +// 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 "MapHandlerNode.h" + +@interface ViewController () + +@end + +@implementation ViewController + + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super initWithNode:[[MapHandlerNode alloc] init]]; + if (self == nil) { return self; } + + return self; +} + +#pragma mark - UIViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + self.navigationController.navigationBarHidden = true; +} + +@end diff --git a/examples/ASMapNode/Sample/main.m b/examples/ASMapNode/Sample/main.m new file mode 100644 index 0000000000..791ef4b743 --- /dev/null +++ b/examples/ASMapNode/Sample/main.m @@ -0,0 +1,25 @@ +// +// 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/ASViewController/Sample/ViewController.m b/examples/ASViewController/Sample/ViewController.m index 0ac2f56136..aa98294aa2 100644 --- a/examples/ASViewController/Sample/ViewController.m +++ b/examples/ASViewController/Sample/ViewController.m @@ -16,7 +16,7 @@ // #import "ViewController.h" -#import "ASTableNode.h" +#import #import "DetailViewController.h" diff --git a/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASCollectionNode.m b/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASCollectionNode.m index f0950db089..d406f3b170 100644 --- a/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASCollectionNode.m +++ b/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASCollectionNode.m @@ -66,9 +66,9 @@ }; } -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - return CGSizeMake(100, 100); + return ASSizeRangeMakeExactSize(CGSizeMake(100, 100)); } @end diff --git a/examples/Videos/Sample/ViewController.h b/examples/Videos/Sample/ViewController.h index 7cce1c400a..2b35e7f47d 100644 --- a/examples/Videos/Sample/ViewController.h +++ b/examples/Videos/Sample/ViewController.h @@ -14,8 +14,7 @@ // 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 +#import @interface ViewController : UIViewController diff --git a/examples/Videos/Sample/ViewController.m b/examples/Videos/Sample/ViewController.m index 43a0fd6755..1b390386a2 100644 --- a/examples/Videos/Sample/ViewController.m +++ b/examples/Videos/Sample/ViewController.m @@ -16,8 +16,7 @@ // #import "ViewController.h" -#import "ASLayoutSpec.h" -#import "ASStaticLayoutSpec.h" +#import @interface ViewController() @property (nonatomic, strong) ASDisplayNode *rootNode; @@ -28,12 +27,13 @@ #pragma mark - UIViewController -- (void)viewWillAppear:(BOOL)animated +- (void)viewDidLoad { - [super viewWillAppear:animated]; - + [super viewDidLoad]; + // Root node for the view controller _rootNode = [ASDisplayNode new]; + _rootNode.frame = self.view.bounds; _rootNode.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; ASVideoNode *guitarVideoNode = self.guitarVideoNode; @@ -46,6 +46,9 @@ ASVideoNode *simonVideoNode = self.simonVideoNode; [_rootNode addSubnode:simonVideoNode]; + ASVideoNode *hlsVideoNode = self.hlsVideoNode; + [_rootNode addSubnode:hlsVideoNode]; + _rootNode.layoutSpecBlock = ^ASLayoutSpec *(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { guitarVideoNode.layoutPosition = CGPointMake(0, 0); guitarVideoNode.preferredFrameSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height/3); @@ -55,19 +58,14 @@ simonVideoNode.layoutPosition = CGPointMake(0, [UIScreen mainScreen].bounds.size.height - ([UIScreen mainScreen].bounds.size.height/3)); simonVideoNode.preferredFrameSize = CGSizeMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/3); - return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode]]; + + hlsVideoNode.layoutPosition = CGPointMake(0, [UIScreen mainScreen].bounds.size.height/3); + hlsVideoNode.preferredFrameSize = CGSizeMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/3); + + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode]]; }; - [self.view addSubnode:_rootNode]; -} - -- (void)viewDidLayoutSubviews -{ - [super viewDidLayoutSubviews]; - // After all subviews are layed out we have to measure it and move the root node to the right place - CGSize viewSize = self.view.bounds.size; - [self.rootNode measureWithSizeRange:ASSizeRangeMake(viewSize, viewSize)]; - [self.rootNode setNeedsLayout]; + [self.view addSubnode:_rootNode]; } #pragma mark - Getter / Setter @@ -117,6 +115,24 @@ return simonVideoNode; } +- (ASVideoNode *)hlsVideoNode; +{ + ASVideoNode *hlsVideoNode = [[ASVideoNode alloc] init]; + + hlsVideoNode.delegate = self; + hlsVideoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8"]]; + hlsVideoNode.gravity = AVLayerVideoGravityResize; + hlsVideoNode.backgroundColor = [UIColor lightGrayColor]; + hlsVideoNode.shouldAutorepeat = YES; + hlsVideoNode.shouldAutoplay = YES; + hlsVideoNode.muted = YES; + + // Placeholder image + hlsVideoNode.URL = [NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/en/5/52/Testcard_F.jpg"]; + + return hlsVideoNode; +} + - (ASButtonNode *)playButton; { ASButtonNode *playButtonNode = [[ASButtonNode alloc] init]; diff --git a/examples_extra/ASTraitCollection/Sample/KittenNode.m b/examples_extra/ASTraitCollection/Sample/KittenNode.m index 750bf63a1b..a7f95e91fb 100644 --- a/examples_extra/ASTraitCollection/Sample/KittenNode.m +++ b/examples_extra/ASTraitCollection/Sample/KittenNode.m @@ -155,13 +155,14 @@ static const CGFloat kInnerPadding = 10.0f; { OverrideViewController *overrideVC = [[OverrideViewController alloc] init]; + __weak OverrideViewController *weakOverrideVC = overrideVC; overrideVC.overrideDisplayTraitsWithTraitCollection = ^(UITraitCollection *traitCollection) { ASTraitCollection *asyncTraitCollection = [ASTraitCollection traitCollectionWithDisplayScale:traitCollection.displayScale userInterfaceIdiom:traitCollection.userInterfaceIdiom horizontalSizeClass:UIUserInterfaceSizeClassCompact verticalSizeClass:UIUserInterfaceSizeClassCompact forceTouchCapability:traitCollection.forceTouchCapability - traitCollectionContext:nil]; + containerSize:weakOverrideVC.view.bounds.size]; return asyncTraitCollection; }; diff --git a/examples/CarthageBuildTest/Cartfile b/examples_extra/CarthageBuildTest/Cartfile similarity index 100% rename from examples/CarthageBuildTest/Cartfile rename to examples_extra/CarthageBuildTest/Cartfile diff --git a/examples/CarthageBuildTest/CarthageExample/AppDelegate.h b/examples_extra/CarthageBuildTest/CarthageExample/AppDelegate.h similarity index 100% rename from examples/CarthageBuildTest/CarthageExample/AppDelegate.h rename to examples_extra/CarthageBuildTest/CarthageExample/AppDelegate.h diff --git a/examples/CarthageBuildTest/CarthageExample/AppDelegate.m b/examples_extra/CarthageBuildTest/CarthageExample/AppDelegate.m similarity index 100% rename from examples/CarthageBuildTest/CarthageExample/AppDelegate.m rename to examples_extra/CarthageBuildTest/CarthageExample/AppDelegate.m diff --git a/examples_extra/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples_extra/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..118c98f746 --- /dev/null +++ b/examples_extra/CarthageBuildTest/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard b/examples_extra/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from examples/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard rename to examples_extra/CarthageBuildTest/CarthageExample/Base.lproj/LaunchScreen.storyboard diff --git a/examples/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard b/examples_extra/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard similarity index 100% rename from examples/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard rename to examples_extra/CarthageBuildTest/CarthageExample/Base.lproj/Main.storyboard diff --git a/examples/CarthageBuildTest/CarthageExample/Info.plist b/examples_extra/CarthageBuildTest/CarthageExample/Info.plist similarity index 100% rename from examples/CarthageBuildTest/CarthageExample/Info.plist rename to examples_extra/CarthageBuildTest/CarthageExample/Info.plist diff --git a/examples/CarthageBuildTest/CarthageExample/ViewController.h b/examples_extra/CarthageBuildTest/CarthageExample/ViewController.h similarity index 100% rename from examples/CarthageBuildTest/CarthageExample/ViewController.h rename to examples_extra/CarthageBuildTest/CarthageExample/ViewController.h diff --git a/examples/CarthageBuildTest/CarthageExample/ViewController.m b/examples_extra/CarthageBuildTest/CarthageExample/ViewController.m similarity index 100% rename from examples/CarthageBuildTest/CarthageExample/ViewController.m rename to examples_extra/CarthageBuildTest/CarthageExample/ViewController.m diff --git a/examples/CarthageBuildTest/CarthageExample/main.m b/examples_extra/CarthageBuildTest/CarthageExample/main.m similarity index 100% rename from examples/CarthageBuildTest/CarthageExample/main.m rename to examples_extra/CarthageBuildTest/CarthageExample/main.m diff --git a/examples/CarthageBuildTest/README.md b/examples_extra/CarthageBuildTest/README.md similarity index 100% rename from examples/CarthageBuildTest/README.md rename to examples_extra/CarthageBuildTest/README.md diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/project.pbxproj b/examples_extra/CarthageBuildTest/Sample.xcodeproj/project.pbxproj similarity index 100% rename from examples/CarthageBuildTest/Sample.xcodeproj/project.pbxproj rename to examples_extra/CarthageBuildTest/Sample.xcodeproj/project.pbxproj diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples_extra/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to examples_extra/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples_extra/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from examples/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to examples_extra/CarthageBuildTest/Sample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/examples/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples_extra/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme similarity index 100% rename from examples/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme rename to examples_extra/CarthageBuildTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme diff --git a/examples_extra/Multiplex/Sample/ScreenNode.m b/examples_extra/Multiplex/Sample/ScreenNode.m index d07574bb41..6471b39737 100644 --- a/examples_extra/Multiplex/Sample/ScreenNode.m +++ b/examples_extra/Multiplex/Sample/ScreenNode.m @@ -95,15 +95,15 @@ - (NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(id)imageIdentifier { if ([imageIdentifier isEqualToString:@"worst"]) { - return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples/Multiplex/worst.png"]; + return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples_extra/Multiplex/worst.png"]; } if ([imageIdentifier isEqualToString:@"medium"]) { - return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples/Multiplex/medium.png"]; + return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples_extra/Multiplex/medium.png"]; } if ([imageIdentifier isEqualToString:@"best"]) { - return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples/Multiplex/best.png"]; + return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples_extra/Multiplex/best.png"]; } // unexpected identifier diff --git a/examples_extra/TextStressTest/Default-568h@2x.png b/examples_extra/TextStressTest/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples_extra/TextStressTest/Default-568h@2x.png differ diff --git a/examples_extra/TextStressTest/Default-667h@2x.png b/examples_extra/TextStressTest/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples_extra/TextStressTest/Default-667h@2x.png differ diff --git a/examples_extra/TextStressTest/Default-736h@3x.png b/examples_extra/TextStressTest/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples_extra/TextStressTest/Default-736h@3x.png differ diff --git a/examples_extra/TextStressTest/Podfile b/examples_extra/TextStressTest/Podfile new file mode 100644 index 0000000000..5c30ce798e --- /dev/null +++ b/examples_extra/TextStressTest/Podfile @@ -0,0 +1,6 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '7.0' +target 'Sample' do + pod 'AsyncDisplayKit', :path => '../..' +end + diff --git a/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj b/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..3453463d27 --- /dev/null +++ b/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,364 @@ +// !$*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 */; }; + 92F1263CECFE3FFCC7A5F936 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E8EC8300ABAAEA079224272A /* 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 = ""; }; + 0CDEE995962D3E4584D302EE /* 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 = ""; }; + 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; }; + A950870A2154F92D5DC91F1A /* 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 = ""; }; + E8EC8300ABAAEA079224272A /* 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 = ( + 92F1263CECFE3FFCC7A5F936 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 6DE0E5D094594AB09140EF84 /* Pods */, + F5DF5EAD6C1B97F91D1C830F /* Frameworks */, + ); + 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 = ""; + }; + 6DE0E5D094594AB09140EF84 /* Pods */ = { + isa = PBXGroup; + children = ( + 0CDEE995962D3E4584D302EE /* Pods-Sample.debug.xcconfig */, + A950870A2154F92D5DC91F1A /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + F5DF5EAD6C1B97F91D1C830F /* Frameworks */ = { + isa = PBXGroup; + children = ( + E8EC8300ABAAEA079224272A /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 77F6A2B5E8DA12933E6365CE /* [CP] Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + 96436DA0C1AFF84D8041B522 /* [CP] Embed Pods Frameworks */, + D17B5BD4AA634EFE93D71E9F /* [CP] Copy Pods Resources */, + ); + 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 = 0730; + 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 */ + 77F6A2B5E8DA12933E6365CE /* [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; + }; + 96436DA0C1AFF84D8041B522 /* [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; + }; + D17B5BD4AA634EFE93D71E9F /* [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 = 0CDEE995962D3E4584D302EE /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A950870A2154F92D5DC91F1A /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + 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_extra/TextStressTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples_extra/TextStressTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..d41d58c5d8 --- /dev/null +++ b/examples_extra/TextStressTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples_extra/TextStressTest/Sample/AppDelegate.h b/examples_extra/TextStressTest/Sample/AppDelegate.h new file mode 100644 index 0000000000..2aa29369b4 --- /dev/null +++ b/examples_extra/TextStressTest/Sample/AppDelegate.h @@ -0,0 +1,18 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * 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_extra/TextStressTest/Sample/AppDelegate.m b/examples_extra/TextStressTest/Sample/AppDelegate.m new file mode 100644 index 0000000000..a8e5594780 --- /dev/null +++ b/examples_extra/TextStressTest/Sample/AppDelegate.m @@ -0,0 +1,27 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * 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_extra/TextStressTest/Sample/Info.plist b/examples_extra/TextStressTest/Sample/Info.plist new file mode 100644 index 0000000000..fb4115c84c --- /dev/null +++ b/examples_extra/TextStressTest/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_extra/TextStressTest/Sample/ViewController.h b/examples_extra/TextStressTest/Sample/ViewController.h new file mode 100644 index 0000000000..d0e9200d88 --- /dev/null +++ b/examples_extra/TextStressTest/Sample/ViewController.h @@ -0,0 +1,16 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * 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_extra/TextStressTest/Sample/ViewController.m b/examples_extra/TextStressTest/Sample/ViewController.m new file mode 100644 index 0000000000..3c21c64b8a --- /dev/null +++ b/examples_extra/TextStressTest/Sample/ViewController.m @@ -0,0 +1,165 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * 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 + +#define NUMBER_ELEMENTS 2 + +@interface ViewController () +{ + NSMutableArray *_textNodes; + NSMutableArray *_textLabels; + UIScrollView *_scrollView; +} + +@end + + +@implementation ViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _textNodes = [NSMutableArray array]; + _textLabels = [NSMutableArray array]; + + _scrollView = [[UIScrollView alloc] init]; + [self.view addSubview:_scrollView]; + + for (int i = 0; i < NUMBER_ELEMENTS; i++) { + + ASTextNode *node = [self createNodeForIndex:i]; + [_textNodes addObject:node]; + [_scrollView addSubnode:node]; + + UILabel *label = [self createLabelForIndex:i]; + [_textLabels addObject:label]; + [_scrollView addSubview:label]; + } +} + +- (void)viewWillLayoutSubviews +{ + [super viewWillLayoutSubviews]; + + CGFloat maxWidth = 0; + CGFloat maxHeight = 0; + + CGRect frame = CGRectMake(50, 50, 0, 0); + + for (int i = 0; i < NUMBER_ELEMENTS; i++) { + frame.size = [self sizeForIndex:i]; + [[_textNodes objectAtIndex:i] setFrame:frame]; + + frame.origin.x += frame.size.width + 50; + + [[_textLabels objectAtIndex:i] setFrame:frame]; + + if (frame.size.width > maxWidth) { + maxWidth = frame.size.width; + } + if ((frame.size.height + frame.origin.y) > maxHeight) { + maxHeight = frame.size.height + frame.origin.y; + } + + frame.origin.x -= frame.size.width + 50; + frame.origin.y += frame.size.height + 20; + } + + _scrollView.frame = self.view.bounds; + _scrollView.contentSize = CGSizeMake(maxWidth, maxHeight); +} + +- (ASTextNode *)createNodeForIndex:(NSUInteger)index +{ + ASTextNode *node = [[ASTextNode alloc] init]; + node.attributedText = [self textForIndex:index]; + node.backgroundColor = [UIColor orangeColor]; + + NSMutableAttributedString *string = [node.attributedText mutableCopy]; + + switch (index) { + case 0: // top justification (ASDK) vs. center justification (UILabel) + node.maximumNumberOfLines = 3; + return node; + + case 1: // default truncation attributed string color shouldn't match attributed text color (ASDK) vs. match (UIKit) + node.maximumNumberOfLines = 3; + [string addAttribute:NSForegroundColorAttributeName + value:[UIColor redColor] + range:NSMakeRange(0, [string length])]; + node.attributedText = string; + return node; + + default: + return nil; + } +} + +- (UILabel *)createLabelForIndex:(NSUInteger)index +{ + UILabel *label = [[UILabel alloc] init]; + label.attributedText = [self textForIndex:index]; + label.backgroundColor = [UIColor greenColor]; + + NSMutableAttributedString *string = [label.attributedText mutableCopy]; + + switch (index) { + case 0: + label.numberOfLines = 3; + return label; + + case 1: + label.numberOfLines = 3; + [string addAttribute:NSForegroundColorAttributeName + value:[UIColor redColor] + range:NSMakeRange(0, [string length])]; + label.attributedText = string; + return label; + + default: + return nil; + } +} + +- (NSAttributedString *)textForIndex:(NSUInteger)index +{ + NSDictionary *attrs = @{ NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue" size:12.0f] }; + + switch (index) { + case 0: + return [[NSAttributedString alloc] initWithString:@"1\n2\n3\n4\n5" attributes:attrs]; + + case 1: + return [[NSAttributedString alloc] initWithString:@"1\n2\n3\n4\n5" attributes:attrs]; + + default: + return nil; + } +} + +- (CGSize)sizeForIndex:(NSUInteger)index +{ + switch (index) { + case 0: + return CGSizeMake(40, 100); + + case 1: + return CGSizeMake(40, 100); + + default: + return CGSizeZero; + } +} + +@end diff --git a/examples_extra/TextStressTest/Sample/main.m b/examples_extra/TextStressTest/Sample/main.m new file mode 100644 index 0000000000..ae9488711c --- /dev/null +++ b/examples_extra/TextStressTest/Sample/main.m @@ -0,0 +1,20 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * 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/inferScript.sh b/inferScript.sh new file mode 100755 index 0000000000..81919c1e81 --- /dev/null +++ b/inferScript.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +if ! [ -x "$(command -v infer)" ]; then + echo "infer not found" + echo "Install infer with homebrew: brew install infer" +else + infer --continue --reactive -- xcodebuild build -workspace AsyncDisplayKit.xcworkspace -scheme "AsyncDisplayKit-iOS" -configuration Debug -sdk iphonesimulator9.3 +fi diff --git a/smoke-tests/Life Without CocoaPods/Life With Frameworks/Assets.xcassets/AppIcon.appiconset/Contents.json b/smoke-tests/Life Without CocoaPods/Life With Frameworks/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..eeea76c2db --- /dev/null +++ b/smoke-tests/Life Without CocoaPods/Life With Frameworks/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,73 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/smoke-tests/Life Without CocoaPods/Life With Frameworks/Base.lproj/LaunchScreen.storyboard b/smoke-tests/Life Without CocoaPods/Life With Frameworks/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..ebf48f6039 --- /dev/null +++ b/smoke-tests/Life Without CocoaPods/Life With Frameworks/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/smoke-tests/Life Without CocoaPods/Life With Frameworks/Info.plist b/smoke-tests/Life Without CocoaPods/Life With Frameworks/Info.plist new file mode 100644 index 0000000000..eabb3ae346 --- /dev/null +++ b/smoke-tests/Life Without CocoaPods/Life With Frameworks/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/smoke-tests/Life Without CocoaPods/Life With Frameworks/main.m b/smoke-tests/Life Without CocoaPods/Life With Frameworks/main.m new file mode 100644 index 0000000000..3f9d50d481 --- /dev/null +++ b/smoke-tests/Life Without CocoaPods/Life With Frameworks/main.m @@ -0,0 +1,16 @@ +// +// main.m +// Life With Frameworks +// +// Created by Kiel Gillard on 7/07/2016. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj index aaaa497852..21f17c4b12 100644 --- a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj +++ b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj @@ -13,12 +13,78 @@ 0589691B1ABCE0E80059CE2A /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 058969181ABCE0E80059CE2A /* Default-568h@2x.png */; }; 0589691C1ABCE0E80059CE2A /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 058969191ABCE0E80059CE2A /* Default-667h@2x.png */; }; 0589691D1ABCE0E80059CE2A /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0589691A1ABCE0E80059CE2A /* Default-736h@3x.png */; }; - 058969281ABCE1750059CE2A /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058969271ABCE1750059CE2A /* libAsyncDisplayKit.a */; }; 0589692A1ABCE17C0059CE2A /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */; }; 0589692C1ABCE1820059CE2A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0589692B1ABCE1820059CE2A /* Photos.framework */; }; 92DD2FEC1BF4D8BB0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */; }; + F729B8BB1D2E176700C9EDBC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F729B8BA1D2E176700C9EDBC /* main.m */; }; + F729B8C61D2E176700C9EDBC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F729B8C51D2E176700C9EDBC /* Assets.xcassets */; }; + F729B8C91D2E176700C9EDBC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F729B8C71D2E176700C9EDBC /* LaunchScreen.storyboard */; }; + F729B8D11D2E17A300C9EDBC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 058968F71ABCE06E0059CE2A /* AppDelegate.m */; }; + F729B8D21D2E17A300C9EDBC /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 058968FA1ABCE06E0059CE2A /* ViewController.m */; }; + F729B8D31D2E17C800C9EDBC /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7CE6CB31D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */; }; + F729B8D41D2E17C800C9EDBC /* AsyncDisplayKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F7CE6CB31D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + F7CE6CB61D2CE00800BE4C15 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F7CE6CAD1D2CDFFB00BE4C15 /* libAsyncDisplayKit.a */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + F729B8D51D2E17C800C9EDBC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = B35061D91B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; + F7CE6CAC1D2CDFFB00BE4C15 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09AC195D04C000B7D73C; + remoteInfo = AsyncDisplayKit; + }; + F7CE6CAE1D2CDFFB00BE4C15 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09BC195D04C000B7D73C; + remoteInfo = AsyncDisplayKitTests; + }; + F7CE6CB01D2CDFFB00BE4C15 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 057D02BF1AC0A66700C7AC3C; + remoteInfo = AsyncDisplayKitTestHost; + }; + F7CE6CB21D2CDFFB00BE4C15 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B35061DA1B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; + F7CE6CB41D2CE00300BE4C15 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 058D09AB195D04C000B7D73C; + remoteInfo = AsyncDisplayKit; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + F729B8D71D2E17C800C9EDBC /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + F729B8D41D2E17C800C9EDBC /* AsyncDisplayKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 058968EF1ABCE06E0059CE2A /* Life Without CocoaPods.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Life Without CocoaPods.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 058968F31ABCE06E0059CE2A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -30,10 +96,15 @@ 058969181ABCE0E80059CE2A /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 058969191ABCE0E80059CE2A /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = ""; }; 0589691A1ABCE0E80059CE2A /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = ""; }; - 058969271ABCE1750059CE2A /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libAsyncDisplayKit.a; path = "../../build/Debug-iphoneos/libAsyncDisplayKit.a"; sourceTree = ""; }; 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 0589692B1ABCE1820059CE2A /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; + F729B8B71D2E176700C9EDBC /* Life With Frameworks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Life With Frameworks.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + F729B8BA1D2E176700C9EDBC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + F729B8C51D2E176700C9EDBC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + F729B8C81D2E176700C9EDBC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + F729B8CA1D2E176700C9EDBC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AsyncDisplayKit.xcodeproj; path = ../../AsyncDisplayKit.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -41,10 +112,18 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F7CE6CB61D2CE00800BE4C15 /* libAsyncDisplayKit.a in Frameworks */, 92DD2FEC1BF4D8BB0074C9DD /* MapKit.framework in Frameworks */, 0589692C1ABCE1820059CE2A /* Photos.framework in Frameworks */, 0589692A1ABCE17C0059CE2A /* AssetsLibrary.framework in Frameworks */, - 058969281ABCE1750059CE2A /* libAsyncDisplayKit.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F729B8B41D2E176700C9EDBC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F729B8D31D2E17C800C9EDBC /* AsyncDisplayKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -57,8 +136,9 @@ 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */, 0589692B1ABCE1820059CE2A /* Photos.framework */, 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */, - 058969271ABCE1750059CE2A /* libAsyncDisplayKit.a */, + F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */, 058968F11ABCE06E0059CE2A /* Life Without CocoaPods */, + F729B8B81D2E176700C9EDBC /* Life With Frameworks */, 058968F01ABCE06E0059CE2A /* Products */, ); indentWidth = 2; @@ -70,6 +150,7 @@ isa = PBXGroup; children = ( 058968EF1ABCE06E0059CE2A /* Life Without CocoaPods.app */, + F729B8B71D2E176700C9EDBC /* Life With Frameworks.app */, ); name = Products; sourceTree = ""; @@ -98,6 +179,36 @@ name = "Supporting Files"; sourceTree = ""; }; + F729B8B81D2E176700C9EDBC /* Life With Frameworks */ = { + isa = PBXGroup; + children = ( + F729B8C51D2E176700C9EDBC /* Assets.xcassets */, + F729B8C71D2E176700C9EDBC /* LaunchScreen.storyboard */, + F729B8CA1D2E176700C9EDBC /* Info.plist */, + F729B8B91D2E176700C9EDBC /* Supporting Files */, + ); + path = "Life With Frameworks"; + sourceTree = ""; + }; + F729B8B91D2E176700C9EDBC /* Supporting Files */ = { + isa = PBXGroup; + children = ( + F729B8BA1D2E176700C9EDBC /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + F7CE6CA61D2CDFFB00BE4C15 /* Products */ = { + isa = PBXGroup; + children = ( + F7CE6CAD1D2CDFFB00BE4C15 /* libAsyncDisplayKit.a */, + F7CE6CAF1D2CDFFB00BE4C15 /* AsyncDisplayKitTests.xctest */, + F7CE6CB11D2CDFFB00BE4C15 /* AsyncDisplayKitTestHost.app */, + F7CE6CB31D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */, + ); + name = Products; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -112,24 +223,47 @@ buildRules = ( ); dependencies = ( + F7CE6CB51D2CE00300BE4C15 /* PBXTargetDependency */, ); name = "Life Without CocoaPods"; productName = "Life Without CocoaPods"; productReference = 058968EF1ABCE06E0059CE2A /* Life Without CocoaPods.app */; productType = "com.apple.product-type.application"; }; + F729B8B61D2E176700C9EDBC /* Life With Frameworks */ = { + isa = PBXNativeTarget; + buildConfigurationList = F729B8D01D2E176700C9EDBC /* Build configuration list for PBXNativeTarget "Life With Frameworks" */; + buildPhases = ( + F729B8B31D2E176700C9EDBC /* Sources */, + F729B8B41D2E176700C9EDBC /* Frameworks */, + F729B8B51D2E176700C9EDBC /* Resources */, + F729B8D71D2E17C800C9EDBC /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + F729B8D61D2E17C800C9EDBC /* PBXTargetDependency */, + ); + name = "Life With Frameworks"; + productName = "Life With Frameworks"; + productReference = F729B8B71D2E176700C9EDBC /* Life With Frameworks.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 058968E71ABCE06E0059CE2A /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0620; + LastUpgradeCheck = 0730; ORGANIZATIONNAME = Facebook; TargetAttributes = { 058968EE1ABCE06E0059CE2A = { CreatedOnToolsVersion = 6.2; }; + F729B8B61D2E176700C9EDBC = { + CreatedOnToolsVersion = 7.3.1; + }; }; }; buildConfigurationList = 058968EA1ABCE06E0059CE2A /* Build configuration list for PBXProject "Life Without CocoaPods" */; @@ -143,13 +277,51 @@ mainGroup = 058968E61ABCE06E0059CE2A; productRefGroup = 058968F01ABCE06E0059CE2A /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = F7CE6CA61D2CDFFB00BE4C15 /* Products */; + ProjectRef = F7CE6CA51D2CDFFB00BE4C15 /* AsyncDisplayKit.xcodeproj */; + }, + ); projectRoot = ""; targets = ( 058968EE1ABCE06E0059CE2A /* Life Without CocoaPods */, + F729B8B61D2E176700C9EDBC /* Life With Frameworks */, ); }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + F7CE6CAD1D2CDFFB00BE4C15 /* libAsyncDisplayKit.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libAsyncDisplayKit.a; + remoteRef = F7CE6CAC1D2CDFFB00BE4C15 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + F7CE6CAF1D2CDFFB00BE4C15 /* AsyncDisplayKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = AsyncDisplayKitTests.xctest; + remoteRef = F7CE6CAE1D2CDFFB00BE4C15 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + F7CE6CB11D2CDFFB00BE4C15 /* AsyncDisplayKitTestHost.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = AsyncDisplayKitTestHost.app; + remoteRef = F7CE6CB01D2CDFFB00BE4C15 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + F7CE6CB31D2CDFFB00BE4C15 /* AsyncDisplayKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = AsyncDisplayKit.framework; + remoteRef = F7CE6CB21D2CDFFB00BE4C15 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ 058968ED1ABCE06E0059CE2A /* Resources */ = { isa = PBXResourcesBuildPhase; @@ -161,6 +333,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F729B8B51D2E176700C9EDBC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F729B8C91D2E176700C9EDBC /* LaunchScreen.storyboard in Resources */, + F729B8C61D2E176700C9EDBC /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -174,8 +355,42 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F729B8B31D2E176700C9EDBC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F729B8D21D2E17A300C9EDBC /* ViewController.m in Sources */, + F729B8D11D2E17A300C9EDBC /* AppDelegate.m in Sources */, + F729B8BB1D2E176700C9EDBC /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + F729B8D61D2E17C800C9EDBC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "AsyncDisplayKit-iOS"; + targetProxy = F729B8D51D2E17C800C9EDBC /* PBXContainerItemProxy */; + }; + F7CE6CB51D2CE00300BE4C15 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = AsyncDisplayKit; + targetProxy = F7CE6CB41D2CE00300BE4C15 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + F729B8C71D2E176700C9EDBC /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + F729B8C81D2E176700C9EDBC /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ 058969101ABCE06E0059CE2A /* Debug */ = { isa = XCBuildConfiguration; @@ -197,6 +412,7 @@ "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_GENERATE_TEST_COVERAGE_FILES = YES; @@ -264,14 +480,12 @@ INFOPLIST_FILE = "Life Without CocoaPods/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "/Users/nadi/src/AsyncDisplayKit/build/Debug-iphoneos", - ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( "-ObjC", "-lc++", ); + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -283,18 +497,49 @@ INFOPLIST_FILE = "Life Without CocoaPods/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "/Users/nadi/src/AsyncDisplayKit/build/Debug-iphoneos", - ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( "-ObjC", "-lc++", ); + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; + F729B8CB1D2E176700C9EDBC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = "Life With Frameworks/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.Life-With-Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + F729B8CC1D2E176700C9EDBC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = "Life With Frameworks/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.Life-With-Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -316,6 +561,14 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F729B8D01D2E176700C9EDBC /* Build configuration list for PBXNativeTarget "Life With Frameworks" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F729B8CB1D2E176700C9EDBC /* Debug */, + F729B8CC1D2E176700C9EDBC /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; /* End XCConfigurationList section */ }; rootObject = 058968E71ABCE06E0059CE2A /* Project object */; diff --git a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/xcshareddata/xcschemes/Life Without CocoaPods.xcscheme b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/xcshareddata/xcschemes/Life Without CocoaPods.xcscheme index 71e83345d7..2abacce743 100644 --- a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/xcshareddata/xcschemes/Life Without CocoaPods.xcscheme +++ b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/xcshareddata/xcschemes/Life Without CocoaPods.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -38,15 +38,18 @@ ReferencedContainer = "container:Life Without CocoaPods.xcodeproj"> + + @@ -62,10 +65,10 @@ diff --git a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Info.plist b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Info.plist index 20d72f5238..fb4115c84c 100644 --- a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Info.plist +++ b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m index 6fe33cada7..9d7af84d65 100644 --- a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m +++ b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods/ViewController.m @@ -12,21 +12,19 @@ #import -@interface ViewController () { - ASTextNode *_textNode; -} - +@interface ViewController () +@property (nonatomic, strong) ASTextNode *textNode; @end @implementation ViewController - (void)viewDidLoad { - _textNode = [[ASTextNode alloc] init]; - _textNode.attributedString = [[NSAttributedString alloc] initWithString:@"Testing, testing."]; - [_textNode measure:self.view.bounds.size]; - _textNode.frame = (CGRect){ .origin = CGPointZero, .size = _textNode.calculatedSize }; - [self.view addSubnode:_textNode]; + self.textNode = [[ASTextNode alloc] init]; + self.textNode.attributedString = [[NSAttributedString alloc] initWithString:@"Testing, testing." attributes:@{ NSForegroundColorAttributeName: [UIColor redColor] }]; + [self.textNode measure:self.view.bounds.size]; + self.textNode.frame = (CGRect){ .origin = CGPointZero, .size = self.textNode.calculatedSize }; + [self.view addSubnode:self.textNode]; } @end