diff --git a/ASDKListKit/Podfile b/ASDKListKit/Podfile index a2361cf3c0..9204e224f9 100644 --- a/ASDKListKit/Podfile +++ b/ASDKListKit/Podfile @@ -3,7 +3,7 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' target 'ASDKListKitTests' do pod 'AsyncDisplayKit/IGListKit', :path => '..' - pod 'IGListKit', :git => 'https://github.com/Instagram/IGListKit', :commit => '5eca718' + pod 'IGListKit', :git => 'https://github.com/Instagram/IGListKit', :commit => 'e9e09d7' pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master' end diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index ff026649f1..3f6bf04e34 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -6,6 +6,7 @@ Pod::Spec.new do |spec| spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com' } spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => spec.version.to_s } + spec.deprecated_in_favor_of = 'Texture' spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/' @@ -19,7 +20,6 @@ Pod::Spec.new do |spec| # Subspecs spec.subspec 'Core' do |core| - core.prefix_header_file = 'Source/AsyncDisplayKit-Prefix.pch' core.public_header_files = [ 'Source/*.h', 'Source/Details/**/*.h', @@ -44,16 +44,12 @@ Pod::Spec.new do |spec| end spec.subspec 'PINRemoteImage' do |pin| - # Note: The core.prefix_header_file includes setup of PIN_REMOTE_IMAGE, so the line below could be removed. - pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } - pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.8' + pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.9' pin.dependency 'PINRemoteImage/PINCache' pin.dependency 'AsyncDisplayKit/Core' end spec.subspec 'IGListKit' do |igl| - # Note: The core.prefix_header_file includes setup of IG_LIST_KIT, so the line below could be removed. - igl.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) IG_LIST_KIT=1' } igl.dependency 'IGListKit', '2.1.0' igl.dependency 'AsyncDisplayKit/Core' end diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 0338da70c1..dded87d385 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -93,6 +93,8 @@ 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */; }; 34EFC7731B701D0700AD841F /* ASAbsoluteLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED181B17843500DA7C62 /* ASAbsoluteLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED191B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm */; }; + 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */; }; 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; }; 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; }; @@ -146,8 +148,6 @@ 696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; 696F01EE1DD2AF450049FBD5 /* ASEventLog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */; }; 696FCB311D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */; }; - 69708BA61D76386D005C3CF9 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 69708BA41D76386D005C3CF9 /* ASEqualityHashHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 69708BA81D76386D005C3CF9 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */; }; 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */; settings = {ATTRIBUTES = (Private, ); }; }; 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */; }; 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */; }; @@ -304,13 +304,13 @@ C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A011E5FAF9700626263 /* ASElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = CC0349FF1E5FAF9700626263 /* ASElementMap.h */; }; CC034A021E5FAF9700626263 /* ASElementMap.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A001E5FAF9700626263 /* ASElementMap.m */; }; + CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */; }; CC034A0D1E60C3D500626263 /* ASRectTable.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A0B1E60C3D500626263 /* ASRectTable.h */; }; CC034A0E1E60C3D500626263 /* ASRectTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A0C1E60C3D500626263 /* ASRectTable.m */; }; CC034A101E60C9BF00626263 /* ASRectTableTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */; }; CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */; }; CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m */; }; - CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */; }; CC051F1F1D7A286A006434CB /* ASCALayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC051F1E1D7A286A006434CB /* ASCALayerTests.m */; }; CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */; }; CC0F885B1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m */; }; @@ -372,9 +372,25 @@ DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; + E516FC7F1E9FE24200714FF4 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = E516FC7D1E9FE24200714FF4 /* ASEqualityHashHelpers.h */; }; + E516FC801E9FE24200714FF4 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = E516FC7E1E9FE24200714FF4 /* ASEqualityHashHelpers.mm */; }; E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; }; E5711A2C1C840C81009619D4 /* ASCollectionElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASCollectionElement.h */; settings = {ATTRIBUTES = (Private, ); }; }; E5711A301C840C96009619D4 /* ASCollectionElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */; }; + E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */; }; + E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */; }; + E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E58E9E491E941DA5004CFC59 /* ASCollectionLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; + E58E9E4A1E941DA5004CFC59 /* ASCollectionLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */; }; + E5ABAC7B1E8564EE007AC15C /* ASRectTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E5ABAC791E8564EE007AC15C /* ASRectTable.h */; }; + E5ABAC7C1E8564EE007AC15C /* ASRectTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */; }; + E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */ = {isa = PBXBuildFile; fileRef = E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */; }; + E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */; }; + E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */ = {isa = PBXBuildFile; fileRef = E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E5E281761E71C845006B67C2 /* ASCollectionLayoutState.m in Sources */ = {isa = PBXBuildFile; fileRef = E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */; }; F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */; }; /* End PBXBuildFile section */ @@ -389,7 +405,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 044285011BAA3CC700D16268 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 044285011BAA3CC700D16268 /* AsyncDisplayKit.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = AsyncDisplayKit.modulemap; sourceTree = ""; }; 044285051BAA63FE00D16268 /* ASBatchFetching.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchFetching.h; sourceTree = ""; }; 044285061BAA63FE00D16268 /* ASBatchFetching.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBatchFetching.m; sourceTree = ""; }; 0442850B1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTwoDimensionalArrayUtils.h; sourceTree = ""; }; @@ -440,7 +456,7 @@ 058D09DF195D050800B7D73C /* ASTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNode.h; sourceTree = ""; }; 058D09E0195D050800B7D73C /* ASTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTextNode.mm; sourceTree = ""; }; 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayLayer.h; sourceTree = ""; }; - 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = _ASDisplayLayer.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = _ASDisplayLayer.mm; sourceTree = ""; }; 058D09E4195D050800B7D73C /* _ASDisplayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayView.h; sourceTree = ""; }; 058D09E5195D050800B7D73C /* _ASDisplayView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayView.mm; sourceTree = ""; }; 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASHighlightOverlayLayer.h; sourceTree = ""; }; @@ -450,7 +466,7 @@ 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableAttributedString+TextKitAdditions.h"; sourceTree = ""; }; 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableAttributedString+TextKitAdditions.m"; sourceTree = ""; }; 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransaction.h; sourceTree = ""; }; - 058D09F9195D050800B7D73C /* _ASAsyncTransaction.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = _ASAsyncTransaction.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 058D09F9195D050800B7D73C /* _ASAsyncTransaction.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = _ASAsyncTransaction.mm; sourceTree = ""; }; 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "_ASAsyncTransactionContainer+Private.h"; sourceTree = ""; }; 058D09FB195D050800B7D73C /* _ASAsyncTransactionContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransactionContainer.h; sourceTree = ""; }; 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _ASAsyncTransactionContainer.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -533,6 +549,8 @@ 299DA1A71A828D2900162D41 /* ASBatchContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchContext.h; sourceTree = ""; }; 299DA1A81A828D2900162D41 /* ASBatchContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBatchContext.mm; sourceTree = ""; }; 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASBasicImageDownloaderContextTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionReusableView.h; sourceTree = ""; }; + 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionReusableView.m; sourceTree = ""; }; 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableViewTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; @@ -583,8 +601,6 @@ 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEventLog.h; sourceTree = ""; }; 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEventLog.mm; sourceTree = ""; }; 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASBackgroundLayoutSpecSnapshotTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 69708BA41D76386D005C3CF9 /* ASEqualityHashHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASEqualityHashHelpers.h; path = TextKit/ASEqualityHashHelpers.h; sourceTree = ""; }; - 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASEqualityHashHelpers.mm; path = TextKit/ASEqualityHashHelpers.mm; sourceTree = ""; }; 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASLayoutSpec+Subclasses.h"; sourceTree = ""; }; 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASLayoutSpec+Subclasses.mm"; sourceTree = ""; }; 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASEditableTextNodeTests.m; sourceTree = ""; }; @@ -698,13 +714,13 @@ BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = ""; }; CC0349FF1E5FAF9700626263 /* ASElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASElementMap.h; sourceTree = ""; }; CC034A001E5FAF9700626263 /* ASElementMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASElementMap.m; sourceTree = ""; }; + CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = ""; }; + CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASDisplayNode+Convenience.m"; sourceTree = ""; }; CC034A0B1E60C3D500626263 /* ASRectTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRectTable.h; sourceTree = ""; }; CC034A0C1E60C3D500626263 /* ASRectTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTable.m; sourceTree = ""; }; CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTableTests.m; sourceTree = ""; }; CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+IGListKitMethods.h"; sourceTree = ""; }; CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AsyncDisplayKit+IGListKitMethods.m"; sourceTree = ""; }; - CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = ""; }; - CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASDisplayNode+Convenience.m"; sourceTree = ""; }; CC051F1E1D7A286A006434CB /* ASCALayerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCALayerTests.m; sourceTree = ""; }; CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASUICollectionViewTests.m; sourceTree = ""; }; CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; @@ -772,11 +788,27 @@ DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASCollectionInternal.m; path = Details/ASCollectionInternal.m; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; + E516FC7D1E9FE24200714FF4 /* ASEqualityHashHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHashHelpers.h; sourceTree = ""; }; + E516FC7E1E9FE24200714FF4 /* ASEqualityHashHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEqualityHashHelpers.mm; sourceTree = ""; }; E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTransition.mm; sourceTree = ""; }; E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTransition.h; sourceTree = ""; }; E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutElement.mm; sourceTree = ""; }; E5711A2A1C840C81009619D4 /* ASCollectionElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionElement.h; sourceTree = ""; }; E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionElement.mm; sourceTree = ""; }; + E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionFlowLayoutDelegate.h; sourceTree = ""; }; + E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionFlowLayoutDelegate.m; sourceTree = ""; }; + E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutContext.h; sourceTree = ""; }; + E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutContext.mm; sourceTree = ""; }; + E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutDelegate.h; sourceTree = ""; }; + E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayout.h; sourceTree = ""; }; + E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayout.mm; sourceTree = ""; }; + E5ABAC791E8564EE007AC15C /* ASRectTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRectTable.h; sourceTree = ""; }; + E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTable.m; sourceTree = ""; }; + E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASElementMap.h; sourceTree = ""; }; + E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASElementMap.m; sourceTree = ""; }; + E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionLayoutContext+Private.h"; sourceTree = ""; }; + E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutState.h; sourceTree = ""; }; + E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionLayoutState.m; 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 = ""; }; @@ -970,7 +1002,7 @@ isa = PBXGroup; children = ( CC57EAF91E394EA40034C595 /* Info.plist */, - 044285011BAA3CC700D16268 /* module.modulemap */, + 044285011BAA3CC700D16268 /* AsyncDisplayKit.modulemap */, ); name = "Supporting Files"; sourceTree = ""; @@ -1061,6 +1093,8 @@ children = ( CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */, CC0F885D1E4280B800576FED /* _ASCollectionViewCell.m */, + 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */, + 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */, 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */, 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, 058D09E4195D050800B7D73C /* _ASDisplayView.h */, @@ -1117,6 +1151,7 @@ 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */, 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */, 25B171EA1C12242700508A7A /* Data Controller */, + E5B077EB1E6843AF00C24B5B /* Collection Layout */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */, @@ -1150,10 +1185,8 @@ children = ( CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */, CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.m */, - CC0349FF1E5FAF9700626263 /* ASElementMap.h */, - CC034A001E5FAF9700626263 /* ASElementMap.m */, - CC034A0B1E60C3D500626263 /* ASRectTable.h */, - CC034A0C1E60C3D500626263 /* ASRectTable.m */, + E5ABAC791E8564EE007AC15C /* ASRectTable.h */, + E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */, CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */, CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */, 6947B0BB1E36B4E30007C478 /* Layout */, @@ -1171,6 +1204,9 @@ 044285051BAA63FE00D16268 /* ASBatchFetching.h */, 044285061BAA63FE00D16268 /* ASBatchFetching.m */, CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */, + E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */, + E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */, + E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */, CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */, CC0F885A1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h */, CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m */, @@ -1190,6 +1226,8 @@ 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */, 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */, 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */, + E516FC7D1E9FE24200714FF4 /* ASEqualityHashHelpers.h */, + E516FC7E1E9FE24200714FF4 /* ASEqualityHashHelpers.mm */, 6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */, 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */, 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */, @@ -1224,8 +1262,6 @@ 257754661BED245B00737CA5 /* TextKit */ = { isa = PBXGroup; children = ( - 69708BA41D76386D005C3CF9 /* ASEqualityHashHelpers.h */, - 69708BA51D76386D005C3CF9 /* ASEqualityHashHelpers.mm */, B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */, B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */, 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */, @@ -1267,6 +1303,8 @@ 4640521A1A3F83C40061C0BA /* ASDataController.mm */, E5711A2A1C840C81009619D4 /* ASCollectionElement.h */, E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */, + E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */, + E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */, AC6145401D8AFAE8003D62A2 /* ASSection.h */, AC6145421D8AFD4F003D62A2 /* ASSection.m */, ); @@ -1382,6 +1420,20 @@ path = Debug; sourceTree = ""; }; + E5B077EB1E6843AF00C24B5B /* Collection Layout */ = { + isa = PBXGroup; + children = ( + E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */, + E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */, + E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */, + E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */, + E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */, + E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */, + E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */, + ); + name = "Collection Layout"; + sourceTree = ""; + }; FD40E2760492F0CAAEAD552D /* Pods */ = { isa = PBXGroup; children = ( @@ -1399,8 +1451,14 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */, + E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */, + E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */, + E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */, + E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */, 696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */, 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */, + 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */, 690C356B1E05680300069B91 /* ASDimensionDeprecated.h in Headers */, 683489281D70DE3400327501 /* ASDisplayNode+Deprecated.h in Headers */, 698371DB1E4379CD00437585 /* ASNodeController+Beta.h in Headers */, @@ -1428,7 +1486,6 @@ B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */, B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */, B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */, - CC034A0D1E60C3D500626263 /* ASRectTable.h in Headers */, B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, 34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */, CC55A7111E52A0F200594372 /* ASResponderChainEnumerator.h in Headers */, @@ -1490,6 +1547,7 @@ 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */, DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, + E516FC7F1E9FE24200714FF4 /* ASEqualityHashHelpers.h in Headers */, 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */, 6947B0C31E36B5040007C478 /* ASStackPositionedLayout.h in Headers */, DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */, @@ -1497,13 +1555,14 @@ CC57EAF71E3939350034C595 /* ASCollectionView+Undeprecated.h in Headers */, B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */, AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, - CC034A011E5FAF9700626263 /* ASElementMap.h in Headers */, B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */, + E58E9E491E941DA5004CFC59 /* ASCollectionLayout.h in Headers */, 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */, CC58AA4B1E398E1D002C8CB4 /* ASBlockTypes.h in Headers */, 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */, 692BE8D71E36B65B00C86D87 /* ASLayoutSpecPrivate.h in Headers */, 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, + E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */, DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */, 9F98C0271DBE29FC00476D92 /* ASControlTargetAction.h in Headers */, @@ -1520,7 +1579,6 @@ 698DFF441E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h in Headers */, CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */, 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */, - 69708BA61D76386D005C3CF9 /* ASEqualityHashHelpers.h in Headers */, E5711A2C1C840C81009619D4 /* ASCollectionElement.h in Headers */, 6947B0BE1E36B4E30007C478 /* ASStackUnpositionedLayout.h in Headers */, CC4C2A771D88E3BF0039ACAB /* ASTraceEvent.h in Headers */, @@ -1541,6 +1599,7 @@ B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */, B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, 34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */, + E5ABAC7B1E8564EE007AC15C /* ASRectTable.h in Headers */, B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */, 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, @@ -1830,12 +1889,14 @@ DEB8ED7C1DD003D300DBDE55 /* ASLayoutTransition.mm in Sources */, 9F98C0261DBE29E000476D92 /* ASControlTargetAction.m in Sources */, 9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */, + 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */, 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, 690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */, B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.mm in Sources */, + E516FC801E9FE24200714FF4 /* ASEqualityHashHelpers.mm in Sources */, 6947B0C51E36B5040007C478 /* ASStackPositionedLayout.mm in Sources */, B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */, AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */, @@ -1848,10 +1909,10 @@ DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */, B350624C1B010EFD0018CF92 /* _ASPendingState.mm in Sources */, 698371DC1E4379CD00437585 /* ASNodeController+Beta.m in Sources */, - 69708BA81D76386D005C3CF9 /* ASEqualityHashHelpers.mm in Sources */, 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */, + E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */, 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, @@ -1859,6 +1920,7 @@ B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */, AC47D9421B3B891B00AAEE9D /* ASCellNode.mm in Sources */, + E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.mm in Sources */, 34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */, 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */, @@ -1867,7 +1929,6 @@ 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */, 9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */, 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, - CC034A021E5FAF9700626263 /* ASElementMap.m in Sources */, B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, @@ -1881,10 +1942,10 @@ B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */, DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */, 254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */, - CC034A0E1E60C3D500626263 /* ASRectTable.m in Sources */, 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */, E5711A301C840C96009619D4 /* ASCollectionElement.mm in Sources */, B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */, + E5E281761E71C845006B67C2 /* ASCollectionLayoutState.m in Sources */, B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */, B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */, B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */, @@ -1896,6 +1957,7 @@ CC0F885F1E4280B800576FED /* _ASCollectionViewCell.m in Sources */, CC2F65EF1E5FFB1600DA57C9 /* ASMutableElementMap.m in Sources */, B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, + E58E9E4A1E941DA5004CFC59 /* ASCollectionLayout.mm in Sources */, 6947B0C01E36B4E30007C478 /* ASStackUnpositionedLayout.mm in Sources */, 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */, @@ -1923,6 +1985,7 @@ 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */, 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, 90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */, + E5ABAC7C1E8564EE007AC15C /* ASRectTable.m in Sources */, 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */, B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */, 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */, @@ -1932,6 +1995,7 @@ 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.m in Sources */, 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */, CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */, + E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, @@ -2170,13 +2234,13 @@ GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "Source/AsyncDisplayKit-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = Source/module.modulemap; + MODULEMAP_FILE = Source/AsyncDisplayKit.modulemap; MTL_ENABLE_DEBUG_INFO = YES; + OTHER_CFLAGS = "-Wundef"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AsyncDisplayKit; SKIP_INSTALL = YES; @@ -2202,12 +2266,12 @@ GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "Source/AsyncDisplayKit-Prefix.pch"; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = Source/module.modulemap; + MODULEMAP_FILE = Source/AsyncDisplayKit.modulemap; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-Wundef"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AsyncDisplayKit; SKIP_INSTALL = YES; @@ -2315,12 +2379,12 @@ GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "Source/AsyncDisplayKit-Prefix.pch"; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = Source/module.modulemap; + MODULEMAP_FILE = Source/AsyncDisplayKit.modulemap; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-Wundef"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = AsyncDisplayKit; SKIP_INSTALL = YES; diff --git a/BUCK b/BUCK index c5d5e0178e..2527c2f053 100755 --- a/BUCK +++ b/BUCK @@ -88,7 +88,6 @@ asyncdisplaykit_library( for name in ['AsyncDisplayKit', 'AsyncDisplayKit-PINRemoteImage']: asyncdisplaykit_library( name = name, - additional_preprocessor_flags = ['-DPIN_REMOTE_IMAGE=1'], deps = [ '//Pods/PINRemoteImage:PINRemoteImage-PINCache', ], diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2861dc49e5..e1df6d05e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,10 +61,6 @@ Complete your CLA here: We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. -Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe -disclosure of security bugs. In those cases, please go through the process -outlined on that page and do not file a public issue. - ## 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. diff --git a/README.md b/README.md index 5f42670c59..01b0a51945 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,43 @@ +

AsyncDisplayKit has been moved and renamed: Texture

+Texture Logo +

Learn more here

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ![AsyncDisplayKit](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/logo.png) [![Apps Using](https://img.shields.io/cocoapods/at/AsyncDisplayKit.svg?label=Apps%20Using%20ASDK&colorB=28B9FE)](http://cocoapods.org/pods/AsyncDisplayKit) diff --git a/Source/ASCollectionNode+Beta.h b/Source/ASCollectionNode+Beta.h index 788d60fd25..11768b5f63 100644 --- a/Source/ASCollectionNode+Beta.h +++ b/Source/ASCollectionNode+Beta.h @@ -10,7 +10,8 @@ #import -@protocol ASCollectionViewLayoutFacilitatorProtocol; +@protocol ASCollectionViewLayoutFacilitatorProtocol, ASCollectionLayoutDelegate; +@class ASElementMap; NS_ASSUME_NONNULL_BEGIN @@ -23,8 +24,17 @@ NS_ASSUME_NONNULL_BEGIN */ @property (strong, nonatomic, nullable) Class collectionViewClass; +/** + * The elements that are currently displayed. The "UIKit index space". Must be accessed on main thread. + */ +@property (strong, nonatomic, readonly) ASElementMap *visibleElements; + +@property (strong, readonly, nullable) id layoutDelegate; + - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator; +- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(nullable id)layoutFacilitator; + - (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); - (void)endUpdatesAnimated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index 262d52fa12..e8163510cb 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -96,6 +96,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL allowsMultipleSelection; +/** + * The layout used to organize the node's items. + * + * @discussion Assigning a new layout object to this property causes the new layout to be applied (without animations) to the node’s items. + */ +@property (nonatomic, strong) UICollectionViewLayout *collectionViewLayout; /** * Tuning parameters for a range type in full mode. diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index 9ae9f626bd..aa320f495f 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -11,10 +11,12 @@ // #import +#import #import #import #import +#import #import #import #import @@ -33,6 +35,7 @@ @interface _ASCollectionPendingState : NSObject @property (weak, nonatomic) id delegate; @property (weak, nonatomic) id dataSource; +@property (strong, nonatomic) UICollectionViewLayout *collectionViewLayout; @property (nonatomic, assign) ASLayoutRangeMode rangeMode; @property (nonatomic, assign) BOOL allowsSelection; // default is YES @property (nonatomic, assign) BOOL allowsMultipleSelection; // default is NO @@ -133,13 +136,21 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionVi return [self initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil]; } +- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(id)layoutFacilitator +{ + return [self initWithFrame:CGRectZero collectionViewLayout:[[ASCollectionLayout alloc] initWithLayoutDelegate:layoutDelegate] layoutFacilitator:layoutFacilitator]; +} + - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator { if (self = [super init]) { + // Must call the setter here to make sure pendingState is created and the layout is configured. + [self setCollectionViewLayout:layout]; + __weak __typeof__(self) weakSelf = self; [self setViewBlock:^{ __typeof__(self) strongSelf = weakSelf; - return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(strongSelf)]; + return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(strongSelf)]; }]; } return self; @@ -162,10 +173,12 @@ - (void)didLoad view.inverted = pendingState.inverted; view.allowsSelection = pendingState.allowsSelection; view.allowsMultipleSelection = pendingState.allowsMultipleSelection; - + if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; } + + // Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block. } } @@ -180,18 +193,20 @@ - (void)clearContents [self.rangeController clearContents]; } -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - [self.rangeController clearPreloadedData]; -} - - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState { [super interfaceStateDidChange:newState fromState:oldState]; [ASRangeController layoutDebugOverlayIfNeeded]; } +- (void)didEnterPreloadState +{ + // Intentionally allocate the view here so that super will trigger a layout pass on it which in turn will trigger the intial data load. + // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. + [self view]; + [super didEnterPreloadState]; +} + #if ASRangeControllerLoggingEnabled - (void)didEnterVisibleState { @@ -206,9 +221,15 @@ - (void)didExitVisibleState } #endif +- (void)didExitPreloadState +{ + [super didExitPreloadState]; + [self.rangeController clearPreloadedData]; +} + #pragma mark Setter / Getter -// TODO: Implement this without the view. +// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection - (ASDataController *)dataController { return self.view.dataController; @@ -340,6 +361,42 @@ - (BOOL)allowsMultipleSelection } } +- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout +{ + if ([self pendingState]) { + [self _configureCollectionViewLayout:layout]; + _pendingState.collectionViewLayout = layout; + } else { + [self _configureCollectionViewLayout:layout]; + self.view.collectionViewLayout = layout; + } +} + +- (UICollectionViewLayout *)collectionViewLayout +{ + if ([self pendingState]) { + return _pendingState.collectionViewLayout; + } else { + return self.view.collectionViewLayout; + } +} + +- (ASElementMap *)visibleElements +{ + ASDisplayNodeAssertMainThread(); + // TODO Own the data controller when view is not yet loaded + return self.dataController.visibleMap; +} + +- (id)layoutDelegate +{ + UICollectionViewLayout *layout = self.collectionViewLayout; + if ([layout isKindOfClass:[ASCollectionLayout class]]) { + return ((ASCollectionLayout *)layout).layoutDelegate; + } + return nil; +} + #pragma mark - Range Tuning - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType @@ -662,4 +719,14 @@ - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; return result; } +#pragma mark - Private methods + +- (void)_configureCollectionViewLayout:(UICollectionViewLayout *)layout +{ + if ([layout isKindOfClass:[ASCollectionLayout class]]) { + ASCollectionLayout *collectionLayout = (ASCollectionLayout *)layout; + collectionLayout.collectionNode = self; + } +} + @end diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index d8a2b33dc9..f8b466bc78 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -9,13 +9,14 @@ // #import -#import #import #import #import #import #import +#import #import +#import #import #import #import @@ -24,16 +25,16 @@ #import #import #import -#import #import #import -#import +#import #import #import #import #import #import #import +#import /** * A macro to get self.collectionNode and assign it to a local variable, or return @@ -146,6 +147,7 @@ @interface ASCollectionView () )asyncDelegate _asyncDelegateFlags.scrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; + _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; _asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]; _asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]; _asyncDelegateFlags.collectionViewWillDisplayNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNode:forItemAtIndexPath:)]; @@ -532,10 +539,13 @@ - (void)setAsyncDelegate:(id)asyncDelegate } } -- (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout +- (void)setCollectionViewLayout:(nonnull UICollectionViewLayout *)collectionViewLayout { + ASDisplayNodeAssertMainThread(); [super setCollectionViewLayout:collectionViewLayout]; + [self _configureCollectionViewLayout:collectionViewLayout]; + // Trigger recreation of layout inspector with new collection view layout if (_layoutInspector != nil) { _layoutInspector = nil; @@ -746,6 +756,14 @@ - (NSArray *)visibleNodes #pragma mark Internal +- (void)_configureCollectionViewLayout:(nonnull UICollectionViewLayout *)layout +{ + _hasDataControllerLayoutDelegate = [layout conformsToProtocol:@protocol(ASDataControllerLayoutDelegate)]; + if (_hasDataControllerLayoutDelegate) { + _dataController.layoutDelegate = (id)layout; + } +} + /** 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 @@ -793,9 +811,11 @@ - (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))co [_changeSet addCompletionHandler:completion]; if (_batchUpdateCount == 0) { - _changeSet.animated = animated; - [_dataController updateWithChangeSet:_changeSet]; + _ASHierarchyChangeSet *changeSet = _changeSet; + // Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop _changeSet = nil; + changeSet.animated = animated; + [_dataController updateWithChangeSet:changeSet]; } } @@ -819,7 +839,7 @@ - (void)registerSupplementaryNodeOfKind:(NSString *)elementKind { ASDisplayNodeAssert(elementKind != nil, @"A kind is needed for supplementary node registration"); [_registeredSupplementaryKinds addObject:elementKind]; - [self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier]; + [self registerClass:[_ASCollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier]; } - (void)insertSections:(NSIndexSet *)sections @@ -922,7 +942,9 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection ASCellNode *cell = [self nodeForItemAtIndexPath:indexPath]; if (cell.shouldUseUIKitCell) { if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) { - return [(id)_asyncDelegate collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:indexPath]; + CGSize size = [(id)_asyncDelegate collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:indexPath]; + cell.style.preferredSize = size; + return size; } } ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; @@ -970,15 +992,17 @@ - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopViewForSupplementaryElement && (_asyncDataSourceFlags.interopAlwaysDequeue || node.shouldUseUIKitCell); if (shouldDequeueExternally) { + // This codepath is used for both IGListKit mode, and app-level UICollectionView interop. view = [(id)_asyncDataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; } else { + ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self); view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; } - - if (!node.shouldUseUIKitCell) { - ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self); + + if (_ASCollectionReusableView *reusableView = ASDynamicCast(view, _ASCollectionReusableView)) { + reusableView.node = node; } - + if (node) { [_rangeController configureContentView:view forCellNode:node]; } @@ -1092,8 +1116,14 @@ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:( cell.layoutAttributes = nil; } -- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath +- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(_ASCollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { + // This is a safeguard similar to the behavior for cells in -[ASCollectionView collectionView:willDisplayCell:forItemAtIndexPath:] + // It ensures _ASCollectionReusableView receives layoutAttributes and calls applyLayoutAttributes. + if (view.layoutAttributes == nil) { + view.layoutAttributes = [collectionView layoutAttributesForSupplementaryElementOfKind:elementKind atIndexPath:indexPath]; + } + if (_asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath]; @@ -1317,6 +1347,15 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi } } +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView +{ + _deceleratingVelocity = CGPointZero; + + if (_asyncDelegateFlags.scrollViewDidEndDecelerating) { + [_asyncDelegate scrollViewDidEndDecelerating:scrollView]; + } +} + - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { @@ -1503,7 +1542,6 @@ - (void)_beginBatchFetching } } - #pragma mark - ASDataControllerSource - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath @@ -1566,11 +1604,6 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt return block; } -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; -} - - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section { if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) { @@ -1665,6 +1698,11 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementa } } +- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +{ + return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; +} + - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { if (_layoutInspectorFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath) { @@ -1880,6 +1918,7 @@ - (void)rangeController:(ASRangeController *)rangeController didUpdateWithChange } #pragma mark - ASCellNodeDelegate + - (void)nodeSelectedStateDidChange:(ASCellNode *)node { NSIndexPath *indexPath = [self indexPathForNode:node]; @@ -2018,6 +2057,10 @@ - (void)didMoveToWindow */ - (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds newValue:(CGRect)newBounds { + if (_hasDataControllerLayoutDelegate) { + // Let the layout delegate handle bounds changes if it's available. + return; + } if (self.collectionViewLayout == nil) { return; } diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index 797073debf..97fcd97568 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -197,7 +197,7 @@ extern NSInteger const ASDefaultDrawingPriority; /** * Set the block that should be used to load this node's layer. * - * @param viewBlock The block that creates a layer for this node. + * @param layerBlock The block that creates a layer for this node. * * @precondition The node is not yet loaded. * @@ -639,6 +639,11 @@ extern NSInteger const ASDefaultDrawingPriority; */ - (void)setNeedsLayout; +/** + * Performs a layout pass on the node. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread. + */ +- (void)layoutIfNeeded; + @property (nonatomic, strong, nullable) id contents; // default=nil @property (nonatomic, assign) BOOL clipsToBounds; // default==NO @property (nonatomic, getter=isOpaque) BOOL opaque; // default==YES diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index b4089791e5..8478e0a016 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -38,19 +38,6 @@ #import #import -/** - * Assert if the current thread owns a mutex. - * This assertion is useful when you want to indicate and enforce the locking policy/expectation of methods. - * To determine when and which methods acquired a (recursive) mutex (to debug deadlocks, for example), - * put breakpoints at some of these assertions. When the breakpoints hit, walk through stack trace frames - * and check ownership count of the mutex. - */ -#if CHECK_LOCKING_SAFETY - #define ASDisplayNodeAssertLockUnownedByCurrentThread(lock) ASDisplayNodeAssertFalse(lock.ownedByCurrentThread()) -#else - #define ASDisplayNodeAssertLockUnownedByCurrentThread(lock) -#endif - #if ASDisplayNodeLoggingEnabled #define LOG(...) NSLog(__VA_ARGS__) #else @@ -998,7 +985,7 @@ - (void)invalidateCalculatedLayout - (void)__layout { - ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertThreadAffinity(self); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); { @@ -1027,8 +1014,12 @@ - (void)__layout [self _locked_layoutPlaceholderIfNecessary]; } - [self layout]; - [self layoutDidFinish]; + [self _layoutSublayouts]; + + ASPerformBlockOnMainThread(^{ + [self layout]; + [self layoutDidFinish]; + }); } /// Needs to be called with lock held @@ -1067,7 +1058,7 @@ - (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds std::shared_ptr nextLayout = _pendingDisplayNodeLayout; #define layoutSizeDifferentFromBounds !CGSizeEqualToSize(nextLayout->layout.size, boundsSizeForLayout) - // nextLayout was likely created by a call to layoutThatFits:, check if is valid and can be applied. + // nextLayout was likely created by a call to layoutThatFits:, check if it is valid and can be applied. // If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr-> if (nextLayout == nullptr || nextLayout->isDirty() == YES || layoutSizeDifferentFromBounds) { // Use the last known constrainedSize passed from a parent during layout (if never, use bounds). @@ -1418,12 +1409,13 @@ - (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size - (void)layout { - [self _layoutSublayouts]; + ASDisplayNodeAssertMainThread(); + // Subclass hook } - (void)_layoutSublayouts { - ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertThreadAffinity(self); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); ASLayout *layout; @@ -3733,6 +3725,10 @@ - (void)didEnterPreloadState ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); [_interfaceStateDelegate didEnterPreloadState]; + // Trigger a layout pass to ensure all subnodes have the correct size to preload their content. + // This is important for image nodes, as well as collection and table nodes. + [self layoutIfNeeded]; + if (_methodOverrides & ASDisplayNodeMethodOverrideFetchData) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" diff --git a/Source/ASImageNode+AnimatedImage.mm b/Source/ASImageNode+AnimatedImage.mm index 14f7f4f155..cc1e614543 100644 --- a/Source/ASImageNode+AnimatedImage.mm +++ b/Source/ASImageNode+AnimatedImage.mm @@ -24,6 +24,7 @@ #import #import +#define ASAnimatedImageDebug 0 @interface ASNetworkImageNode (Private) - (void)_locked_setDefaultImage:(UIImage *)image; diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index 1591d8187f..5a461170f0 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -97,14 +97,8 @@ - (NSUInteger)hash // 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), - + ASHashFromCGSize(_backingSize), + ASHashFromCGRect(_imageDrawRect), AS::hash()(_isOpaque), [_backgroundColor hash], AS::hash()((void*)_preContextBlock), diff --git a/Source/ASMapNode.mm b/Source/ASMapNode.mm index 6381ac565d..bd6be23e39 100644 --- a/Source/ASMapNode.mm +++ b/Source/ASMapNode.mm @@ -8,6 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import + #if TARGET_OS_IOS #import diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index f9756a7761..e1d9f68551 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -18,7 +18,7 @@ #import #import -#if PIN_REMOTE_IMAGE +#if AS_PIN_REMOTE_IMAGE #import #else #import @@ -172,7 +172,7 @@ - (instancetype)initWithCache:(id)cache downloader:(id +#import #import #import #import @@ -19,7 +20,7 @@ #import #import -#if PIN_REMOTE_IMAGE +#if AS_PIN_REMOTE_IMAGE #import #endif @@ -58,6 +59,7 @@ @interface ASNetworkImageNode () unsigned int downloaderImplementsSetProgress:1; unsigned int downloaderImplementsSetPriority:1; unsigned int downloaderImplementsAnimatedImage:1; + unsigned int downloaderImplementsCancelWithResume:1; } _downloaderFlags; // Immutable and set on init only. We don't need to lock in this case. @@ -85,6 +87,7 @@ - (instancetype)initWithCache:(id)cache downloader:(id)cache downloader:(id #import #import #import @@ -282,13 +283,13 @@ - (void)processQueue // itemsToProcess will be empty if _queueConsumer == nil so no need to check again. if (itemsToProcess.empty() == false) { -#if ASRunloopQueueLoggingEnabled +#if ASRunLoopQueueLoggingEnabled NSLog(@"<%@> - Starting processing of: %ld", self, itemsToProcess.size()); #endif auto itemsEnd = itemsToProcess.cend(); for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) { _queueConsumer(*iterator, isQueueDrained && iterator == itemsEnd - 1); -#if ASRunloopQueueLoggingEnabled +#if ASRunLoopQueueLoggingEnabled NSLog(@"<%@> - Finished processing 1 item", self); #endif } diff --git a/Source/ASSectionController.h b/Source/ASSectionController.h index dbf39d93c0..f57413f07d 100644 --- a/Source/ASSectionController.h +++ b/Source/ASSectionController.h @@ -7,9 +7,13 @@ // #import +#import +#import NS_ASSUME_NONNULL_BEGIN +@class ASBatchContext; + /** * A protocol that your section controllers should conform to, * in addition to IGListSectionType, in order to be used with AsyncDisplayKit. diff --git a/Source/ASSupplementaryNodeSource.h b/Source/ASSupplementaryNodeSource.h index 9d3233c9e2..52ad429377 100644 --- a/Source/ASSupplementaryNodeSource.h +++ b/Source/ASSupplementaryNodeSource.h @@ -7,12 +7,11 @@ // #import +#import #import NS_ASSUME_NONNULL_BEGIN -@class ASCellNode; - @protocol ASSupplementaryNodeSource /** diff --git a/Source/ASTableNode.mm b/Source/ASTableNode.mm index 7f27bef456..92fb5b006d 100644 --- a/Source/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -64,10 +64,6 @@ @interface ASTableNode () @property (nonatomic, strong) _ASTablePendingState *pendingState; @end -@interface ASTableView () -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass; -@end - @implementation ASTableNode #pragma mark Lifecycle @@ -126,18 +122,20 @@ - (void)clearContents [self.rangeController clearContents]; } -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - [self.rangeController clearPreloadedData]; -} - - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState { [super interfaceStateDidChange:newState fromState:oldState]; [ASRangeController layoutDebugOverlayIfNeeded]; } +- (void)didEnterPreloadState +{ + // Intentionally allocate the view here so that super will trigger a layout pass on it which in turn will trigger the intial data load. + // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. + [self view]; + [super didEnterPreloadState]; +} + #if ASRangeControllerLoggingEnabled - (void)didEnterVisibleState { @@ -152,9 +150,15 @@ - (void)didExitVisibleState } #endif +- (void)didExitPreloadState +{ + [super didExitPreloadState]; + [self.rangeController clearPreloadedData]; +} + #pragma mark Setter / Getter -// TODO: Implement this without the view. +// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection - (ASDataController *)dataController { return self.view.dataController; diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 7a8353fe0c..e2b92ab140 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -14,7 +14,6 @@ #import #import #import -#import #import #import #import @@ -182,6 +181,7 @@ @interface ASTableView () )_proxyDataSource; - + [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; } @@ -432,6 +432,7 @@ - (void)setAsyncDelegate:(id)asyncDelegate _asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didEndDisplayingRowWithNode:)]; _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; + _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; _asyncDelegateFlags.tableViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; _asyncDelegateFlags.tableNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableNode:willBeginBatchFetchWithContext:)]; _asyncDelegateFlags.shouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]; @@ -689,9 +690,11 @@ - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL completed))c [_changeSet addCompletionHandler:completion]; if (_batchUpdateCount == 0) { - _changeSet.animated = animated; - [_dataController updateWithChangeSet:_changeSet]; + _ASHierarchyChangeSet *changeSet = _changeSet; + // Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop _changeSet = nil; + changeSet.animated = animated; + [_dataController updateWithChangeSet:changeSet]; } } @@ -1198,6 +1201,15 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi } } +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView +{ + _deceleratingVelocity = CGPointZero; + + if (_asyncDelegateFlags.scrollViewDidEndDecelerating) { + [_asyncDelegate scrollViewDidEndDecelerating:scrollView]; + } +} + - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) { @@ -1543,7 +1555,7 @@ - (void)rangeController:(ASRangeController *)rangeController didUpdateWithChange [changeSet executeCompletionHandlerWithFinished:YES]; } -#pragma mark - ASDataControllerDelegate +#pragma mark - ASDataControllerSource - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { ASCellNodeBlock block = nil; diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index 35e4209b72..bd09de7c86 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -27,6 +27,7 @@ #import #import +#import #import /** @@ -51,17 +52,6 @@ #pragma mark - ASTextKitRenderer -// Not used at the moment but handy to have -/*ASDISPLAYNODE_INLINE NSUInteger ASHashFromCGRect(CGRect rect) -{ - return ((*(NSUInteger *)&rect.origin.x << 10 ^ *(NSUInteger *)&rect.origin.y) + (*(NSUInteger *)&rect.size.width << 10 ^ *(NSUInteger *)&rect.size.height)); -}*/ - -ASDISPLAYNODE_INLINE NSUInteger ASHashFromCGSize(CGSize size) -{ - return ((*(NSUInteger *)&size.width << 10 ^ *(NSUInteger *)&size.height)); -} - @interface ASTextNodeRendererKey : NSObject @property (assign, nonatomic) ASTextKitAttributes attributes; @property (assign, nonatomic) CGSize constrainedSize; diff --git a/Source/ASVideoPlayerNode.mm b/Source/ASVideoPlayerNode.mm index 941bd3c53a..f9eb4b5391 100644 --- a/Source/ASVideoPlayerNode.mm +++ b/Source/ASVideoPlayerNode.mm @@ -10,6 +10,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import + #if TARGET_OS_IOS #import diff --git a/Source/ASViewController.mm b/Source/ASViewController.mm index 7d5f6db5c6..c3c1ad7307 100644 --- a/Source/ASViewController.mm +++ b/Source/ASViewController.mm @@ -12,7 +12,6 @@ #import #import -#import #import #import #import diff --git a/Source/AsyncDisplayKit+IGListKitMethods.h b/Source/AsyncDisplayKit+IGListKitMethods.h index 5bc08f613b..b8b01a37a9 100644 --- a/Source/AsyncDisplayKit+IGListKitMethods.h +++ b/Source/AsyncDisplayKit+IGListKitMethods.h @@ -6,7 +6,9 @@ // Copyright © 2017 Facebook. All rights reserved. // -#if IG_LIST_KIT +#import + +#if AS_IG_LIST_KIT #import #import diff --git a/Source/AsyncDisplayKit+IGListKitMethods.m b/Source/AsyncDisplayKit+IGListKitMethods.m index a4ac764c53..7e61c2806b 100644 --- a/Source/AsyncDisplayKit+IGListKitMethods.m +++ b/Source/AsyncDisplayKit+IGListKitMethods.m @@ -6,7 +6,9 @@ // Copyright © 2017 Facebook. All rights reserved. // -#if IG_LIST_KIT +#import + +#if AS_IG_LIST_KIT #import "AsyncDisplayKit+IGListKitMethods.h" #import @@ -44,4 +46,4 @@ + (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInte @end -#endif // IG_LIST_KIT +#endif // AS_IG_LIST_KIT diff --git a/Source/AsyncDisplayKit-Prefix.pch b/Source/AsyncDisplayKit-Prefix.pch deleted file mode 100644 index 374bad0162..0000000000 --- a/Source/AsyncDisplayKit-Prefix.pch +++ /dev/null @@ -1,24 +0,0 @@ -// -// Prefix header -// -// The contents of this file are implicitly included at the beginning of every source file. -// - -#ifdef __OBJC__ - #import -#endif - -// Some build systems (Cocoapods, Buck, Bazel, etc) will define these flags manually if the functionality -// is needed by the app. Carthage in particular, or if a user forgets to set the build flag, benefit from -// checking if each flag is not defined and then setting it to whether or not the header is accessible. - -#ifndef PIN_REMOTE_IMAGE -// For Carthage or manual builds, this will define PIN_REMOTE_IMAGE if the header is available in the -// search path e.g. they've dragged in the framework (technically this will not be able to detect if -// a user does not include the framework in the link binary with build step). - #define PIN_REMOTE_IMAGE __has_include() -#endif - -#ifndef IG_LIST_KIT - #define IG_LIST_KIT __has_include() -#endif diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index 5e84654d17..c0b228827f 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -8,6 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import #import #import @@ -37,9 +38,15 @@ #import #import +#import +#import +#import +#import + #import #import -#if IG_LIST_KIT + +#if AS_IG_LIST_KIT #import #import #endif @@ -81,7 +88,6 @@ #import #import #import -#import #import #import #import diff --git a/Source/module.modulemap b/Source/AsyncDisplayKit.modulemap similarity index 100% rename from Source/module.modulemap rename to Source/AsyncDisplayKit.modulemap diff --git a/Source/Base/ASAvailability.h b/Source/Base/ASAvailability.h index 48b65ac088..7f131ed75e 100644 --- a/Source/Base/ASAvailability.h +++ b/Source/Base/ASAvailability.h @@ -23,9 +23,6 @@ #define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) #define AS_AT_LEAST_IOS10 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) -#define AS_TARGET_OS_OSX (!(TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH)) -#define AS_TARGET_OS_IOS TARGET_OS_IPHONE - // If Yoga is available, make it available anywhere we use ASAvailability. // This reduces Yoga-specific code in other files. #ifndef YOGA_HEADER_PATH @@ -36,45 +33,11 @@ #define YOGA __has_include(YOGA_HEADER_PATH) #endif -#if AS_TARGET_OS_OSX - -#define UIEdgeInsets NSEdgeInsets -#define NSStringFromCGSize NSStringFromSize -#define NSStringFromCGPoint NSStringFromPoint - -#import - -@interface NSValue (ASAvailability) -+ (NSValue *)valueWithCGPoint:(CGPoint)point; -+ (NSValue *)valueWithCGSize:(CGSize)size; -- (CGRect)CGRectValue; -- (CGPoint)CGPointValue; -- (CGSize)CGSizeValue; -@end +#define AS_PIN_REMOTE_IMAGE __has_include() +#define AS_IG_LIST_KIT __has_include() -@implementation NSValue(ASAvailability) -+ (NSValue *)valueWithCGPoint:(CGPoint)point -{ - return [self valueWithPoint:point]; -} -+ (NSValue *)valueWithCGSize:(CGSize)size -{ - return [self valueWithSize:size]; -} -- (CGRect)CGRectValue -{ - return self.rectValue; -} - -- (CGPoint)CGPointValue -{ - return self.pointValue; -} - -- (CGSize)CGSizeValue -{ - return self.sizeValue; -} -@end - -#endif +/** + * For IGListKit versions < 3.0, you have to use IGListCollectionView. + * For 3.0 and later, that class is removed and you use UICollectionView. + */ +#define IG_LIST_COLLECTION_VIEW __has_include() diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index c854206535..c4e8231a20 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -12,14 +12,6 @@ #import -#ifndef PIN_REMOTE_IMAGE -#if __has_include() -#define PIN_REMOTE_IMAGE 1 -#else -#define PIN_REMOTE_IMAGE 0 -#endif -#endif - // The C++ compiler mangles C function names. extern "C" { /* your C functions */ } prevents this. // You should wrap all C function prototypes declared in headers with ASDISPLAYNODE_EXTERN_C_BEGIN/END, even if // they are included only from .m (Objective-C) files. It's common for .m files to start using C++ diff --git a/Source/Base/ASLog.h b/Source/Base/ASLog.h index fbc12d17c9..6d061a7f1b 100644 --- a/Source/Base/ASLog.h +++ b/Source/Base/ASLog.h @@ -10,7 +10,6 @@ #pragma once -#import #define ASMultiplexImageNodeLogDebug(...) #define ASMultiplexImageNodeCLogDebug(...) @@ -19,7 +18,7 @@ #define ASMultiplexImageNodeCLogError(...) // Note: `` only exists in Xcode 8 and later. -#if PROFILE && __has_include("") +#if defined(PROFILE) && __has_include("") #import diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.h b/Source/Details/ASCollectionFlowLayoutDelegate.h new file mode 100644 index 0000000000..515435bb75 --- /dev/null +++ b/Source/Details/ASCollectionFlowLayoutDelegate.h @@ -0,0 +1,22 @@ +// +// ASCollectionFlowLayoutDelegate.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 28/2/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED + +@interface ASCollectionFlowLayoutDelegate : NSObject + +- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.m b/Source/Details/ASCollectionFlowLayoutDelegate.m new file mode 100644 index 0000000000..cc21f401ed --- /dev/null +++ b/Source/Details/ASCollectionFlowLayoutDelegate.m @@ -0,0 +1,81 @@ +// ASCollectionFlowLayoutDelegate.m +// AsyncDisplayKit +// +// Created by Huy Nguyen on 28/2/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import + +@implementation ASCollectionFlowLayoutDelegate { + ASScrollDirection _scrollableDirections; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _scrollableDirections = ASScrollDirectionVerticalDirections; + } + return self; +} + +- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections +{ + self = [self init]; + if (self) { + _scrollableDirections = scrollableDirections; + } + return self; +} + +- (ASSizeRange)sizeRangeThatFits:(CGSize)viewportSize +{ + ASSizeRange sizeRange = ASSizeRangeUnconstrained; + if (ASScrollDirectionContainsVerticalDirection(_scrollableDirections) == NO) { + sizeRange.min.height = viewportSize.height; + sizeRange.max.height = viewportSize.height; + } + if (ASScrollDirectionContainsHorizontalDirection(_scrollableDirections) == NO) { + sizeRange.min.width = viewportSize.width; + sizeRange.max.width = viewportSize.width; + } + return sizeRange; +} + +- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements +{ + return nil; +} + +- (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context +{ + ASElementMap *elements = context.elements; + NSMutableArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); + if (children.count == 0) { + return [[ASCollectionLayoutState alloc] initWithElements:elements + contentSize:CGSizeZero + elementToLayoutArrtibutesMap:[NSMapTable weakToStrongObjectsMapTable]]; + } + + ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + flexWrap:ASStackLayoutFlexWrapWrap + alignContent:ASStackLayoutAlignContentStart + children:children]; + stackSpec.concurrent = YES; + ASLayout *layout = [stackSpec layoutThatFits:[self sizeRangeThatFits:context.viewportSize]]; + return [[ASCollectionLayoutState alloc] initWithElements:elements layout:layout]; +} + +@end diff --git a/Source/Details/ASCollectionLayoutContext.h b/Source/Details/ASCollectionLayoutContext.h new file mode 100644 index 0000000000..dc64a7dc6f --- /dev/null +++ b/Source/Details/ASCollectionLayoutContext.h @@ -0,0 +1,29 @@ +// +// ASCollectionLayoutContext.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 21/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +@class ASElementMap; + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED + +@interface ASCollectionLayoutContext : NSObject + +@property (nonatomic, assign, readonly) CGSize viewportSize; +@property (nonatomic, strong, readonly) ASElementMap *elements; +@property (nonatomic, strong, readonly, nullable) id additionalInfo; + +- (instancetype)init __unavailable; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionLayoutContext.mm b/Source/Details/ASCollectionLayoutContext.mm new file mode 100644 index 0000000000..8ba54e25b2 --- /dev/null +++ b/Source/Details/ASCollectionLayoutContext.mm @@ -0,0 +1,59 @@ +// +// ASCollectionLayoutContext.mm +// AsyncDisplayKit +// +// Created by Huy Nguyen on 21/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +#import +#import +#import +#import + +@implementation ASCollectionLayoutContext + +- (instancetype)initWithViewportSize:(CGSize)viewportSize elements:(ASElementMap *)elements additionalInfo:(id)additionalInfo +{ + self = [super init]; + if (self) { + _viewportSize = viewportSize; + _elements = elements; + _additionalInfo = additionalInfo; + } + return self; +} + +- (BOOL)isEqualToContext:(ASCollectionLayoutContext *)context +{ + if (context == nil) { + return NO; + } + return CGSizeEqualToSize(_viewportSize, context.viewportSize) && ASObjectIsEqual(_elements, context.elements) && ASObjectIsEqual(_additionalInfo, context.additionalInfo); +} + +- (BOOL)isEqual:(id)other +{ + if (self == other) { + return YES; + } + if (! [other isKindOfClass:[ASCollectionLayoutContext class]]) { + return NO; + } + return [self isEqualToContext:other]; +} + +- (NSUInteger)hash +{ + NSUInteger subhashes[] = { + ASHashFromCGSize(_viewportSize), + [_elements hash], + [_additionalInfo hash] + }; + return ASIntegerArrayHash(subhashes, sizeof(subhashes) / sizeof(subhashes[0])); +} + +@end diff --git a/Source/Details/ASCollectionLayoutDelegate.h b/Source/Details/ASCollectionLayoutDelegate.h new file mode 100644 index 0000000000..c077578f1b --- /dev/null +++ b/Source/Details/ASCollectionLayoutDelegate.h @@ -0,0 +1,45 @@ +// +// ASCollectionLayoutDelegate.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 21/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +@class ASElementMap, ASCollectionLayoutContext, ASCollectionLayoutState; + +NS_ASSUME_NONNULL_BEGIN + +@protocol ASCollectionLayoutDelegate + +/** + * @abstract Returns any additional information needed for a coming layout pass with the given elements. + * + * @discussion The returned object must support equality and hashing (i.e `-isEqual:` and `-hash` must be properly implemented). + * + * @discussion This method will be called on main thread. + */ +- (nullable id)additionalInfoForLayoutWithElements:(ASElementMap *)elements; + +/** + * @abstract Prepares and returns a new layout for given context. + * + * @param context A context that contains all elements to be laid out and any additional information needed. + * + * @return The new layout calculated for the given context. + * + * @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements. + * As a result, this method should rely solely on the given context and should not reach out to other objects for information not available in the context. + * + * @discussion This method will be called on background theads. It must be thread-safe and should not change any internal state of this object. + * + * @discussion This method must block its calling thread. It can dispatch to other theads to reduce blocking time. + */ +- (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionLayoutState.h b/Source/Details/ASCollectionLayoutState.h new file mode 100644 index 0000000000..031e7f4f7e --- /dev/null +++ b/Source/Details/ASCollectionLayoutState.h @@ -0,0 +1,55 @@ +// +// ASCollectionLayoutState.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 9/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +@class ASElementMap, ASCollectionElement, ASLayout; + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface ASCollectionLayoutState : NSObject + +/// The elements used to calculate this object +@property (nonatomic, strong, readonly) ASElementMap *elements; + +@property (nonatomic, assign, readonly) CGSize contentSize; + +/// Element to layout attributes map. Should use weak pointers for elements. +@property (nonatomic, strong, readonly) NSMapTable *elementToLayoutArrtibutesMap; + +- (instancetype)init __unavailable; + +/** + * Designated initializer. + * + * @param elements The elements used to calculate this object + * + * @param contentSize The content size of the collection's layout + * + * @param elementToLayoutArrtibutesMap Map between elements to their layout attributes. The map may contain all elements, or a subset of them and will be updated later. + * Also, it should have NSMapTableObjectPointerPersonality and NSMapTableWeakMemory as key options. + */ +- (instancetype)initWithElements:(ASElementMap *)elements contentSize:(CGSize)contentSize elementToLayoutArrtibutesMap:(NSMapTable *)attrsMap NS_DESIGNATED_INITIALIZER; + +/** + * Convenience initializer. + * + * @param elements The elements used to calculate this object + * + * @param layout The layout describes size and position of all elements, or a subset of them and will be updated later. + * + * @discussion The sublayouts that describe position of elements must be direct children of the root layout object parameter. + */ +- (instancetype)initWithElements:(ASElementMap *)elements layout:(ASLayout *)layout; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionLayoutState.m b/Source/Details/ASCollectionLayoutState.m new file mode 100644 index 0000000000..b65a471799 --- /dev/null +++ b/Source/Details/ASCollectionLayoutState.m @@ -0,0 +1,52 @@ +// +// ASCollectionLayoutState.m +// AsyncDisplayKit +// +// Created by Huy Nguyen on 9/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import + +@implementation ASCollectionLayoutState + +- (instancetype)initWithElements:(ASElementMap *)elements layout:(ASLayout *)layout +{ + NSMapTable *attrsMap = [NSMapTable mapTableWithKeyOptions:(NSMapTableObjectPointerPersonality | NSMapTableWeakMemory) valueOptions:NSMapTableStrongMemory]; + for (ASLayout *sublayout in layout.sublayouts) { + ASCollectionElement *element = ((ASCellNode *)sublayout.layoutElement).collectionElement; + NSIndexPath *indexPath = [elements indexPathForElement:element]; + NSString *supplementaryElementKind = element.supplementaryElementKind; + + UICollectionViewLayoutAttributes *attrs; + if (supplementaryElementKind == nil) { + attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; + } else { + attrs = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:supplementaryElementKind withIndexPath:indexPath]; + } + + attrs.frame = sublayout.frame; + [attrsMap setObject:attrs forKey:element]; + } + + return [self initWithElements:elements contentSize:layout.size elementToLayoutArrtibutesMap:attrsMap]; +} + +- (instancetype)initWithElements:(ASElementMap *)elements contentSize:(CGSize)contentSize elementToLayoutArrtibutesMap:(NSMapTable *)attrsMap +{ + self = [super init]; + if (self) { + _elements = elements; + _contentSize = contentSize; + _elementToLayoutArrtibutesMap = attrsMap; + } + return self; +} + +@end diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h index 47f7237320..82b4664cee 100644 --- a/Source/Details/ASDataController.h +++ b/Source/Details/ASDataController.h @@ -27,9 +27,10 @@ NS_ASSUME_NONNULL_BEGIN #endif @class ASCellNode; +@class ASCollectionElement; @class ASDataController; @class ASElementMap; -@class ASCollectionElement; +@class ASLayout; @class _ASHierarchyChangeSet; @protocol ASTraitEnvironment; @protocol ASSectionContext; @@ -51,11 +52,6 @@ extern NSString * const ASCollectionInvalidUpdateException; */ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath; -/** - The constrained size range for layout. - */ -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; - /** Fetch the number of rows in specific section. */ @@ -73,12 +69,20 @@ extern NSString * const ASCollectionInvalidUpdateException; @optional +/** + The constrained size range for layout. Called only if collection layout delegate is not provided. + */ +- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; + - (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections; - (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; +/** + The constrained size range for layout. Called only if no data controller layout delegate is provided. + */ - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - (nullable id)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section; @@ -113,6 +117,33 @@ extern NSString * const ASCollectionInvalidUpdateException; @end +@protocol ASDataControllerLayoutDelegate + +/** + * @abstract Returns a layout context needed for a coming layout pass with the given elements. + * The context should contain the elements and any additional information needed. + * + * @discussion This method will be called on main thread. + */ +- (id)layoutContextWithElements:(ASElementMap *)elements; + +/** + * @abstract Prepares in advance a new layout with the given context. + * + * @param context A context that was previously returned by `-layoutContextWithElements:`. + * + * @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements. + * As a result, this method should rely solely on the given context and should not reach out to its collection/table view for information regarding items. + * + * @discussion This method will be called on background theads. It must be thread-safe and should not change any internal state of the conforming object. + * It's recommended to put the resulting layouts of this method into a thread-safe cache that can be looked up later on. + * + * @discussion This method must block its calling thread. It can dispatch to other theads to reduce blocking time. + */ +- (void)prepareLayoutWithContext:(id)context; + +@end + /** * Controller to layout data in background, and managed data updating. * @@ -154,6 +185,11 @@ extern NSString * const ASCollectionInvalidUpdateException; */ @property (nonatomic, weak) id environmentDelegate; +/** + * Delegate for preparing layouts. Main thead only. + */ +@property (nonatomic, weak) id layoutDelegate; + #ifdef __cplusplus /** * Returns the most recently gathered item counts from the data source. If the counts @@ -193,7 +229,7 @@ extern NSString * const ASCollectionInvalidUpdateException; - (void)relayoutAllNodes; /** - * Re-measures given noades in the backing store. + * Re-measures given nodes in the backing store. * * @discussion Used to respond to setNeedsLayout calls in ASCellNode */ @@ -201,6 +237,16 @@ extern NSString * const ASCollectionInvalidUpdateException; - (void)waitUntilAllUpdatesAreCommitted; +/** + * Notifies the data controller object that its environment has changed. The object will request its environment delegate for new information + * and propagate the information to all visible elements, including ones that are being prepared in background. + * + * @discussion If called before the initial @c reloadData, this method will do nothing and the trait collection of the initial load will be requested from the environment delegate. + * + * @discussion This method can be called on any threads. + */ +- (void)environmentDidChange; + @end NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index 1cf2e65a3d..8eb0be4ca0 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -13,15 +13,17 @@ #import #import #import -#import -#import -#import -#import -#import #import +#import #import #import +#import +#import #import +#import +#import +#import + #import #import #import @@ -52,6 +54,7 @@ + (void)_expectToInsertNodes:(NSUInteger)count; #endif @interface ASDataController () { + id _layoutDelegate; NSInteger _nextSectionID; @@ -69,6 +72,7 @@ @interface ASDataController () { unsigned int supplementaryNodeKindsInSections:1; unsigned int supplementaryNodesOfKindInSection:1; unsigned int supplementaryNodeBlockOfKindAtIndexPath:1; + unsigned int constrainedSizeForNodeAtIndexPath:1; unsigned int constrainedSizeForSupplementaryNodeOfKindAtIndexPath:1; unsigned int contextForSection:1; } _dataSourceFlags; @@ -91,6 +95,7 @@ - (instancetype)initWithDataSource:(id)dataSource eventL _dataSourceFlags.supplementaryNodeKindsInSections = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeKindsInSections:)]; _dataSourceFlags.supplementaryNodesOfKindInSection = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodesOfKind:inSection:)]; _dataSourceFlags.supplementaryNodeBlockOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; + _dataSourceFlags.constrainedSizeForNodeAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForNodeAtIndexPath:)]; _dataSourceFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:)]; _dataSourceFlags.contextForSection = [_dataSource respondsToSelector:@selector(dataController:contextForSection:)]; @@ -132,9 +137,23 @@ + (NSUInteger)parallelProcessorCount return parallelProcessorCount; } +- (id)layoutDelegate +{ + ASDisplayNodeAssertMainThread(); + return _layoutDelegate; +} + +- (void)setLayoutDelegate:(id)layoutDelegate +{ + ASDisplayNodeAssertMainThread(); + if (layoutDelegate != _layoutDelegate) { + _layoutDelegate = layoutDelegate; + } +} + #pragma mark - Cell Layout -- (void)batchLayoutNodesFromContexts:(NSArray *)elements batchSize:(NSInteger)batchSize batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler +- (void)batchAllocateNodesFromElements:(NSArray *)elements andLayout:(BOOL)shouldLayout batchSize:(NSInteger)batchSize batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler { ASSERT_ON_EDITING_QUEUE; #if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK @@ -156,9 +175,9 @@ - (void)batchLayoutNodesFromContexts:(NSArray *)elements // Processing in batches for (NSUInteger i = 0; i < count; i += batchSize) { NSRange batchedRange = NSMakeRange(i, MIN(count - i, batchSize)); - NSArray *batchedContexts = [elements subarrayWithRange:batchedRange]; - NSArray *nodes = [self _layoutNodesFromContexts:batchedContexts]; - batchCompletionHandler(batchedContexts, nodes); + NSArray *batchedElements = [elements subarrayWithRange:batchedRange]; + NSArray *nodes = [self _allocateNodesFromElements:batchedElements andLayout:shouldLayout]; + batchCompletionHandler(batchedElements, nodes); } ASProfilingSignpostEnd(2, _dataSource); @@ -176,7 +195,8 @@ - (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrai node.frame = frame; } -- (NSArray *)_layoutNodesFromContexts:(NSArray *)elements +// TODO Is returned array still needed? Can it be removed? +- (NSArray *)_allocateNodesFromElements:(NSArray *)elements andLayout:(BOOL)shouldLayout { ASSERT_ON_EDITING_QUEUE; @@ -198,16 +218,19 @@ - (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrai ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. } - - // Layout the node if the size range is valid. - ASSizeRange sizeRange = context.constrainedSize; - if (ASSizeRangeHasSignificantArea(sizeRange)) { - [self _layoutNode:node withConstrainedSize:sizeRange]; - } + + if (shouldLayout) { + // Layout the node if the size range is valid. + ASSizeRange sizeRange = context.constrainedSize; + if (ASSizeRangeHasSignificantArea(sizeRange)) { + [self _layoutNode:node withConstrainedSize:sizeRange]; + } #if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - [ASDataController _didLayoutNode]; + [ASDataController _didLayoutNode]; #endif + } + allocatedNodeBuffer[i] = node; }); @@ -269,6 +292,7 @@ - (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrai * @param owningNode The node that owns the new elements. * @param traitCollection The trait collection needed to initialize elements * @param indexPathsAreNew YES if index paths are "after the update," NO otherwise. + * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source */ - (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map forSectionsContainingIndexPaths:(NSArray *)indexPaths @@ -276,6 +300,7 @@ - (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection indexPathsAreNew:(BOOL)indexPathsAreNew + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); @@ -298,7 +323,7 @@ - (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map } for (NSString *kind in [self supplementaryKindsInSections:newSections]) { - [self _insertElementsIntoMap:map kind:kind forSections:newSections owningNode:owningNode traitCollection:traitCollection]; + [self _insertElementsIntoMap:map kind:kind forSections:newSections owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } } @@ -309,12 +334,14 @@ - (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map * @param sections The sections that should be populated by new elements * @param owningNode The node that owns the new elements. * @param traitCollection The trait collection needed to initialize elements + * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source */ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map kind:(NSString *)kind forSections:(NSIndexSet *)sections owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); @@ -323,7 +350,7 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map } NSArray *indexPaths = [self _allIndexPathsForItemsOfKind:kind inSections:sections]; - [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPaths owningNode:owningNode traitCollection:traitCollection]; + [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPaths owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } /** @@ -334,12 +361,14 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map * @param indexPaths The index paths at which new elements should be populated * @param owningNode The node that owns the new elements. * @param traitCollection The trait collection needed to initialize elements + * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source */ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map kind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); @@ -362,7 +391,11 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map nodeBlock = [_dataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; } - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + ASSizeRange constrainedSize; + if (shouldFetchSizeRanges) { + constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + } + ASCollectionElement *element = [[ASCollectionElement alloc] initWithNodeBlock:nodeBlock supplementaryElementKind:isRowKind ? nil : kind constrainedSize:constrainedSize @@ -404,18 +437,34 @@ - (void)invalidateDataSourceItemCounts return @[]; } +- (ASSizeRange)constrainedSizeForElement:(ASCollectionElement *)element inElementMap:(ASElementMap *)map +{ + ASDisplayNodeAssertMainThread(); + NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind; + NSIndexPath *indexPath = [map indexPathForElement:element]; + return [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; +} + + - (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); + + id dataSource = _dataSource; + if (dataSource == nil) { + return ASSizeRangeZero; + } + if ([kind isEqualToString:ASDataControllerRowNodeKind]) { - return [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; + ASDisplayNodeAssert(_dataSourceFlags.constrainedSizeForNodeAtIndexPath, @"-dataController:constrainedSizeForNodeAtIndexPath: must also be implemented"); + return [dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; } if (_dataSourceFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath){ - return [_dataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; + return [dataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; } - ASDisplayNodeAssert(NO, @"Unknown constrained size for node of kind %@ by data source %@", kind, _dataSource); + ASDisplayNodeAssert(NO, @"Unknown constrained size for node of kind %@ by data source %@", kind, dataSource); return ASSizeRangeZero; } @@ -423,13 +472,9 @@ - (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSInde - (void)waitUntilAllUpdatesAreCommitted { - ASDisplayNodeAssertMainThread(); - - 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:^{ }]; + [self _scheduleBlockOnMainSerialQueue:^{ }]; } - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet @@ -442,12 +487,9 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - /** - * If the initial reloadData has not been called, just bail because we don't have - * our old data source counts. - * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable - * For the issue that UICollectionView has that we're choosing to workaround. - */ + // If the initial reloadData has not been called, just bail because we don't have our old data source counts. + // See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable + // for the issue that UICollectionView has that we're choosing to workaround. if (!_initialReloadDataHasBeenCalled) { [changeSet executeCompletionHandlerWithFinished:YES]; return; @@ -455,6 +497,7 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet [self invalidateDataSourceItemCounts]; + // Log events ASDataControllerLogEvent(self, @"triggeredUpdate: %@", changeSet); #if ASEVENTLOG_ENABLE NSString *changeSetDescription = ASObjectDescriptionMakeTiny(changeSet); @@ -476,45 +519,68 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet @throw e; } } + + // Since we waited for _editingTransactionGroup at the beginning of this method, at this point we can guarantee that _pendingMap equals to _visibleMap. + // So if the change set is empty, we don't need to modify data and can safely schedule to notify the delegate. + if (changeSet.isEmpty) { + [_mainSerialQueue performBlockOnMainThread:^{ + [_delegate dataController:self willUpdateWithChangeSet:changeSet]; + [_delegate dataController:self didUpdateWithChangeSet:changeSet]; + }]; + return; + } // Mutable copy of current data. ASMutableElementMap *mutableMap = [_pendingMap mutableCopy]; - // Step 1: update the mutable copies to match the data source's state + BOOL canDelegateLayout = (_layoutDelegate != nil); + + // Step 1: Update the mutable copies to match the data source's state [self _updateSectionContextsInMap:mutableMap changeSet:changeSet]; - //TODO If _elements is the same, use a fast path __weak id environment = [self.environmentDelegate dataControllerEnvironment]; __weak ASDisplayNode *owningNode = (ASDisplayNode *)environment; // This is gross! ASPrimitiveTraitCollection existingTraitCollection = [environment primitiveTraitCollection]; - [self _updateElementsInMap:mutableMap changeSet:changeSet owningNode:owningNode traitCollection:existingTraitCollection]; + [self _updateElementsInMap:mutableMap changeSet:changeSet owningNode:owningNode traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegateLayout)]; // Step 2: Clone the new data ASElementMap *newMap = [mutableMap copy]; - _pendingMap = newMap; + // Step 3: Ask layout delegate for contexts + id layoutContext = nil; + if (canDelegateLayout) { + layoutContext = [_layoutDelegate layoutContextWithElements:newMap]; + } + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - // Step 3: Layout **all** new elements without batching in background. - NSArray *unmeasuredElements = [ASDataController unmeasuredElementsFromMap:newMap]; - // TODO layout in batches, esp reloads - [self batchLayoutNodesFromContexts:unmeasuredElements batchSize:unmeasuredElements.count batchCompletion:^(id, id) { + // Step 4: Allocate and layout elements if can't delegate + NSArray *elementsToProcess; + if (canDelegateLayout) { + // Allocate all nodes before handling them to the layout delegate. + // In the future, we may want to let the delegate drive allocation as well. + elementsToProcess = ASArrayByFlatMapping(newMap, + ASCollectionElement *element, + (element.nodeIfAllocated == nil ? element : nil)); + } else { + elementsToProcess = ASArrayByFlatMapping(newMap, + ASCollectionElement *element, + (element.nodeIfAllocated.calculatedLayout == nil ? element : nil)); + } + + [self batchAllocateNodesFromElements:elementsToProcess andLayout:(! canDelegateLayout) batchSize:elementsToProcess.count batchCompletion:^(NSArray *elements, NSArray *nodes) { ASSERT_ON_EDITING_QUEUE; + + if (canDelegateLayout) { + [_layoutDelegate prepareLayoutWithContext:layoutContext]; + } + [_mainSerialQueue performBlockOnMainThread:^{ [_delegate dataController:self willUpdateWithChangeSet:changeSet]; - // Step 4: Deploy the new data as "completed" and inform delegate + // Step 5: Deploy the new data as "completed" and inform delegate _visibleMap = newMap; - - [_delegate dataController:self didUpdateWithChangeSet:changeSet]; - // Step 5: If the environment changed mid-update, notify all visible elements - __weak id newEnvironment = [self.environmentDelegate dataControllerEnvironment]; - ASPrimitiveTraitCollection newTraitCollection = [newEnvironment primitiveTraitCollection]; - if (! ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(existingTraitCollection, newTraitCollection)) { - for (ASCollectionElement *element in _visibleMap) { - element.traitCollection = newTraitCollection; - } - } + [_delegate dataController:self didUpdateWithChangeSet:changeSet]; }]; }]; }); @@ -531,11 +597,7 @@ - (void)_updateSectionContextsInMap:(ASMutableElementMap *)map changeSet:(_ASHie return; } - // TODO if the change set includes solely section reloads that together are equivalent to reloadData (i.e reload the only section), - // do a reloadData here as an optimization. - if (changeSet.includesReloadData) { - [map removeAllSectionContexts]; NSUInteger sectionCount = [self itemCountsFromDataSource].size(); @@ -577,19 +639,17 @@ - (void)_updateElementsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); - - // TODO if the change set includes solely section reloads that together are equivalent to reloadData (i.e reload the only section), - // do a reloadData here as an optimization. - + if (changeSet.includesReloadData) { [map removeAllElements]; NSUInteger sectionCount = [self itemCountsFromDataSource].size(); if (sectionCount > 0) { NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - [self _insertElementsIntoMap:map sections:sectionIndexes owningNode:owningNode traitCollection:traitCollection]; + [self _insertElementsIntoMap:map sections:sectionIndexes owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } // Return immediately because reloadData can't be used in conjuntion with other updates. return; @@ -602,7 +662,8 @@ - (void)_updateElementsInMap:(ASMutableElementMap *)map changeSet:changeSet owningNode:owningNode traitCollection:traitCollection - indexPathsAreNew:NO]; + indexPathsAreNew:NO + shouldFetchSizeRanges:shouldFetchSizeRanges]; } for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { @@ -612,17 +673,18 @@ - (void)_updateElementsInMap:(ASMutableElementMap *)map } for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map sections:change.indexSet owningNode:owningNode traitCollection:traitCollection]; + [self _insertElementsIntoMap:map sections:change.indexSet owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind atIndexPaths:change.indexPaths owningNode:owningNode traitCollection:traitCollection]; + [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind atIndexPaths:change.indexPaths owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; // Aggressively reload supplementary nodes (#1773 & #1629) [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths changeSet:changeSet owningNode:owningNode traitCollection:traitCollection - indexPathsAreNew:YES]; + indexPathsAreNew:YES + shouldFetchSizeRanges:shouldFetchSizeRanges]; } } @@ -630,6 +692,7 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map sections:(NSIndexSet *)sectionIndexes owningNode:(ASDisplayNode *)owningNode traitCollection:(ASPrimitiveTraitCollection)traitCollection + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); @@ -639,12 +702,12 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map // Items [map insertEmptySectionsOfItemsAtIndexes:sectionIndexes]; - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind forSections:sectionIndexes owningNode:owningNode traitCollection:traitCollection]; + [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind forSections:sectionIndexes owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; // Supplementaries for (NSString *kind in [self supplementaryKindsInSections:sectionIndexes]) { // Step 2: Populate new elements for all sections - [self _insertElementsIntoMap:map kind:kind forSections:sectionIndexes owningNode:owningNode traitCollection:traitCollection]; + [self _insertElementsIntoMap:map kind:kind forSections:sectionIndexes owningNode:owningNode traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges]; } } @@ -660,9 +723,7 @@ - (void)relayoutNodes:(id)nodes nodesSizeChanged:(NSMutableAr } for (ASCellNode *node in nodes) { - NSString *kind = node.collectionElement.supplementaryElementKind ?: ASDataControllerRowNodeKind; - NSIndexPath *indexPath = [_pendingMap indexPathForElement:node.collectionElement]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForElement:node.collectionElement inElementMap:_pendingMap]; [self _layoutNode:node withConstrainedSize:constrainedSize]; BOOL matchesSize = [_dataSource dataController:self presentedSizeForElement:node.collectionElement matchesSize:node.frame.size]; if (! matchesSize) { @@ -678,25 +739,19 @@ - (void)relayoutAllNodes return; } + // Can't relayout right away because _visibleMap 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 _visibleMap LOG(@"Edit Command - relayoutRows"); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Can't relayout right away because _completedElements 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 _completedElements - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - [self _relayoutAllNodes]; - }]; - }); + [self _scheduleBlockOnMainSerialQueue:^{ + [self _relayoutAllNodes]; + }]; } - (void)_relayoutAllNodes { ASDisplayNodeAssertMainThread(); for (ASCollectionElement *element in _visibleMap) { - NSIndexPath *indexPath = [_visibleMap indexPathForElement:element]; - NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForElement:element inElementMap:_visibleMap]; if (ASSizeRangeHasSignificantArea(constrainedSize)) { element.constrainedSize = constrainedSize; @@ -710,15 +765,33 @@ - (void)_relayoutAllNodes } } -+ (NSArray *)unmeasuredElementsFromMap:(ASElementMap *)map +# pragma mark - ASPrimitiveTraitCollection + +- (void)environmentDidChange { - NSMutableArray *unloadedContexts = [NSMutableArray array]; - for (ASCollectionElement *element in map) { - if (element.nodeIfAllocated.calculatedLayout == nil) { - [unloadedContexts addObject:element]; + ASPerformBlockOnMainThread(^{ + if (!_initialReloadDataHasBeenCalled) { + return; } - } - return unloadedContexts; + + // Can't update the trait collection right away because _visibleMap may not be up-to-date, + // i.e there might be some elements that were allocated using the old trait collection but haven't been added to _visibleMap + [self _scheduleBlockOnMainSerialQueue:^{ + ASPrimitiveTraitCollection newTraitCollection = [[_environmentDelegate dataControllerEnvironment] primitiveTraitCollection]; + for (ASCollectionElement *element in _visibleMap) { + element.traitCollection = newTraitCollection; + } + }]; + }); +} + +# pragma mark - Helper methods + +- (void)_scheduleBlockOnMainSerialQueue:(dispatch_block_t)block +{ + ASDisplayNodeAssertMainThread(); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + [_mainSerialQueue performBlockOnMainThread:block]; } @end diff --git a/Source/Details/ASDelegateProxy.m b/Source/Details/ASDelegateProxy.m index acd0c5349a..94f113a72d 100644 --- a/Source/Details/ASDelegateProxy.m +++ b/Source/Details/ASDelegateProxy.m @@ -54,7 +54,8 @@ - (BOOL)interceptsSelector:(SEL)selector selector == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) || // used for batch fetching API - selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) + selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) || + selector == @selector(scrollViewDidEndDecelerating:) ); } @@ -96,6 +97,7 @@ - (BOOL)interceptsSelector:(SEL)selector // used for batch fetching API selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) || + selector == @selector(scrollViewDidEndDecelerating:) || // used for ASCellNode visibility selector == @selector(scrollViewDidScroll:) || diff --git a/Source/Private/ASElementMap.h b/Source/Details/ASElementMap.h similarity index 96% rename from Source/Private/ASElementMap.h rename to Source/Details/ASElementMap.h index 9ef69c7245..17e6fb4f51 100644 --- a/Source/Private/ASElementMap.h +++ b/Source/Details/ASElementMap.h @@ -49,6 +49,11 @@ AS_SUBCLASSING_RESTRICTED */ @property (copy, readonly) NSArray *itemIndexPaths; +/** + * All the item elements in this map, in ascending order. O(N) + */ +@property (copy, readonly) NSArray *itemElements; + /** * Returns the index path that corresponds to the same element in @c map at the given @c indexPath. O(1) */ diff --git a/Source/Private/ASElementMap.m b/Source/Details/ASElementMap.m similarity index 98% rename from Source/Private/ASElementMap.m rename to Source/Details/ASElementMap.m index 94d9658ce5..ee7842ec47 100644 --- a/Source/Private/ASElementMap.m +++ b/Source/Details/ASElementMap.m @@ -69,6 +69,11 @@ - (instancetype)initWithSections:(NSArray *)sections items:(ASColle return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems); } +- (NSArray *)itemElements +{ + return ASElementsInTwoDimensionalArray(_sectionsOfItems); +} + - (NSInteger)numberOfSections { return _sectionsOfItems.count; diff --git a/Source/Details/ASEventLog.h b/Source/Details/ASEventLog.h index 89cd279db5..38d122dd06 100644 --- a/Source/Details/ASEventLog.h +++ b/Source/Details/ASEventLog.h @@ -18,7 +18,7 @@ #endif #ifndef ASEVENTLOG_ENABLE -#define ASEVENTLOG_ENABLE DEBUG +#define ASEVENTLOG_ENABLE 0 #endif NS_ASSUME_NONNULL_BEGIN diff --git a/Source/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h index dfd1f82c42..435cf0a221 100644 --- a/Source/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -109,6 +109,17 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { @optional +/** + @abstract Cancels an image download, however indicating resume data should be stored in case of redownload. + @param downloadIdentifier The opaque download identifier object returned from + `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. + @discussion This method has no effect if `downloadIdentifier` is nil. If implemented, this method + may be called instead of `cancelImageDownloadForIdentifier:` in cases where ASDK believes there's a chance + the image download will be resumed (currently when an image exits preload range). You can use this to store + any data that has already been downloaded for use in resuming the download later. + */ +- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier; + /** @abstract Return an object that conforms to ASAnimatedImageProtocol @param animatedImageData Data that represents an animated image. diff --git a/Source/Details/ASObjectDescriptionHelpers.m b/Source/Details/ASObjectDescriptionHelpers.m index 327b96b29e..7661e1f0c9 100644 --- a/Source/Details/ASObjectDescriptionHelpers.m +++ b/Source/Details/ASObjectDescriptionHelpers.m @@ -6,14 +6,9 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import #import -#if AS_TARGET_OS_IOS #import -#else -#import -#endif #import diff --git a/Source/Details/ASPINRemoteImageDownloader.h b/Source/Details/ASPINRemoteImageDownloader.h index 5a7799d403..268a5ff4e9 100644 --- a/Source/Details/ASPINRemoteImageDownloader.h +++ b/Source/Details/ASPINRemoteImageDownloader.h @@ -10,7 +10,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#if PIN_REMOTE_IMAGE +#import + +#if AS_PIN_REMOTE_IMAGE #import diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index b35875d9a0..b861c3055b 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -10,7 +10,9 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#if PIN_REMOTE_IMAGE +#import + +#if AS_PIN_REMOTE_IMAGE #import #import @@ -245,7 +247,13 @@ - (nullable id)downloadImageWithURL:(NSURL *)URL - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier { ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier]; + [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:NO]; +} + +- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier +{ + ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); + [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:YES]; } - (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier @@ -267,18 +275,18 @@ - (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:( { ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityMedium; + PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityDefault; switch (priority) { case ASImageDownloaderPriorityPreload: - pi_priority = PINRemoteImageManagerPriorityMedium; + pi_priority = PINRemoteImageManagerPriorityLow; break; case ASImageDownloaderPriorityImminent: - pi_priority = PINRemoteImageManagerPriorityHigh; + pi_priority = PINRemoteImageManagerPriorityDefault; break; case ASImageDownloaderPriorityVisible: - pi_priority = PINRemoteImageManagerPriorityVeryHigh; + pi_priority = PINRemoteImageManagerPriorityHigh; break; } [[self sharedPINRemoteImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier]; diff --git a/Source/Details/ASPhotosFrameworkImageRequest.m b/Source/Details/ASPhotosFrameworkImageRequest.m index ad81dbf3c3..d51c49a81f 100644 --- a/Source/Details/ASPhotosFrameworkImageRequest.m +++ b/Source/Details/ASPhotosFrameworkImageRequest.m @@ -12,7 +12,6 @@ #import #import -#import NSString *const ASPhotosURLScheme = @"ph"; diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index 024c99384b..e5ae2345f4 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -12,7 +12,7 @@ #import #import -#import +#import #import #import #import // Required for interfaceState and hierarchyState setter methods. @@ -410,9 +410,16 @@ - (void)_updateVisibleNodeIndexPaths #pragma mark - Notification observers +/** + * If we're in a restricted range mode, but we're going to change to a full range mode soon, + * go ahead and schedule the transition as soon as all the currently-scheduled rendering is done #1163. + */ - (void)registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState { - if (!_didRegisterForNodeDisplayNotifications) { + // Do not schedule to listen if we're already in full range mode. + // This avoids updating the range controller during a collection teardown when it is removed + // from the hierarchy and its data source is cleared, causing UIKit to call -reloadData. + if (!_didRegisterForNodeDisplayNotifications && _currentRangeMode != ASLayoutRangeModeFull) { ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState currentRangeMode:_currentRangeMode]; if (_currentRangeMode != nextRangeMode) { @@ -444,7 +451,13 @@ - (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)nod ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(node, @"Cannot move a nil node to a view"); ASDisplayNodeAssert(contentView, @"Cannot move a node to a non-existent view"); - + + if (node.shouldUseUIKitCell) { + // When using UIKit cells, the ASCellNode is just a placeholder object with a preferredSize. + // In this case, we should not disrupt the subviews of the contentView. + return; + } + if (node.view.superview == contentView) { // this content view is already correctly configured return; diff --git a/Source/Details/ASThread.h b/Source/Details/ASThread.h index 29071b2995..9111242f09 100644 --- a/Source/Details/ASThread.h +++ b/Source/Details/ASThread.h @@ -64,6 +64,18 @@ static inline BOOL ASDisplayNodeThreadIsMain() _Pragma("clang diagnostic pop"); \ } while (0) +/** + * Assert if the current thread owns a mutex. + * This assertion is useful when you want to indicate and enforce the locking policy/expectation of methods. + * To determine when and which methods acquired a (recursive) mutex (to debug deadlocks, for example), + * put breakpoints at some of these assertions. When the breakpoints hit, walk through stack trace frames + * and check ownership count of the mutex. + */ +#if CHECK_LOCKING_SAFETY +#define ASDisplayNodeAssertLockUnownedByCurrentThread(lock) ASDisplayNodeAssertFalse(lock.ownedByCurrentThread()) +#else +#define ASDisplayNodeAssertLockUnownedByCurrentThread(lock) +#endif namespace ASDN { diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index 4a5fde11d2..1e2a83f91c 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -8,9 +8,6 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import - -#if AS_TARGET_OS_IOS #import #import @@ -117,18 +114,12 @@ ASDISPLAYNODE_EXTERN_C_END \ /* 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; }\ + /* 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; }\ \ ASPrimitiveTraitCollection currentTraits = self.primitiveTraitCollection;\ if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(currentTraits, oldTraits) == NO) {\ - /* Must dispatch to main for self.view && [self.view.dataController visibleMap]*/\ - ASPerformBlockOnMainThread(^{\ - ASElementMap *map = self.view.dataController.visibleMap; \ - for (ASCollectionElement *element in map) { \ - ASTraitCollectionPropagateDown(element.nodeIfAllocated, currentTraits); \ - } \ - });\ + [self.dataController environmentDidChange];\ }\ }\ @@ -164,9 +155,3 @@ AS_SUBCLASSING_RESTRICTED @end NS_ASSUME_NONNULL_END - -#else - -// Non iOS - -#endif diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index 1c716984df..2435cb75cd 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -10,10 +10,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import - -#if AS_TARGET_OS_IOS - #import #import @@ -196,9 +194,3 @@ - (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection } @end - -#else - -// Non iOS - -#endif diff --git a/Source/Details/NSIndexSet+ASHelpers.m b/Source/Details/NSIndexSet+ASHelpers.m index d0e3f34b83..756d038a8c 100644 --- a/Source/Details/NSIndexSet+ASHelpers.m +++ b/Source/Details/NSIndexSet+ASHelpers.m @@ -6,13 +6,9 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import -#if AS_TARGET_OS_IOS - #import -#else - #import -#endif +// UIKit indexPath helpers +#import #import diff --git a/Source/Details/UIView+ASConvenience.h b/Source/Details/UIView+ASConvenience.h index 24a9021cfd..9c918689ec 100644 --- a/Source/Details/UIView+ASConvenience.h +++ b/Source/Details/UIView+ASConvenience.h @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)setNeedsDisplay; - (void)setNeedsLayout; +- (void)layoutIfNeeded; @end diff --git a/Source/Details/_ASCollectionReusableView.h b/Source/Details/_ASCollectionReusableView.h new file mode 100644 index 0000000000..9f5bbd6f86 --- /dev/null +++ b/Source/Details/_ASCollectionReusableView.h @@ -0,0 +1,22 @@ +// +// _ASCollectionReusableView.h +// AsyncDisplayKit +// +// Created by Phil Larson on 4/10/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +@class ASCellNode; + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface _ASCollectionReusableView : UICollectionReusableView +@property (nonatomic, weak) ASCellNode *node; +@property (nonatomic, strong, nullable) UICollectionViewLayoutAttributes *layoutAttributes; +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/_ASCollectionReusableView.m b/Source/Details/_ASCollectionReusableView.m new file mode 100644 index 0000000000..50651eb797 --- /dev/null +++ b/Source/Details/_ASCollectionReusableView.m @@ -0,0 +1,82 @@ +// +// _ASCollectionReusableView.m +// AsyncDisplayKit +// +// Created by Phil Larson on 4/10/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "_ASCollectionReusableView.h" +#import "ASCellNode+Internal.h" +#import + +@implementation _ASCollectionReusableView + +- (void)setNode:(ASCellNode *)node +{ + ASDisplayNodeAssertMainThread(); + node.layoutAttributes = _layoutAttributes; + _node = node; +} + +- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes +{ + _layoutAttributes = layoutAttributes; + _node.layoutAttributes = layoutAttributes; +} + +- (void)prepareForReuse +{ + self.layoutAttributes = nil; + + // Need to clear node pointer before UIKit calls setSelected:NO / setHighlighted:NO on its cells + self.node = nil; + [super prepareForReuse]; +} + +/** + * In the initial case, this is called by UICollectionView during cell dequeueing, before + * we get a chance to assign a node to it, so we must be sure to set these layout attributes + * on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already + * have our node assigned e.g. during a layout update for existing cells, we also attempt + * to update it now. + */ +- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes +{ + self.layoutAttributes = layoutAttributes; +} + +/** + * Keep our node filling our content view. + */ +- (void)layoutSubviews +{ + [super layoutSubviews]; + self.node.frame = self.bounds; +} + +@end + +/** + * A category that makes _ASCollectionReusableView conform to IGListBindable. + * + * We don't need to do anything to bind the view model – the cell node + * serves the same purpose. + */ +#if __has_include() + +#import + +@interface _ASCollectionReusableView (IGListBindable) +@end + +@implementation _ASCollectionReusableView (IGListBindable) + +- (void)bindViewModel:(id)viewModel +{ + // nop +} + +@end + +#endif diff --git a/Source/Details/_ASCollectionViewCell.m b/Source/Details/_ASCollectionViewCell.m index a2f5bdda13..76709d0c31 100644 --- a/Source/Details/_ASCollectionViewCell.m +++ b/Source/Details/_ASCollectionViewCell.m @@ -58,6 +58,7 @@ - (void)prepareForReuse */ - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { + [super applyLayoutAttributes:layoutAttributes]; self.layoutAttributes = layoutAttributes; } diff --git a/Source/Details/_ASDisplayView.mm b/Source/Details/_ASDisplayView.mm index a61b1ce571..0fe3f9ff93 100644 --- a/Source/Details/_ASDisplayView.mm +++ b/Source/Details/_ASDisplayView.mm @@ -342,20 +342,20 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer - (void)tintColorDidChange { - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - [super tintColorDidChange]; - - [node tintColorDidChange]; + ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. + [super tintColorDidChange]; + + [node tintColorDidChange]; } - (BOOL)canBecomeFirstResponder { - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node canBecomeFirstResponder]; + ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. + return [node canBecomeFirstResponder]; } - (BOOL)canResignFirstResponder { - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node canResignFirstResponder]; + ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. + return [node canResignFirstResponder]; } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender diff --git a/Source/IGListAdapter+AsyncDisplayKit.h b/Source/IGListAdapter+AsyncDisplayKit.h index ae19e74008..9f2cf9ad76 100644 --- a/Source/IGListAdapter+AsyncDisplayKit.h +++ b/Source/IGListAdapter+AsyncDisplayKit.h @@ -6,7 +6,9 @@ // Copyright © 2017 Facebook. All rights reserved. // -#if IG_LIST_KIT +#import + +#if AS_IG_LIST_KIT #import @@ -31,4 +33,4 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END -#endif // IG_LIST_KIT +#endif // AS_IG_LIST_KIT diff --git a/Source/IGListAdapter+AsyncDisplayKit.m b/Source/IGListAdapter+AsyncDisplayKit.m index 6aa416bbd8..6210e69e33 100644 --- a/Source/IGListAdapter+AsyncDisplayKit.m +++ b/Source/IGListAdapter+AsyncDisplayKit.m @@ -6,7 +6,9 @@ // Copyright © 2017 Facebook. All rights reserved. // -#if IG_LIST_KIT +#import + +#if AS_IG_LIST_KIT #import "IGListAdapter+AsyncDisplayKit.h" #import "ASIGListAdapterBasedDataSource.h" @@ -36,12 +38,15 @@ - (void)setASDKCollectionNode:(ASCollectionNode *)collectionNode collectionNode.delegate = dataSource; __weak IGListAdapter *weakSelf = self; [collectionNode onDidLoad:^(__kindof ASCollectionNode * _Nonnull collectionNode) { - // We manually set the superclass of ASCollectionView to IGListCollectionView at runtime. - // Issue tracked at https://github.com/Instagram/IGListKit/issues/409 +#if IG_LIST_COLLECTION_VIEW + // We manually set the superclass of ASCollectionView to IGListCollectionView at runtime if needed. weakSelf.collectionView = (IGListCollectionView *)collectionNode.view; +#else + weakSelf.collectionView = collectionNode.view; +#endif }]; } @end -#endif // IG_LIST_KIT +#endif // AS_IG_LIST_KIT diff --git a/Source/Layout/ASDimension.mm b/Source/Layout/ASDimension.mm index 6db911d11b..86a5270d14 100644 --- a/Source/Layout/ASDimension.mm +++ b/Source/Layout/ASDimension.mm @@ -10,11 +10,7 @@ #import -#if AS_TARGET_OS_IOS #import -#else -#import -#endif #import @@ -108,13 +104,7 @@ ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRan NSString *NSStringFromASSizeRange(ASSizeRange sizeRange) { -#if AS_TARGET_OS_IOS return [NSString stringWithFormat:@"", NSStringFromCGSize(sizeRange.min), NSStringFromCGSize(sizeRange.max)]; -#else - return [NSString stringWithFormat:@"", - NSStringFromRect(NSRectFromCGRect(sizeRange.min)), - NSStringFromRect(NSRectFromCGRect(sizeRange.max))]; -#endif } diff --git a/Source/Layout/ASInsetLayoutSpec.h b/Source/Layout/ASInsetLayoutSpec.h index 5515c9679e..a4679a6f0f 100644 --- a/Source/Layout/ASInsetLayoutSpec.h +++ b/Source/Layout/ASInsetLayoutSpec.h @@ -8,7 +8,6 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/Layout/ASLayoutElement.h b/Source/Layout/ASLayoutElement.h index 3af69103f8..162156e4f1 100644 --- a/Source/Layout/ASLayoutElement.h +++ b/Source/Layout/ASLayoutElement.h @@ -8,21 +8,18 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import #import #import #import #import #import +#import @class ASLayout; @class ASLayoutSpec; @protocol ASLayoutElementStylability; -#if AS_TARGET_OS_IOS -#import "ASTraitCollection.h" @protocol ASTraitEnvironment; -#endif NS_ASSUME_NONNULL_BEGIN @@ -66,11 +63,7 @@ ASDISPLAYNODE_EXTERN_C_END * access to the options via convenience properties. If you are creating custom layout spec, then you can * extend the backing layout options class to accommodate any new layout options. */ -#if AS_TARGET_OS_IOS @protocol ASLayoutElement -#else -@protocol ASLayoutElement -#endif #pragma mark - Getter diff --git a/Source/Layout/ASLayoutElement.mm b/Source/Layout/ASLayoutElement.mm index de88730dd1..d127b6884a 100644 --- a/Source/Layout/ASLayoutElement.mm +++ b/Source/Layout/ASLayoutElement.mm @@ -11,6 +11,7 @@ // #import "ASDisplayNode+FrameworkPrivate.h" +#import #import #import #import diff --git a/Source/Layout/ASLayoutElementExtensibility.h b/Source/Layout/ASLayoutElementExtensibility.h index 9121530851..4bd6b39085 100644 --- a/Source/Layout/ASLayoutElementExtensibility.h +++ b/Source/Layout/ASLayoutElementExtensibility.h @@ -10,11 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#if AS_TARGET_OS_IOS #import -#else -#import -#endif #import diff --git a/Source/Layout/ASLayoutElementPrivate.h b/Source/Layout/ASLayoutElementPrivate.h index cd227d412d..a82f39a0c2 100644 --- a/Source/Layout/ASLayoutElementPrivate.h +++ b/Source/Layout/ASLayoutElementPrivate.h @@ -8,14 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import #import - -#if AS_TARGET_OS_IOS #import -#else -#import -#endif @protocol ASLayoutElement; @class ASLayoutElementStyle; diff --git a/Source/Layout/ASLayoutSpec+Subclasses.mm b/Source/Layout/ASLayoutSpec+Subclasses.mm index a743364ee5..cc1dbf8f8f 100644 --- a/Source/Layout/ASLayoutSpec+Subclasses.mm +++ b/Source/Layout/ASLayoutSpec+Subclasses.mm @@ -54,9 +54,7 @@ @implementation ASLayoutSpec (Subclassing) if (self.isFinalLayoutElement == NO) { id finalLayoutElement = [child finalLayoutElement]; if (finalLayoutElement != child) { -#if AS_TARGET_OS_IOS finalLayoutElement.primitiveTraitCollection = child.primitiveTraitCollection; -#endif return finalLayoutElement; } } diff --git a/Source/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm index 8f81f288e4..541adbcb8c 100644 --- a/Source/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -16,7 +16,6 @@ #import #import #import -#import #import #import @@ -49,9 +48,7 @@ - (instancetype)init } _isMutable = YES; -#if AS_TARGET_OS_IOS _primitiveTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); -#endif _childrenArray = [[NSMutableArray alloc] init]; return self; @@ -174,8 +171,6 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state object #pragma mark - ASTraitEnvironment -#if AS_TARGET_OS_IOS - - (ASPrimitiveTraitCollection)primitiveTraitCollection { return _primitiveTraitCollection; @@ -192,11 +187,7 @@ - (ASTraitCollection *)asyncTraitCollection return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection]; } -#endif - -#if AS_TARGET_OS_IOS ASPrimitiveTraitCollectionDeprecatedImplementation -#endif #pragma mark - ASLayoutElementStyleExtensibility diff --git a/Source/Layout/ASStackLayoutDefines.h b/Source/Layout/ASStackLayoutDefines.h index 79281d73dc..3b18d71967 100644 --- a/Source/Layout/ASStackLayoutDefines.h +++ b/Source/Layout/ASStackLayoutDefines.h @@ -86,13 +86,13 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) { ASStackLayoutAlignSelfStretch, }; -// TODO documentation +/** Whether children are stacked into a single or multiple lines. */ typedef NS_ENUM(NSUInteger, ASStackLayoutFlexWrap) { ASStackLayoutFlexWrapNoWrap, ASStackLayoutFlexWrapWrap, }; -// TODO documentation +/** Orientation of lines along cross axis if there are multiple lines. */ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignContent) { ASStackLayoutAlignContentStart, ASStackLayoutAlignContentCenter, diff --git a/Source/Layout/ASStackLayoutSpec.h b/Source/Layout/ASStackLayoutSpec.h index a87673b8f0..703099a5c4 100644 --- a/Source/Layout/ASStackLayoutSpec.h +++ b/Source/Layout/ASStackLayoutSpec.h @@ -63,7 +63,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) ASStackLayoutFlexWrap flexWrap; /** Orientation of lines along cross axis if there are multiple lines. Defaults to ASStackLayoutAlignContentStart */ @property (nonatomic, assign) ASStackLayoutAlignContent alignContent; - /** Whether this stack can dispatch to other threads, regardless of which thread it's running on */ @property (nonatomic, assign, getter=isConcurrent) BOOL concurrent; diff --git a/Source/Private/ASCollectionLayout.h b/Source/Private/ASCollectionLayout.h new file mode 100644 index 0000000000..76260a0f9f --- /dev/null +++ b/Source/Private/ASCollectionLayout.h @@ -0,0 +1,51 @@ +// +// ASCollectionLayout.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 28/2/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +@protocol ASCollectionLayoutDelegate; +@class ASElementMap, ASCollectionLayout, ASCollectionNode; + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED + +@interface ASCollectionLayout : UICollectionViewLayout + +/** + * The collection node object currently using this layout object. + * + * @discussion The collection node object sets the value of this property when a new layout object is assigned to it. + * + * @discussion To get the truth on the current state of the collection, call methods on the collection node or the data source rather than the collection view because: + * 1. The view might not yet be allocated. + * 2. The collection node and data source are thread-safe. + */ +@property (nonatomic, weak) ASCollectionNode *collectionNode; + +@property (nonatomic, strong, readonly) id layoutDelegate; + +/** + * Initializes with a layout delegate. + * + * @discussion For developers' convenience, the delegate is retained by this layout object, similar to UICollectionView retains its UICollectionViewLayout object. + * + * @discussion For simplicity, the delegate is read-only. If a new layout delegate is needed, construct a new layout object with that delegate and notify ASCollectionView about it. + * This ensures the underlying UICollectionView purges its cache and properly loads the new layout. + */ +- (instancetype)initWithLayoutDelegate:(id)layoutDelegate NS_DESIGNATED_INITIALIZER; + +- (instancetype)init __unavailable; + +- (instancetype)initWithCoder:(NSCoder *)aDecoder __unavailable; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASCollectionLayout.mm b/Source/Private/ASCollectionLayout.mm new file mode 100644 index 0000000000..2ce144568e --- /dev/null +++ b/Source/Private/ASCollectionLayout.mm @@ -0,0 +1,153 @@ +// +// ASCollectionLayout.mm +// AsyncDisplayKit +// +// Created by Huy Nguyen on 28/2/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +@interface ASCollectionLayout () { + ASDN::Mutex __instanceLock__; // Non-recursive mutex, ftw! + + // Main thread only. + ASCollectionLayoutState *_state; + + // The pending state calculated ahead of time, if any. + ASCollectionLayoutState *_pendingState; + // The context used to calculate _pendingState + ASCollectionLayoutContext *_layoutContextForPendingState; + + BOOL _layoutDelegateImplementsAdditionalInfoForLayoutWithElements; +} + +@end + +@implementation ASCollectionLayout + +- (instancetype)initWithLayoutDelegate:(id)layoutDelegate +{ + self = [super init]; + if (self) { + ASDisplayNodeAssertNotNil(layoutDelegate, @"Collection layout delegate cannot be nil"); + _layoutDelegate = layoutDelegate; + _layoutDelegateImplementsAdditionalInfoForLayoutWithElements = [layoutDelegate respondsToSelector:@selector(additionalInfoForLayoutWithElements:)]; + } + return self; +} + +#pragma mark - ASDataControllerLayoutDelegate + +- (id)layoutContextWithElements:(ASElementMap *)elements +{ + ASDisplayNodeAssertMainThread(); + id additionalInfo = nil; + if (_layoutDelegateImplementsAdditionalInfoForLayoutWithElements) { + additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements]; + } + return [[ASCollectionLayoutContext alloc] initWithViewportSize:[self viewportSize] elements:elements additionalInfo:additionalInfo]; +} + +- (void)prepareLayoutWithContext:(id)context +{ + ASCollectionLayoutState *state = [_layoutDelegate calculateLayoutWithContext:context]; + + ASDN::MutexLocker l(__instanceLock__); + _pendingState = state; + _layoutContextForPendingState = context; +} + +#pragma mark - UICollectionViewLayout overrides + +- (void)prepareLayout +{ + ASDisplayNodeAssertMainThread(); + [super prepareLayout]; + ASCollectionLayoutContext *context = [self layoutContextWithElements:_collectionNode.visibleElements]; + + ASCollectionLayoutState *state = nil; + { + ASDN::MutexLocker l(__instanceLock__); + if (_pendingState != nil && ASObjectIsEqual(_layoutContextForPendingState, context)) { + // Looks like we can use the pending state. Great! + state = _pendingState; + _pendingState = nil; + _layoutContextForPendingState = nil; + } + } + + if (state == nil) { + state = [_layoutDelegate calculateLayoutWithContext:context]; + } + + _state = state; +} + +- (void)invalidateLayout +{ + ASDisplayNodeAssertMainThread(); + [super invalidateLayout]; + _state = nil; +} + +- (CGSize)collectionViewContentSize +{ + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertNotNil(_state, @"Collection layout state should not be nil at this point"); + return _state.contentSize; +} + +- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect +{ + NSMutableArray *attributesInRect = [NSMutableArray array]; + NSMapTable *attrsMap = _state.elementToLayoutArrtibutesMap; + for (ASCollectionElement *element in attrsMap) { + UICollectionViewLayoutAttributes *attrs = [attrsMap objectForKey:element]; + if (CGRectIntersectsRect(rect, attrs.frame)) { + [attributesInRect addObject:attrs]; + } + } + return attributesInRect; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath +{ + ASCollectionLayoutState *state = _state; + ASCollectionElement *element = [state.elements elementForItemAtIndexPath:indexPath]; + return [state.elementToLayoutArrtibutesMap objectForKey:element]; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath +{ + ASCollectionLayoutState *state = _state; + ASCollectionElement *element = [state.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath]; + return [state.elementToLayoutArrtibutesMap objectForKey:element]; +} + +#pragma mark - Private methods + +- (CGSize)viewportSize +{ + ASCollectionNode *collectionNode = _collectionNode; + if (collectionNode != nil && !collectionNode.isNodeLoaded) { + // TODO consider calculatedSize as well + return collectionNode.threadSafeBounds.size; + } else { + ASDisplayNodeAssertMainThread(); + return self.collectionView.bounds.size; + } +} + +@end diff --git a/Source/Private/ASCollectionLayoutContext+Private.h b/Source/Private/ASCollectionLayoutContext+Private.h new file mode 100644 index 0000000000..eb58c5e5d9 --- /dev/null +++ b/Source/Private/ASCollectionLayoutContext+Private.h @@ -0,0 +1,19 @@ +// +// ASCollectionLayoutContext+Private.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 10/4/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASCollectionLayoutContext (Private) + +- (instancetype)initWithViewportSize:(CGSize)viewportSize elements:(ASElementMap *)elements additionalInfo:(nullable id)additionalInfo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index c26faba35c..a5139e0b7f 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -40,7 +40,6 @@ #if DISPLAYNODE_USE_LOCKS #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 @@ -79,8 +78,6 @@ ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNo #define _setToLayer(layerProperty, layerValueExpr) BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); \ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNodeGetPendingState(self).layerProperty = (layerValueExpr); } -#define _messageToViewOrLayer(viewAndLayerSelector) (_view ? [_view viewAndLayerSelector] : [_layer viewAndLayerSelector]) - /** * This category implements certain frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node, * with minimal loss in performance. Unlike UIView and CALayer methods, these can be called from a non-main thread until the view or layer is created. @@ -301,8 +298,17 @@ - (void)setFrame:(CGRect)rect - (void)setNeedsDisplay { - _bridge_prologue_write; - if (_hierarchyState & ASHierarchyStateRasterized) { + BOOL isRasterized = NO; + BOOL shouldApply = NO; + id viewOrLayer = nil; + { + _bridge_prologue_write; + isRasterized = _hierarchyState & ASHierarchyStateRasterized; + shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + viewOrLayer = _view ?: _layer; + } + + if (isRasterized) { ASPerformBlockOnMainThread(^{ // The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node // begins materializing the view / layer hierarchy (locking itself or a descendant) while this node walks up @@ -319,13 +325,13 @@ - (void)setNeedsDisplay [rasterizedContainerNode setNeedsDisplay]; }); } else { - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); if (shouldApply) { // If not rasterized, and the node is loaded (meaning we certainly have a view or layer), send a // message to the view/layer first. This is because __setNeedsDisplay calls as scheduleNodeForDisplay, // which may call -displayIfNeeded. We want to ensure the needsDisplay flag is set now, and then cleared. - _messageToViewOrLayer(setNeedsDisplay); + [viewOrLayer setNeedsDisplay]; } else { + _bridge_prologue_write; [ASDisplayNodeGetPendingState(self) setNeedsDisplay]; } [self __setNeedsDisplay]; @@ -334,29 +340,59 @@ - (void)setNeedsDisplay - (void)setNeedsLayout { - _bridge_prologue_write; - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + BOOL shouldApply = NO; + BOOL loaded = NO; + id viewOrLayer = nil; + { + _bridge_prologue_write; + shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + loaded = __loaded(self); + viewOrLayer = _view ?: _layer; + } + if (shouldApply) { // The node is loaded and we're on main. // Quite the opposite of setNeedsDisplay, we must call __setNeedsLayout before messaging // the view or layer to ensure that measurement and implicitly added subnodes have been handled. - - // Calling __setNeedsLayout while holding the property lock can cause deadlocks - _bridge_prologue_write_unlock; [self __setNeedsLayout]; - _bridge_prologue_write; - _messageToViewOrLayer(setNeedsLayout); - } else if (__loaded(self)) { + [viewOrLayer setNeedsLayout]; + } else if (loaded) { // The node is loaded but we're not on main. - // We will call [self __setNeedsLayout] when we apply - // the pending state. We need to call it on main if the node is loaded - // to support automatic subnode management. + // We will call [self __setNeedsLayout] when we apply the pending state. + // We need to call it on main if the node is loaded to support automatic subnode management. + _bridge_prologue_write; [ASDisplayNodeGetPendingState(self) setNeedsLayout]; } else { // The node is not loaded and we're not on main. - _bridge_prologue_write_unlock; [self __setNeedsLayout]; + } +} + +- (void)layoutIfNeeded +{ + BOOL shouldApply = NO; + BOOL loaded = NO; + id viewOrLayer = nil; + { + _bridge_prologue_write; + shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + loaded = __loaded(self); + viewOrLayer = _view ?: _layer; + } + + if (shouldApply) { + // The node is loaded and we're on main. + // Message the view or layer which in turn will call __layout on us (see -[_ASDisplayLayer layoutSublayers]). + [viewOrLayer layoutIfNeeded]; + } else if (loaded) { + // The node is loaded but we're not on main. + // We will call layoutIfNeeded on the view or layer when we apply the pending state. __layout will in turn be called on us (see -[_ASDisplayLayer layoutSublayers]). + // We need to call it on main if the node is loaded to support automatic subnode management. _bridge_prologue_write; + [ASDisplayNodeGetPendingState(self) layoutIfNeeded]; + } else { + // The node is not loaded and we're not on main. + [self __layout]; } } diff --git a/Source/TextKit/ASEqualityHashHelpers.h b/Source/Private/ASEqualityHashHelpers.h similarity index 91% rename from Source/TextKit/ASEqualityHashHelpers.h rename to Source/Private/ASEqualityHashHelpers.h index 20255c13ae..7079f78529 100644 --- a/Source/TextKit/ASEqualityHashHelpers.h +++ b/Source/Private/ASEqualityHashHelpers.h @@ -9,6 +9,8 @@ // #import +#import +#import #import @@ -16,7 +18,7 @@ // This is the Hash128to64 function from Google's cityhash (available // under the MIT License). We use it to reduce multiple 64 bit hashes // into a single hash. -inline uint64_t ASHashCombine(const uint64_t upper, const uint64_t lower) { +ASDISPLAYNODE_INLINE uint64_t ASHashCombine(const uint64_t upper, const uint64_t lower) { // Murmur-inspired hashing. const uint64_t kMul = 0x9ddfea08eb382d69ULL; uint64_t a = (lower ^ upper) * kMul; @@ -28,13 +30,13 @@ inline uint64_t ASHashCombine(const uint64_t upper, const uint64_t lower) { } #if __LP64__ -inline size_t ASHash64ToNative(uint64_t key) { +ASDISPLAYNODE_INLINE size_t ASHash64ToNative(uint64_t key) { return key; } #else // Thomas Wang downscaling hash function -inline size_t ASHash64ToNative(uint64_t key) { +ASDISPLAYNODE_INLINE size_t ASHash64ToNative(uint64_t key) { key = (~key) + (key << 18); key = key ^ (key >> 31); key = key * 21; @@ -45,6 +47,12 @@ inline size_t ASHash64ToNative(uint64_t key) { } #endif +NSUInteger ASHashFromCGPoint(const CGPoint point); + +NSUInteger ASHashFromCGSize(const CGSize size); + +NSUInteger ASHashFromCGRect(const CGRect rect); + NSUInteger ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count); namespace AS { diff --git a/Source/TextKit/ASEqualityHashHelpers.mm b/Source/Private/ASEqualityHashHelpers.mm similarity index 58% rename from Source/TextKit/ASEqualityHashHelpers.mm rename to Source/Private/ASEqualityHashHelpers.mm index 3fd02471ff..3f98e4833e 100644 --- a/Source/TextKit/ASEqualityHashHelpers.mm +++ b/Source/Private/ASEqualityHashHelpers.mm @@ -10,6 +10,23 @@ #import +#import + +NSUInteger ASHashFromCGPoint(const CGPoint point) +{ + return ASHash64ToNative(ASHashCombine(std::hash()(point.x), std::hash()(point.y))); +} + +NSUInteger ASHashFromCGSize(const CGSize size) +{ + return ASHash64ToNative(ASHashCombine(std::hash()(size.width), std::hash()(size.height))); +} + +NSUInteger ASHashFromCGRect(const CGRect rect) +{ + return ASHashFromCGPoint(rect.origin) + ASHashFromCGSize(rect.size); +} + NSUInteger ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count) { uint64_t result = subhashes[0]; diff --git a/Source/Private/ASIGListAdapterBasedDataSource.h b/Source/Private/ASIGListAdapterBasedDataSource.h index 9f629b7852..6eb8ed81de 100644 --- a/Source/Private/ASIGListAdapterBasedDataSource.h +++ b/Source/Private/ASIGListAdapterBasedDataSource.h @@ -6,7 +6,9 @@ // Copyright © 2017 Facebook. All rights reserved. // -#if IG_LIST_KIT +#import + +#if AS_IG_LIST_KIT #import #import diff --git a/Source/Private/ASIGListAdapterBasedDataSource.m b/Source/Private/ASIGListAdapterBasedDataSource.m index 9b0717a499..1ed2582532 100644 --- a/Source/Private/ASIGListAdapterBasedDataSource.m +++ b/Source/Private/ASIGListAdapterBasedDataSource.m @@ -6,7 +6,9 @@ // Copyright © 2017 Facebook. All rights reserved. // -#if IG_LIST_KIT +#import + +#if AS_IG_LIST_KIT #import "ASIGListAdapterBasedDataSource.h" #import @@ -42,7 +44,7 @@ @interface ASIGListAdapterBasedDataSource () * and then we use it and clear it in beginBatchFetchWithContext: (on default queue). * * It is safe to use it without a lock in this limited way, since those two methods will - * never execute in parallel.6 + * never execute in parallel. */ @property (nonatomic, weak) ASIGSectionController *sectionControllerForBatchFetching; @end @@ -52,7 +54,9 @@ @implementation ASIGListAdapterBasedDataSource - (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter { if (self = [super init]) { +#if IG_LIST_COLLECTION_VIEW [ASIGListAdapterBasedDataSource setASCollectionViewSuperclass]; +#endif [ASIGListAdapterBasedDataSource configureUpdater:listAdapter.updater]; ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDataSource)], @"Expected IGListAdapter to conform to UICollectionViewDataSource."); @@ -244,12 +248,8 @@ - (ASIGSectionController *)sectionControllerForSection:(NSInteger)section return ctrl; } -/** - * Set ASCollectionView's superclass to IGListCollectionView. - * Scary! If IGListKit removed the subclassing restriction, we could - * use #if in the @interface to choose the superclass based on - * whether we have IGListKit available. - */ +/// If needed, set ASCollectionView's superclass to IGListCollectionView (IGListKit < 3.0). +#if IG_LIST_COLLECTION_VIEW + (void)setASCollectionViewSuperclass { #pragma clang diagnostic push @@ -260,6 +260,7 @@ + (void)setASCollectionViewSuperclass }); #pragma clang diagnostic pop } +#endif /// Ensure updater won't call reloadData on us. + (void)configureUpdater:(id)updater @@ -317,4 +318,4 @@ + (ASSectionControllerOverrides)overridesForSectionControllerClass:(Class)c @end -#endif // IG_LIST_KIT +#endif // AS_IG_LIST_KIT diff --git a/Source/Private/ASInternalHelpers.h b/Source/Private/ASInternalHelpers.h index 289d0f7598..87eb24f2f0 100644 --- a/Source/Private/ASInternalHelpers.h +++ b/Source/Private/ASInternalHelpers.h @@ -10,11 +10,7 @@ #import "ASAvailability.h" -#if AS_TARGET_OS_IOS #import -#else -#import -#endif #import @@ -77,14 +73,7 @@ ASDISPLAYNODE_INLINE BOOL ASImageAlphaInfoIsOpaque(CGImageAlphaInfo info) { */ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { if (withoutAnimation) { -#if AS_TARGET_OS_IOS [UIView performWithoutAnimation:block]; -#else - [CATransaction begin]; - [CATransaction setDisableActions: YES]; - block(); - [CATransaction commit]; -#endif } else { block(); } diff --git a/Source/Private/ASInternalHelpers.m b/Source/Private/ASInternalHelpers.m index a2c0a2f7df..385b33c32e 100644 --- a/Source/Private/ASInternalHelpers.m +++ b/Source/Private/ASInternalHelpers.m @@ -10,11 +10,7 @@ #import -#if AS_TARGET_OS_IOS #import -#else -#import -#endif #import #import @@ -92,7 +88,6 @@ void ASPerformBackgroundDeallocation(id object) BOOL ASClassRequiresMainThreadDeallocation(Class c) { -#if AS_TARGET_OS_IOS if (c == [UIImage class] || c == [UIColor class]) { return NO; } @@ -102,19 +97,6 @@ BOOL ASClassRequiresMainThreadDeallocation(Class c) || [c isSubclassOfClass:[UIGestureRecognizer class]]) { return YES; } -#else - if (c == [NSImage class] || c == [NSColor class]) { - return NO; - } - - if ([c isSubclassOfClass:[NSResponder class]] - || [c isSubclassOfClass:[CALayer class]] - || [c isSubclassOfClass:[NSGestureRecognizer class]]) { - return YES; - } -#endif - - const char *name = class_getName(c); if (strncmp(name, "UI", 2) == 0 || strncmp(name, "AV", 2) == 0 || strncmp(name, "CA", 2) == 0) { @@ -152,11 +134,7 @@ CGFloat ASScreenScale() static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ ASDisplayNodeCAssertMainThread(); -#if AS_TARGET_OS_IOS __scale = [[UIScreen mainScreen] scale]; -#else - __scale = [[NSScreen mainScreen] backingScaleFactor]; -#endif }); return __scale; } diff --git a/Source/Private/ASTwoDimensionalArrayUtils.h b/Source/Private/ASTwoDimensionalArrayUtils.h index c907227cc2..e338569934 100644 --- a/Source/Private/ASTwoDimensionalArrayUtils.h +++ b/Source/Private/ASTwoDimensionalArrayUtils.h @@ -37,6 +37,11 @@ extern void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mu */ extern NSArray *ASIndexPathsForTwoDimensionalArray(NSArray* twoDimensionalArray) AS_WARN_UNUSED_RESULT; +/** + * Return all the elements of a two-dimensional array, in ascending order. + */ +extern NSArray *ASElementsInTwoDimensionalArray(NSArray* twoDimensionalArray) AS_WARN_UNUSED_RESULT; + /** * Attempt to get the object at the given index path. Returns @c nil if the index path is out of bounds. */ diff --git a/Source/Private/ASTwoDimensionalArrayUtils.m b/Source/Private/ASTwoDimensionalArrayUtils.m index c0d8888a3b..163f0b66bd 100644 --- a/Source/Private/ASTwoDimensionalArrayUtils.m +++ b/Source/Private/ASTwoDimensionalArrayUtils.m @@ -21,9 +21,10 @@ NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) { NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; + NSInteger i = 0; for (NSArray *subarray in array) { ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - [newArray addObject:[subarray mutableCopy]]; + newArray[i++] = [subarray mutableCopy]; } return newArray; } @@ -65,17 +66,31 @@ void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableAr { NSMutableArray *result = [NSMutableArray array]; NSInteger section = 0; + NSInteger i = 0; for (NSArray *subarray in twoDimensionalArray) { ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); NSInteger itemCount = subarray.count; for (NSInteger item = 0; item < itemCount; item++) { - [result addObject:[NSIndexPath indexPathForItem:item inSection:section]]; + result[i++] = [NSIndexPath indexPathForItem:item inSection:section]; } section++; } return result; } +NSArray *ASElementsInTwoDimensionalArray(NSArray * twoDimensionalArray) +{ + NSMutableArray *result = [NSMutableArray array]; + NSInteger i = 0; + for (NSArray *subarray in twoDimensionalArray) { + ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); + for (id element in subarray) { + result[i++] = element; + } + } + return result; +} + id ASGetElementInTwoDimensionalArray(NSArray *array, NSIndexPath *indexPath) { ASDisplayNodeCAssertNotNil(indexPath, @"Expected non-nil index path"); diff --git a/Source/Private/Layout/ASLayoutSpecPrivate.h b/Source/Private/Layout/ASLayoutSpecPrivate.h index 084c8bfef4..0c31ec6cc9 100644 --- a/Source/Private/Layout/ASLayoutSpecPrivate.h +++ b/Source/Private/Layout/ASLayoutSpecPrivate.h @@ -10,7 +10,6 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import #import #import @@ -18,9 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ASLayoutSpec() { ASDN::RecursiveMutex __instanceLock__; -#if AS_TARGET_OS_IOS ASPrimitiveTraitCollection _primitiveTraitCollection; -#endif ASLayoutElementStyle *_style; NSMutableArray *_childrenArray; } diff --git a/Source/Private/_ASHierarchyChangeSet.h b/Source/Private/_ASHierarchyChangeSet.h index 1e545d0abd..ef3bf17c38 100644 --- a/Source/Private/_ASHierarchyChangeSet.h +++ b/Source/Private/_ASHierarchyChangeSet.h @@ -65,6 +65,7 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); @property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; @property (nonatomic, strong, readonly) NSIndexSet *indexSet; + @property (nonatomic, readonly) _ASHierarchyChangeType changeType; /** @@ -72,9 +73,11 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); * 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 @@ -89,10 +92,28 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); * with type .Insert or .Delete. Calling this on changes of other types is an error. */ - (_ASHierarchyItemChange *)changeByFinalizingType; + @end @interface _ASHierarchyChangeSet : NSObject +/// @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; + +@property (nonatomic, readonly) BOOL completed; + +/// Whether or not changes should be animated. +// TODO: if any update in this chagne set is non-animated, the whole update should be non-animated. +@property (nonatomic, readwrite) BOOL animated; + +@property (nonatomic, readonly) BOOL includesReloadData; + +/// Indicates whether the change set is empty, that is it includes neither reload data nor per item or section changes. +@property (nonatomic, readonly) BOOL isEmpty; + - (instancetype)initWithOldData:(std::vector)oldItemCounts NS_DESIGNATED_INITIALIZER; /** @@ -113,30 +134,20 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); */ - (void)executeCompletionHandlerWithFinished:(BOOL)finished; -/// @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; - /** - Get the section index after the update for the given section before the update. - - @precondition The change set must be completed. - @return The new section index, or NSNotFound if the given section was deleted. + * Get the section index after the update for the given section before the update. + * + * @precondition The change set must be completed. + * @return The new section index, or NSNotFound if the given section was deleted. */ - (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection; -@property (nonatomic, readonly) BOOL completed; -/// Whether or not changes should be animated. -// TODO: if any update in this chagne set is non-animated, the whole update should be non-animated. -@property (nonatomic, readwrite) BOOL animated; -@property (nonatomic, readonly) BOOL includesReloadData; - /// 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. /// NOTE: Calling this method will cause the changeset to convert all reloads into delete/insert pairs. - (void)markCompletedWithNewItemCounts:(std::vector)newItemCounts; - (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. diff --git a/Source/Private/_ASHierarchyChangeSet.mm b/Source/Private/_ASHierarchyChangeSet.mm index 9a8857fcfb..3126b119ad 100644 --- a/Source/Private/_ASHierarchyChangeSet.mm +++ b/Source/Private/_ASHierarchyChangeSet.mm @@ -149,6 +149,11 @@ - (instancetype)initWithOldData:(std::vector)oldItemCounts #pragma mark External API +- (BOOL)isEmpty +{ + return (! _includesReloadData) && (! [self _includesPerItemOrSectionChanges]); +} + - (void)addCompletionHandler:(void (^)(BOOL))completion { [self _ensureNotCompleted]; @@ -423,13 +428,10 @@ - (void)_sortAndCoalesceChangeArrays - (void)_validateUpdate { - // Assert that if reloadData exists, it's the only change - // TODO: remove this and be lenient on them? + // If reloadData exists, ignore other changes if (_includesReloadData) { - if (0 < (_originalDeleteSectionChanges.count + _originalDeleteItemChanges.count - +_originalInsertSectionChanges.count + _originalInsertItemChanges.count - + _reloadSectionChanges.count + _reloadItemChanges.count)) { - ASFailUpdateValidation(@"Attempt to reload data in conjuntion with other updates."); + if ([self _includesPerItemOrSectionChanges]) { + NSLog(@"Warning: A reload data shouldn't be used in conjuntion with other updates."); } return; } @@ -544,6 +546,13 @@ - (void)_validateUpdate } } +- (BOOL)_includesPerItemOrSectionChanges +{ + return 0 < (_originalDeleteSectionChanges.count + _originalDeleteItemChanges.count + +_originalInsertSectionChanges.count + _originalInsertItemChanges.count + + _reloadSectionChanges.count + _reloadItemChanges.count); +} + #pragma mark - Debugging (Private) - (NSString *)description diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index 6f0a5426f2..0baad11298 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -24,7 +24,8 @@ // Properties int needsDisplay:1; int needsLayout:1; - + int layoutIfNeeded:1; + // Flags indicating that a given property should be applied to the view at creation int setClipsToBounds:1; int setOpaque:1; @@ -272,6 +273,11 @@ - (void)setNeedsLayout _flags.needsLayout = YES; } +- (void)layoutIfNeeded +{ + _flags.layoutIfNeeded = YES; +} + - (void)setClipsToBounds:(BOOL)flag { clipsToBounds = flag; @@ -761,9 +767,6 @@ - (void)applyToLayer:(CALayer *)layer if (flags.setEdgeAntialiasingMask) layer.edgeAntialiasingMask = edgeAntialiasingMask; - if (flags.needsLayout) - [layer setNeedsLayout]; - if (flags.setAsyncTransactionContainer) layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -771,6 +774,12 @@ - (void)applyToLayer:(CALayer *)layer ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); ASPendingStateApplyMetricsToLayer(self, layer); + + if (flags.needsLayout) + [layer setNeedsLayout]; + + if (flags.layoutIfNeeded) + [layer layoutIfNeeded]; } - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPropertiesHandling @@ -889,9 +898,6 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setEdgeAntialiasingMask) layer.edgeAntialiasingMask = edgeAntialiasingMask; - if (flags.needsLayout) - [view setNeedsLayout]; - if (flags.setAsyncTransactionContainer) view.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -955,6 +961,12 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr } else { ASPendingStateApplyMetricsToLayer(self, layer); } + + if (flags.needsLayout) + [view setNeedsLayout]; + + if (flags.layoutIfNeeded) + [view layoutIfNeeded]; } // FIXME: Make this more efficient by tracking which properties are set rather than reading everything. diff --git a/Source/tvOS/ASControlNode+tvOS.m b/Source/tvOS/ASControlNode+tvOS.m index b1da932137..005b8832ea 100644 --- a/Source/tvOS/ASControlNode+tvOS.m +++ b/Source/tvOS/ASControlNode+tvOS.m @@ -9,6 +9,9 @@ // 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 + #if TARGET_OS_TV #import diff --git a/Source/tvOS/ASImageNode+tvOS.m b/Source/tvOS/ASImageNode+tvOS.m index 535210a385..4066c41de2 100644 --- a/Source/tvOS/ASImageNode+tvOS.m +++ b/Source/tvOS/ASImageNode+tvOS.m @@ -10,6 +10,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #if TARGET_OS_TV #import diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 5262bfa09a..e126edd5fc 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -114,7 +114,7 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - return [[ASCellNode alloc] init]; + return [[ASTextCellNodeWithSetSelectedCounter alloc] init]; } - (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context @@ -487,12 +487,24 @@ - (void)testCellNodeLayoutAttributes updateValidationTestPrologue NSSet *nodeBatch1 = [NSSet setWithArray:[cn visibleNodes]]; XCTAssertGreaterThan(nodeBatch1.count, 0); + + NSArray *visibleLayoutAttributesBatch1 = [cv.collectionViewLayout layoutAttributesForElementsInRect:cv.bounds]; + XCTAssertGreaterThan(visibleLayoutAttributesBatch1.count, 0); // Expect all visible nodes get 1 applyLayoutAttributes and have a non-nil value. for (ASTextCellNodeWithSetSelectedCounter *node in nodeBatch1) { XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible nodes."); XCTAssertNotNil(node.layoutAttributes, @"Expected layoutAttributes to be non-nil for visible cell node."); } + + for (UICollectionViewLayoutAttributes *layoutAttributes in visibleLayoutAttributesBatch1) { + if (layoutAttributes.representedElementCategory != UICollectionElementCategorySupplementaryView) { + continue; + } + ASTextCellNodeWithSetSelectedCounter *node = (ASTextCellNodeWithSetSelectedCounter *)[cv supplementaryNodeForElementKind:layoutAttributes.representedElementKind atIndexPath:layoutAttributes.indexPath]; + XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible supplementary nodes."); + XCTAssertNotNil(node.layoutAttributes, @"Expected layoutAttributes to be non-nil for visible supplementary node."); + } // Scroll to next batch of items. NSIndexPath *nextIP = [NSIndexPath indexPathForItem:nodeBatch1.count inSection:0]; @@ -508,6 +520,15 @@ - (void)testCellNodeLayoutAttributes XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible nodes, even after node is removed."); XCTAssertNil(node.layoutAttributes, @"Expected layoutAttributes to be nil for removed cell node."); } + + for (UICollectionViewLayoutAttributes *layoutAttributes in visibleLayoutAttributesBatch1) { + if (layoutAttributes.representedElementCategory != UICollectionElementCategorySupplementaryView) { + continue; + } + ASTextCellNodeWithSetSelectedCounter *node = (ASTextCellNodeWithSetSelectedCounter *)[cv supplementaryNodeForElementKind:layoutAttributes.representedElementKind atIndexPath:layoutAttributes.indexPath]; + XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible supplementary nodes, even after node is removed."); + XCTAssertNil(node.layoutAttributes, @"Expected layoutAttributes to be nil for removed supplementary node."); + } } - (void)testCellNodeIndexPathConsistency @@ -1044,6 +1065,38 @@ - (void)testInitialRangeBounds XCTAssertEqual([[cn valueForKeyPath:@"rangeController.currentRangeMode"] integerValue], ASLayoutRangeModeMinimum, @"Expected range mode to be minimum before scrolling begins."); } +- (void)testTraitCollectionChangesMidUpdate +{ + CGRect screenBounds = [UIScreen mainScreen].bounds; + UIWindow *window = [[UIWindow alloc] initWithFrame:screenBounds]; + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + ASCollectionNode *cn = testController.collectionNode; + window.rootViewController = testController; + + [window makeKeyAndVisible]; + // Trigger the initial reload to start + [window layoutIfNeeded]; + + // The initial reload is async, changing the trait collection here should be "mid-update" + ASPrimitiveTraitCollection traitCollection; + traitCollection.displayScale = cn.primitiveTraitCollection.displayScale + 1; // Just a dummy change + traitCollection.containerSize = screenBounds.size; + cn.primitiveTraitCollection = traitCollection; + + [cn waitUntilAllUpdatesAreCommitted]; + [cn.view layoutIfNeeded]; + + // Assert that the new trait collection is picked up by all cell nodes, including ones that were not allocated but are forced to allocate now + for (NSInteger s = 0; s < cn.numberOfSections; s++) { + NSInteger c = [cn numberOfItemsInSection:s]; + for (NSInteger i = 0; i < c; i++) { + NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s]; + ASCellNode *node = [cn.view nodeForItemAtIndexPath:ip]; + XCTAssertTrue(ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, node.primitiveTraitCollection)); + } + } +} + /** * This tests an issue where, since subnode insertions aren't applied until the UIKit layout pass, * which we trigger during the display phase, subnodes like network image nodes are not preloading diff --git a/Tests/ASTableViewTests.mm b/Tests/ASTableViewTests.mm index 1190a9e780..b66eac6e3b 100644 --- a/Tests/ASTableViewTests.mm +++ b/Tests/ASTableViewTests.mm @@ -382,7 +382,7 @@ - (void)testRelayoutAllNodesWithNonZeroSizeInitially CGSize tableViewFinalSize = CGSizeMake(100, 500); // Width and height are swapped so that a later size change will simulate a rotation ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width) - style:UITableViewStylePlain]; + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; @@ -475,7 +475,7 @@ - (void)DISABLED_testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible { CGSize tableViewSize = CGSizeMake(100, 500); ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) - style:UITableViewStylePlain]; + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; tableView.asyncDelegate = dataSource; diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index d1718e2f9f..d00ea60df6 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -44,25 +44,15 @@ - (void)viewDidLoad { [super viewDidLoad]; - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - layout.headerReferenceSize = CGSizeMake(50.0, 50.0); - layout.footerReferenceSize = CGSizeMake(50.0, 50.0); - - // This method is deprecated because we reccommend using ASCollectionNode instead of ASCollectionView. - // This functionality & example project remains for users who insist on using ASCollectionView. - self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; + self.collectionNode = [[ASCollectionNode alloc] initWithLayoutDelegate:[[ASCollectionFlowLayoutDelegate alloc] init] layoutFacilitator:nil]; self.collectionNode.dataSource = self; self.collectionNode.delegate = self; self.collectionNode.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.collectionNode.backgroundColor = [UIColor whiteColor]; - // This method is deprecated because we reccommend using ASCollectionNode instead of ASCollectionView. - // This functionality & example project remains for users who insist on using ASCollectionView. - [self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; - [self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; - [self.view addSubnode:self.collectionNode]; + self.collectionNode.frame = self.view.bounds; #if !SIMULATE_WEB_RESPONSE self.navigationItem.leftItemsSupplementBackButton = YES;