Introduce ASCollectionGalleryLayoutDelegate (#76)

* Implement ASCollectionGalleryLayoutDelegate
- It arranges items of the same size into a multi-line stack (say photo gallery or pager). It takes advantage of the fact that its items always have a fixed size to measure as few items as possible while still being able to track their positions at all time. This helps reduce startup/reloadData time, as well as memory footprint.
- It then uses a measure range, which also works as a allocate range, to figure out which items to measure ahead of time. And it guarantees that each item is scheduled to measure only once.
- Lastly, ASCollectionLayoutDelegate has some new methods that allow delegates to hook up and stay ahead of layout attributes requests from the backing view. ASCollectionGalleryLayoutDelegate for example uses these methods to ensure elements that have their layout attributes requested are always ready for consumption, and to measure more elements in the background.

* Handle items that span multiple pages and other improvements in gallery delegate

* Minor fixes

* Fix failing tests

* Fix custom collection example

* Implement missing method in gallery layout delegate

* Fix warnings

* Some improvements
- Collection layout delegates must have a crollable directions property.
- Simplify gallery delegate by not storing unmeasured attributes since calling measure on already measured elements should be cache hits and super fast.
- Abstact some code in gallery delegate to ASCollectionLayoutState+Private and _ASCollectionGalleryLayoutItem.
- Other improvements in gallery delegate

* Fix file licenses

* Move measure range logic to ASCollectionLayout

* Track unmeasured elements

* Remove pending layout in ASCollectionLayout

* Get back pending layout because the timing to latch new data is not ideal

* Add ASCollectionLayoutCache

* Fix file licenses

* Fix xcodeproj

* Add async collection layout to examples/ASCollectionView

* Measure method in ASCollectionLayout to be a class method

* Encourage more immutable states
- Make -calculateLayoutWithContext: to be class methods in ASDataControllerLayoutDelegate and ASCollectionLayoutDelegate.
- Add layout delegate class and layout cache to ASCollectionLayoutContext+Private, to be use by ASCollectionLayout only.
- ASDataController no longer allocates all nodes but lets ASCollectionLayout determine.
- Add scrollableDirections to the layout context since it's often needed by the layout pass. Otherwise users have to wrap it in an info object.
- Update built-in layout delegates and CustomCollectionView example.
- Publish ASHashing. It might be helpful for clients that implement custom collection info objects.

* Remove additionalInfo property in ASCollectionLayoutState

* ASCollectionLayoutState to correctly filter unmeasured elements

* Add ASHashing to umbrella header

* Fix file licenses

* Add ASDispatchAsync and use it in ASCollectionLayout

* Improve code comment in ASCollectionLayoutState
This commit is contained in:
Huy Nguyen 2017-07-14 18:50:26 +00:00 committed by GitHub
parent dc06cadb8a
commit 3ccc2f0f15
40 changed files with 1478 additions and 467 deletions

View File

@ -424,18 +424,23 @@
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 /* ASHashing.h in Headers */ = {isa = PBXBuildFile; fileRef = E516FC7D1E9FE24200714FF4 /* ASHashing.h */; };
E516FC801E9FE24200714FF4 /* ASHashing.m in Sources */ = {isa = PBXBuildFile; fileRef = E516FC7E1E9FE24200714FF4 /* ASHashing.m */; };
E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */; };
E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */; };
E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */; };
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 */; };
E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */ = {isa = PBXBuildFile; fileRef = E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */; };
E5775AFE1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */; };
E5775B001F13D25400CAC9BC /* ASCollectionLayoutState+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
E5775B021F16759300CAC9BC /* ASCollectionLayoutCache.h in Headers */ = {isa = PBXBuildFile; fileRef = E5775B011F16759300CAC9BC /* ASCollectionLayoutCache.h */; settings = {ATTRIBUTES = (Private, ); }; };
E5775B041F16759F00CAC9BC /* ASCollectionLayoutCache.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5775B031F16759F00CAC9BC /* ASCollectionLayoutCache.mm */; };
E5855DEF1EBB4D83003639AE /* ASCollectionLayoutDefines.m in Sources */ = {isa = PBXBuildFile; fileRef = E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.m */; };
E5855DF01EBB4D83003639AE /* ASCollectionLayoutDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */; settings = {ATTRIBUTES = (Private, ); }; };
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 */; };
E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.m */; };
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 */; };
@ -443,11 +448,16 @@
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 */; };
E5B225281F1790D6001E1431 /* ASHashing.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B225271F1790B5001E1431 /* ASHashing.h */; settings = {ATTRIBUTES = (Public, ); }; };
E5B225291F1790EE001E1431 /* ASHashing.m in Sources */ = {isa = PBXBuildFile; fileRef = E5B225261F1790B5001E1431 /* ASHashing.m */; };
E5B2252E1F17E521001E1431 /* ASDispatch.m in Sources */ = {isa = PBXBuildFile; fileRef = E5B2252D1F17E521001E1431 /* ASDispatch.m */; };
E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
E5C347B11ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E5C347B01ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h */; };
E5C347B31ECB40AA00EC4BE4 /* ASTableNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = E5C347B21ECB40AA00EC4BE4 /* ASTableNode+Beta.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 */; };
E5E281761E71C845006B67C2 /* ASCollectionLayoutState.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */; };
E5E2D72E1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.m */; };
F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */; };
/* End PBXBuildFile section */
@ -899,8 +909,6 @@
DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASCollectionInternal.m; path = Details/ASCollectionInternal.m; sourceTree = "<group>"; };
DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = "<group>"; };
DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = "<group>"; };
E516FC7D1E9FE24200714FF4 /* ASHashing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASHashing.h; sourceTree = "<group>"; };
E516FC7E1E9FE24200714FF4 /* ASHashing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASHashing.m; sourceTree = "<group>"; };
E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutFlatteningTests.m; sourceTree = "<group>"; };
E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTransition.mm; sourceTree = "<group>"; };
E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTransition.h; sourceTree = "<group>"; };
@ -909,10 +917,17 @@
E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutElement.mm; sourceTree = "<group>"; };
E5711A2A1C840C81009619D4 /* ASCollectionElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionElement.h; sourceTree = "<group>"; };
E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionElement.mm; sourceTree = "<group>"; };
E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionGalleryLayoutItem.h; sourceTree = "<group>"; };
E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASCollectionGalleryLayoutItem.mm; sourceTree = "<group>"; };
E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionLayoutState+Private.h"; sourceTree = "<group>"; };
E5775B011F16759300CAC9BC /* ASCollectionLayoutCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutCache.h; sourceTree = "<group>"; };
E5775B031F16759F00CAC9BC /* ASCollectionLayoutCache.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutCache.mm; sourceTree = "<group>"; };
E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionLayoutDefines.m; sourceTree = "<group>"; };
E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutDefines.h; sourceTree = "<group>"; };
E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionFlowLayoutDelegate.h; sourceTree = "<group>"; };
E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionFlowLayoutDelegate.m; sourceTree = "<group>"; };
E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutContext.h; sourceTree = "<group>"; };
E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutContext.mm; sourceTree = "<group>"; };
E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionLayoutContext.m; sourceTree = "<group>"; };
E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutDelegate.h; sourceTree = "<group>"; };
E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayout.h; sourceTree = "<group>"; };
E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayout.mm; sourceTree = "<group>"; };
@ -920,11 +935,16 @@
E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTable.m; sourceTree = "<group>"; };
E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASElementMap.h; sourceTree = "<group>"; };
E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASElementMap.m; sourceTree = "<group>"; };
E5B225261F1790B5001E1431 /* ASHashing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASHashing.m; sourceTree = "<group>"; };
E5B225271F1790B5001E1431 /* ASHashing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASHashing.h; sourceTree = "<group>"; };
E5B2252D1F17E521001E1431 /* ASDispatch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASDispatch.m; sourceTree = "<group>"; };
E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionLayoutContext+Private.h"; sourceTree = "<group>"; };
E5C347B01ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchFetchingDelegate.h; sourceTree = "<group>"; };
E5C347B21ECB40AA00EC4BE4 /* ASTableNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASTableNode+Beta.h"; sourceTree = "<group>"; };
E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutState.h; sourceTree = "<group>"; };
E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionLayoutState.m; sourceTree = "<group>"; };
E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutState.mm; sourceTree = "<group>"; };
E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionGalleryLayoutDelegate.h; sourceTree = "<group>"; };
E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionGalleryLayoutDelegate.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
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 = "<group>"; };
FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = "<group>"; };
@ -1242,6 +1262,8 @@
205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */,
696F01EA1DD2AF450049FBD5 /* ASEventLog.h */,
696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */,
E5B225271F1790B5001E1431 /* ASHashing.h */,
E5B225261F1790B5001E1431 /* ASHashing.m */,
4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */,
4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */,
058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */,
@ -1334,6 +1356,7 @@
CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */,
6947B0BB1E36B4E30007C478 /* Layout */,
CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */,
E52F8AEE1EAE659600B5A912 /* Collection Layout */,
058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */,
058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */,
AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */,
@ -1347,9 +1370,6 @@
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 */,
@ -1360,6 +1380,7 @@
AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */,
AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */,
CC54A81B1D70077A00296A24 /* ASDispatch.h */,
E5B2252D1F17E521001E1431 /* ASDispatch.m */,
058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */,
058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */,
058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */,
@ -1369,8 +1390,6 @@
058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */,
6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */,
6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */,
E516FC7D1E9FE24200714FF4 /* ASHashing.h */,
E516FC7E1E9FE24200714FF4 /* ASHashing.m */,
6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */,
68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */,
058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */,
@ -1637,16 +1656,35 @@
path = Debug;
sourceTree = "<group>";
};
E52F8AEE1EAE659600B5A912 /* Collection Layout */ = {
isa = PBXGroup;
children = (
E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */,
E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */,
E5775B011F16759300CAC9BC /* ASCollectionLayoutCache.h */,
E5775B031F16759F00CAC9BC /* ASCollectionLayoutCache.mm */,
E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */,
E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */,
E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.m */,
E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */,
E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */,
E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */,
);
name = "Collection Layout";
sourceTree = "<group>";
};
E5B077EB1E6843AF00C24B5B /* Collection Layout */ = {
isa = PBXGroup;
children = (
E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */,
E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */,
E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.m */,
E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */,
E5E281751E71C845006B67C2 /* ASCollectionLayoutState.m */,
E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */,
E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */,
E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */,
E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */,
E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */,
E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.m */,
E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */,
E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */,
);
@ -1670,8 +1708,10 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
E5B225281F1790D6001E1431 /* ASHashing.h in Headers */,
CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */,
693A1DCA1ECC944E00D0C9D2 /* IGListAdapter+AsyncDisplayKit.h in Headers */,
E5E2D72E1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h in Headers */,
E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */,
E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */,
E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */,
@ -1778,11 +1818,14 @@
044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */,
AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */,
CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */,
E5775B021F16759300CAC9BC /* ASCollectionLayoutCache.h in Headers */,
E5775B001F13D25400CAC9BC /* ASCollectionLayoutState+Private.h in Headers */,
E5855DF01EBB4D83003639AE /* ASCollectionLayoutDefines.h in Headers */,
E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */,
9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */,
254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */,
CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */,
DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */,
E516FC7F1E9FE24200714FF4 /* ASHashing.h in Headers */,
9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */,
6947B0C31E36B5040007C478 /* ASStackPositionedLayout.h in Headers */,
DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */,
@ -1798,7 +1841,6 @@
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 */,
@ -1850,6 +1892,7 @@
254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */,
B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */,
CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */,
E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */,
25E327571C16819500A2170C /* ASPagerNode.h in Headers */,
CCCCCCDB1EC3EF060087FE10 /* ASTextLine.h in Headers */,
9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */,
@ -2140,6 +2183,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E5B225291F1790EE001E1431 /* ASHashing.m in Sources */,
DEB8ED7C1DD003D300DBDE55 /* ASLayoutTransition.mm in Sources */,
CCA5F62E1EECC2A80060C137 /* ASAssert.m in Sources */,
9F98C0261DBE29E000476D92 /* ASControlTargetAction.m in Sources */,
@ -2154,7 +2198,6 @@
CCA282B91E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.m in Sources */,
636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */,
B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.mm in Sources */,
E516FC801E9FE24200714FF4 /* ASHashing.m in Sources */,
6947B0C51E36B5040007C478 /* ASStackPositionedLayout.mm in Sources */,
B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */,
AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */,
@ -2183,13 +2226,14 @@
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */,
AC47D9421B3B891B00AAEE9D /* ASCellNode.mm in Sources */,
E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.mm in Sources */,
E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.m in Sources */,
34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */,
18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */,
E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */,
68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */,
CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.m in Sources */,
68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */,
E5775B041F16759F00CAC9BC /* ASCollectionLayoutCache.mm in Sources */,
9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */,
69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */,
B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */,
@ -2211,7 +2255,7 @@
68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */,
E5711A301C840C96009619D4 /* ASCollectionElement.mm in Sources */,
B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */,
E5E281761E71C845006B67C2 /* ASCollectionLayoutState.m in Sources */,
E5E281761E71C845006B67C2 /* ASCollectionLayoutState.mm in Sources */,
B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */,
B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */,
B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */,
@ -2226,16 +2270,19 @@
E58E9E4A1E941DA5004CFC59 /* ASCollectionLayout.mm in Sources */,
6947B0C01E36B4E30007C478 /* ASStackUnpositionedLayout.mm in Sources */,
68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */,
E5855DEF1EBB4D83003639AE /* ASCollectionLayoutDefines.m in Sources */,
B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */,
254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */,
34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */,
AC6145441D8AFD4F003D62A2 /* ASSection.m in Sources */,
E5775AFE1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm in Sources */,
34EFC75E1B701BF000AD841F /* ASInternalHelpers.m in Sources */,
34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */,
DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.m in Sources */,
CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.m in Sources */,
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */,
E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.m in Sources */,
34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */,
CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */,
254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */,
@ -2265,6 +2312,7 @@
E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */,
34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */,
7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */,
E5B2252E1F17E521001E1431 /* ASDispatch.m in Sources */,
696F01EE1DD2AF450049FBD5 /* ASEventLog.mm in Sources */,
9C70F2051CDA4F06007D6C76 /* ASTraitCollection.m in Sources */,
83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */,

View File

@ -3,6 +3,7 @@
* Add your own contributions to the next release on the line below this with your name.
- [ASTextNode2] Add initial implementation for link handling. [Scott Goodson](https://github.com/appleguy) [#396](https://github.com/TextureGroup/Texture/pull/396)
- [ASTextNode2] Provide compile flag to globally enable new implementation of ASTextNode: ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE. [Scott Goodson](https://github.com/appleguy) [#396](https://github.com/TextureGroup/Texture/pull/410)
- Add ASCollectionGalleryLayoutDelegate - an async collection layout that makes same-size collections (e.g photo galleries, pagers, etc) fast and lightweight! [Huy Nguyen](https://github.com/nguyenhuy/) [#76](https://github.com/TextureGroup/Texture/pull/76)
##2.3.5
- Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler)

View File

@ -216,6 +216,7 @@
[super didEnterPreloadState];
// Intentionally allocate the view here and 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.
// TODO (ASCL) If this node supports async layout, kick off the initial data load without allocating the view
[[self view] layoutIfNeeded];
}

View File

@ -52,6 +52,7 @@
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASCollectionFlowLayoutDelegate.h>
#import <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>
#import <AsyncDisplayKit/ASSectionController.h>
#import <AsyncDisplayKit/ASSupplementaryNodeSource.h>
@ -98,17 +99,18 @@
#import <AsyncDisplayKit/ASControlNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASEventLog.h>
#import <AsyncDisplayKit/ASHashing.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
#import <AsyncDisplayKit/ASLog.h>
#import <AsyncDisplayKit/ASMutableAttributedStringBuilder.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASRunLoopQueue.h>
#import <AsyncDisplayKit/ASTextKitComponents.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASTraitCollection.h>
#import <AsyncDisplayKit/ASVisibilityProtocols.h>
#import <AsyncDisplayKit/ASWeakSet.h>
#import <AsyncDisplayKit/ASEventLog.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#import <AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h>

View File

@ -16,7 +16,6 @@
//
#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
#import <AsyncDisplayKit/ASScrollDirection.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -17,10 +17,11 @@
#import <AsyncDisplayKit/ASCollectionFlowLayoutDelegate.h>
#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASCellNode+Internal.h>
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutDefines.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
@ -43,18 +44,9 @@
return self;
}
- (ASSizeRange)sizeRangeThatFits:(CGSize)viewportSize
- (ASScrollDirection)scrollableDirections
{
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;
return _scrollableDirections;
}
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
@ -62,7 +54,7 @@
return nil;
}
- (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
NSMutableArray<ASCellNode *> *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node);
@ -80,8 +72,13 @@
alignContent:ASStackLayoutAlignContentStart
children:children];
stackSpec.concurrent = YES;
ASLayout *layout = [stackSpec layoutThatFits:[self sizeRangeThatFits:context.viewportSize]];
return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout];
ASSizeRange sizeRange = ASSizeRangeForCollectionLayoutThatFitsViewportSize(context.viewportSize, context.scrollableDirections);
ASLayout *layout = [stackSpec layoutThatFits:sizeRange];
return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nonnull(ASLayout * _Nonnull sublayout) {
return ((ASCellNode *)sublayout.layoutElement).collectionElement;
}];
}
@end

View File

@ -0,0 +1,32 @@
//
// ASCollectionGalleryLayoutDelegate.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
#import <AsyncDisplayKit/ASScrollDirection.h>
NS_ASSUME_NONNULL_BEGIN
/**
* A thread-safe layout delegate that arranges items with the same size into a flow layout.
*
* @note Supplemenraty elements are not supported.
*/
AS_SUBCLASSING_RESTRICTED
@interface ASCollectionGalleryLayoutDelegate : NSObject <ASCollectionLayoutDelegate>
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections itemSize:(CGSize)itemSize NS_DESIGNATED_INITIALIZER;
- (instancetype)init __unavailable;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,86 @@
//
// ASCollectionGalleryLayoutDelegate.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>
#import <AsyncDisplayKit/_ASCollectionGalleryLayoutItem.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutDefines.h>
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutRangeType.h>
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
#pragma mark - ASCollectionGalleryLayoutDelegate
@implementation ASCollectionGalleryLayoutDelegate {
ASScrollDirection _scrollableDirections;
CGSize _itemSize;
}
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections itemSize:(CGSize)itemSize
{
self = [super init];
if (self) {
ASDisplayNodeAssertFalse(CGSizeEqualToSize(CGSizeZero, itemSize));
_scrollableDirections = scrollableDirections;
_itemSize = itemSize;
}
return self;
}
- (ASScrollDirection)scrollableDirections
{
return _scrollableDirections;
}
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{
return [NSValue valueWithCGSize:_itemSize];
}
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
CGSize pageSize = context.viewportSize;
CGSize itemSize = ((NSValue *)context.additionalInfo).CGSizeValue;
ASScrollDirection scrollableDirections = context.scrollableDirections;
NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements,
ASCollectionElement *element,
[[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]);
if (children.count == 0) {
return [[ASCollectionLayoutState alloc] initWithContext:context
contentSize:CGSizeZero
elementToLayoutAttributesTable:[NSMapTable weakToStrongObjectsMapTable]];
}
// Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element
ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:0
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStart
flexWrap:ASStackLayoutFlexWrapWrap
alignContent:ASStackLayoutAlignContentStart
children:children];
stackSpec.concurrent = YES;
ASLayout *layout = [stackSpec layoutThatFits:ASSizeRangeForCollectionLayoutThatFitsViewportSize(pageSize, scrollableDirections)];
return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement *(ASLayout *sublayout) {
return ((_ASGalleryLayoutItem *)sublayout.layoutElement).collectionElement;
}];
}
@end

View File

@ -15,20 +15,20 @@
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASScrollDirection.h>
@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, assign, readonly) ASScrollDirection scrollableDirections;
@property (nonatomic, weak, readonly) ASElementMap *elements;
@property (nonatomic, strong, readonly, nullable) id additionalInfo;
- (instancetype)init __unavailable;

View File

@ -0,0 +1,107 @@
//
// ASCollectionLayoutContext.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext+Private.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
#import <AsyncDisplayKit/ASCollectionLayoutCache.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASHashing.h>
@implementation ASCollectionLayoutContext {
Class<ASCollectionLayoutDelegate> _layoutDelegateClass;
// This ivar doesn't directly involve in the layout calculation process, i.e contexts can be equal regardless of the layout caches.
// As a result, this ivar is ignored in -isEqualToContext: and -hash.
__weak ASCollectionLayoutCache *_layoutCache;
}
- (instancetype)initWithViewportSize:(CGSize)viewportSize
scrollableDirections:(ASScrollDirection)scrollableDirections
elements:(ASElementMap *)elements
layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass
layoutCache:(ASCollectionLayoutCache *)layoutCache
additionalInfo:(id)additionalInfo
{
self = [super init];
if (self) {
ASDisplayNodeAssertTrue([layoutDelegateClass conformsToProtocol:@protocol(ASCollectionLayoutDelegate)]);
_viewportSize = viewportSize;
_scrollableDirections = scrollableDirections;
_elements = elements;
_layoutDelegateClass = layoutDelegateClass;
_layoutCache = layoutCache;
_additionalInfo = additionalInfo;
}
return self;
}
- (Class<ASCollectionLayoutDelegate>)layoutDelegateClass
{
return _layoutDelegateClass;
}
- (ASCollectionLayoutCache *)layoutCache
{
return _layoutCache;
}
- (BOOL)isEqualToContext:(ASCollectionLayoutContext *)context
{
if (context == nil) {
return NO;
}
// NOTE: ASObjectIsEqual returns YES when both objects are nil.
// So don't use ASObjectIsEqual on _elements.
// It is a weak property and 2 layouts generated from different sets of elements
// should never be considered the same even if they are nil now.
return CGSizeEqualToSize(_viewportSize, context.viewportSize)
&& _scrollableDirections == context.scrollableDirections
&& [_elements isEqual:context.elements]
&& _layoutDelegateClass == context.layoutDelegateClass
&& 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
{
struct {
CGSize viewportSize;
ASScrollDirection scrollableDirections;
NSUInteger elementsHash;
NSUInteger layoutDelegateClassHash;
NSUInteger additionalInfoHash;
} data = {
_viewportSize,
_scrollableDirections,
_elements.hash,
_layoutDelegateClass.hash,
[_additionalInfo hash]
};
return ASHashBytes(&data, sizeof(data));
}
@end

View File

@ -1,72 +0,0 @@
//
// ASCollectionLayoutContext.mm
// Texture
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext+Private.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASHashing.h>
@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
{
struct {
CGSize viewportSize;
NSUInteger elementsHash;
NSUInteger addlInfoHash;
} data = {
_viewportSize,
_elements.hash,
[_additionalInfo hash]
};
return ASHashBytes(&data, sizeof(data));
}
@end

View File

@ -17,6 +17,7 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASScrollDirection.h>
@class ASElementMap, ASCollectionLayoutContext, ASCollectionLayoutState;
@ -25,13 +26,22 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ASCollectionLayoutDelegate <NSObject>
/**
* @abstract Returns any additional information needed for a coming layout pass with the given elements.
* @abstract Returns the scrollable directions of the coming layout (@see @c -calculateLayoutWithContext:).
* It will be available in the context parameter in +calculateLayoutWithContext:
*
* @return The scrollable directions.
*/
- (ASScrollDirection)scrollableDirections;
/**
* @abstract Returns any additional information needed for a coming layout pass (@see @c -calculateLayoutWithContext:) with the given elements.
*
* @param elements The elements to be laid out later.
*
* @discussion The returned object must support equality and hashing (i.e `-isEqual:` and `-hash` must be properly implemented).
* It should contain all the information needed for the layout pass to perform. It will be available in the context parameter in +calculateLayoutWithContext:
*
* @discussion This method will be called on main thread.
* This method will be called on main thread.
*/
- (nullable id)additionalInfoForLayoutWithElements:(ASElementMap *)elements;
@ -43,13 +53,12 @@ NS_ASSUME_NONNULL_BEGIN
* @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.
* As a result, clients must solely rely 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.
* This method can be called on background theads. It must be thread-safe and should not change any internal state of this delegate.
* It must block the calling thread but can dispatch to other theads to reduce total blocking time.
*/
- (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context;
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context;
@end

View File

@ -30,6 +30,8 @@ NS_ASSUME_NONNULL_BEGIN
@end
AS_SUBCLASSING_RESTRICTED
/// An immutable state of the collection layout
@interface ASCollectionLayoutState : NSObject
/// The context used to calculate this object
@ -47,20 +49,25 @@ AS_SUBCLASSING_RESTRICTED
*
* @param contentSize The content size of the collection's layout
*
* @param table A map between elements to their layout attributes. It may contain all elements, or a subset of them that will be updated later.
* It should be initialized using +[NSMapTable elementToLayoutAttributesTable] convenience initializer.
* @param table A map between elements to their layout attributes. It must contain all elements.
* It should have NSMapTableObjectPointerPersonality and NSMapTableWeakMemory as key options.
*/
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context contentSize:(CGSize)contentSize elementToLayoutAttributesTable:(NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)table NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context
contentSize:(CGSize)contentSize
elementToLayoutAttributesTable:(NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)table NS_DESIGNATED_INITIALIZER;
/**
* Convenience initializer.
*
* @param context The context 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 over time.
* @param layout The layout describes size and position of all elements.
*
* @param getElementBlock A block that can retrieve the collection element from a direct sublayout of the root layout.
*/
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context layout:(ASLayout *)layout;
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context
layout:(ASLayout *)layout
getElementBlock:(ASCollectionElement *(^)(ASLayout *))getElementBlock;
/**
* Returns all layout attributes present in this object.
@ -88,7 +95,8 @@ AS_SUBCLASSING_RESTRICTED
*
* @param indexPath The index path of the element.
*/
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath;
/**
* Returns layout attributes of the specified element.

View File

@ -1,135 +0,0 @@
//
// ASCollectionLayoutState.m
// Texture
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASCellNode+Internal.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASPageTable.h>
@implementation NSMapTable (ASCollectionLayoutConvenience)
+ (NSMapTable<ASCollectionElement *,UICollectionViewLayoutAttributes *> *)elementToLayoutAttributesTable
{
return [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory];
}
@end
@implementation ASCollectionLayoutState {
NSMapTable<ASCollectionElement *,UICollectionViewLayoutAttributes *> *_elementToLayoutAttributesTable;
ASPageTable<id, NSMutableArray<UICollectionViewLayoutAttributes *> *> *_pageToLayoutAttributesTable;
}
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context layout:(ASLayout *)layout
{
ASElementMap *elements = context.elements;
NSMapTable *table = [NSMapTable elementToLayoutAttributesTable];
for (ASLayout *sublayout in layout.sublayouts) {
ASCollectionElement *element = ((ASCellNode *)sublayout.layoutElement).collectionElement;
if (element == nil) {
ASDisplayNodeFailAssert(@"Element not found!");
continue;
}
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;
[table setObject:attrs forKey:element];
}
return [self initWithContext:context contentSize:layout.size elementToLayoutAttributesTable:table];
}
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context contentSize:(CGSize)contentSize elementToLayoutAttributesTable:(NSMapTable<ASCollectionElement *,UICollectionViewLayoutAttributes *> *)table
{
self = [super init];
if (self) {
_context = context;
_contentSize = contentSize;
_elementToLayoutAttributesTable = table;
_pageToLayoutAttributesTable = [ASPageTable pageTableWithLayoutAttributes:_elementToLayoutAttributesTable.objectEnumerator contentSize:contentSize pageSize:context.viewportSize];
}
return self;
}
- (NSArray<UICollectionViewLayoutAttributes *> *)allLayoutAttributes
{
return [_elementToLayoutAttributesTable.objectEnumerator allObjects];
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
CGSize pageSize = _context.viewportSize;
NSPointerArray *pages = ASPageCoordinatesForPagesThatIntersectRect(rect, _contentSize, pageSize);
if (pages.count == 0) {
return @[];
}
// Use a mutable set here because some items may span multiple pages
NSMutableSet<UICollectionViewLayoutAttributes *> *result = [NSMutableSet set];
for (id pagePtr in pages) {
ASPageCoordinate page = (ASPageCoordinate)pagePtr;
NSArray<UICollectionViewLayoutAttributes *> *allAttrs = [_pageToLayoutAttributesTable objectForPage:page];
if (allAttrs.count > 0) {
CGRect pageRect = ASPageCoordinateGetPageRect(page, pageSize);
if (CGRectContainsRect(rect, pageRect)) {
[result addObjectsFromArray:allAttrs];
} else {
for (UICollectionViewLayoutAttributes *attrs in allAttrs) {
if (CGRectIntersectsRect(rect, attrs.frame)) {
[result addObject:attrs];
}
}
}
}
}
return [result allObjects];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
ASCollectionElement *element = [_context.elements elementForItemAtIndexPath:indexPath];
return [_elementToLayoutAttributesTable objectForKey:element];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
ASCollectionElement *element = [_context.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath];
return [_elementToLayoutAttributesTable objectForKey:element];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForElement:(ASCollectionElement *)element
{
return [_elementToLayoutAttributesTable objectForKey:element];
}
@end

View File

@ -0,0 +1,217 @@
//
// ASCollectionLayoutState.mm
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASCollectionLayoutState+Private.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASPageTable.h>
#import <AsyncDisplayKit/ASThread.h>
@implementation NSMapTable (ASCollectionLayoutConvenience)
+ (NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)elementToLayoutAttributesTable
{
return [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory];
}
@end
@implementation ASCollectionLayoutState {
ASDN::Mutex __instanceLock__;
NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *_elementToLayoutAttributesTable;
ASPageToLayoutAttributesTable *_pageToLayoutAttributesTable;
ASPageToLayoutAttributesTable *_unmeasuredPageToLayoutAttributesTable;
}
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context
layout:(ASLayout *)layout
getElementBlock:(ASCollectionElement *(^)(ASLayout *))getElementBlock
{
ASElementMap *elements = context.elements;
NSMapTable *table = [NSMapTable elementToLayoutAttributesTable];
for (ASLayout *sublayout in layout.sublayouts) {
ASCollectionElement *element = getElementBlock(sublayout);
if (element == nil) {
ASDisplayNodeFailAssert(@"Element not found!");
continue;
}
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;
[table setObject:attrs forKey:element];
}
return [self initWithContext:context contentSize:layout.size elementToLayoutAttributesTable:table];
}
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context
contentSize:(CGSize)contentSize
elementToLayoutAttributesTable:(NSMapTable *)table
{
self = [super init];
if (self) {
_context = context;
_contentSize = contentSize;
_elementToLayoutAttributesTable = [table copy]; // Copy the given table to make sure clients can't mutate it after this point.
CGSize pageSize = context.viewportSize;
_pageToLayoutAttributesTable = [ASPageTable pageTableWithLayoutAttributes:table.objectEnumerator contentSize:contentSize pageSize:pageSize];
_unmeasuredPageToLayoutAttributesTable = [ASCollectionLayoutState _unmeasuredLayoutAttributesTableFromTable:table contentSize:contentSize pageSize:pageSize];
}
return self;
}
- (NSArray<UICollectionViewLayoutAttributes *> *)allLayoutAttributes
{
return [_elementToLayoutAttributesTable.objectEnumerator allObjects];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
ASCollectionElement *element = [_context.elements elementForItemAtIndexPath:indexPath];
return [_elementToLayoutAttributesTable objectForKey:element];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)elementKind
atIndexPath:(NSIndexPath *)indexPath
{
ASCollectionElement *element = [_context.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath];
return [_elementToLayoutAttributesTable objectForKey:element];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForElement:(ASCollectionElement *)element
{
return [_elementToLayoutAttributesTable objectForKey:element];
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
CGSize pageSize = _context.viewportSize;
NSPointerArray *pages = ASPageCoordinatesForPagesThatIntersectRect(rect, _contentSize, pageSize);
if (pages.count == 0) {
return @[];
}
// Use a set here because some items may span multiple pages
NSMutableSet<UICollectionViewLayoutAttributes *> *result = [NSMutableSet set];
for (id pagePtr in pages) {
ASPageCoordinate page = (ASPageCoordinate)pagePtr;
NSArray<UICollectionViewLayoutAttributes *> *allAttrs = [_pageToLayoutAttributesTable objectForPage:page];
if (allAttrs.count > 0) {
CGRect pageRect = ASPageCoordinateGetPageRect(page, pageSize);
if (CGRectContainsRect(rect, pageRect)) {
[result addObjectsFromArray:allAttrs];
} else {
for (UICollectionViewLayoutAttributes *attrs in allAttrs) {
if (CGRectIntersectsRect(rect, attrs.frame)) {
[result addObject:attrs];
}
}
}
}
}
return [result allObjects];
}
- (ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect
contentSize:(CGSize)contentSize
pageSize:(CGSize)pageSize
{
ASDN::MutexLocker l(__instanceLock__);
if (_unmeasuredPageToLayoutAttributesTable.count == 0 || CGRectIsNull(rect) || CGRectIsEmpty(rect) || CGSizeEqualToSize(CGSizeZero, contentSize) || CGSizeEqualToSize(CGSizeZero, pageSize)) {
return nil;
}
// Step 1: Determine all the pages that intersect the specified rect
NSPointerArray *pagesInRect = ASPageCoordinatesForPagesThatIntersectRect(rect, contentSize, pageSize);
if (pagesInRect.count == 0) {
return nil;
}
// Step 2: Filter out attributes in these pages that intersect the specified rect.
ASPageToLayoutAttributesTable *result = nil;
for (id pagePtr in pagesInRect) {
ASPageCoordinate page = (ASPageCoordinate)pagePtr;
NSMutableArray *attrsInPage = [_unmeasuredPageToLayoutAttributesTable objectForPage:page];
if (attrsInPage.count == 0) {
continue;
}
NSMutableArray *intersectingAttrsInPage = nil;
CGRect pageRect = ASPageCoordinateGetPageRect(page, pageSize);
if (CGRectContainsRect(rect, pageRect)) {
// This page fits well within the specified rect. Simply return all of its attributes.
intersectingAttrsInPage = attrsInPage;
} else {
// The page intersects the specified rect. Some attributes in this page are returned, some are not.
for (UICollectionViewLayoutAttributes *attrs in attrsInPage) {
if (CGRectIntersectsRect(rect, attrs.frame)) {
if (intersectingAttrsInPage == nil) {
intersectingAttrsInPage = [NSMutableArray array];
}
[intersectingAttrsInPage addObject:attrs];
}
}
}
if (intersectingAttrsInPage.count > 0) {
if (attrsInPage.count == intersectingAttrsInPage.count) {
[_unmeasuredPageToLayoutAttributesTable removeObjectForPage:page];
} else {
[attrsInPage removeObjectsInArray:intersectingAttrsInPage];
}
if (result == nil) {
result = [ASPageTable pageTableForStrongObjectPointers];
}
[result setObject:intersectingAttrsInPage forPage:page];
}
}
return result;
}
#pragma mark - Private methods
+ (ASPageToLayoutAttributesTable *)_unmeasuredLayoutAttributesTableFromTable:(NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)table
contentSize:(CGSize)contentSize
pageSize:(CGSize)pageSize
{
NSMutableArray<UICollectionViewLayoutAttributes *> *unmeasuredAttrs = [NSMutableArray array];
for (ASCollectionElement *element in table) {
UICollectionViewLayoutAttributes *attrs = [table objectForKey:element];
if (element.nodeIfAllocated == nil || CGSizeEqualToSize(element.nodeIfAllocated.calculatedSize, attrs.frame.size) == NO) {
[unmeasuredAttrs addObject:attrs];
}
}
return [ASPageTable pageTableWithLayoutAttributes:unmeasuredAttrs contentSize:contentSize pageSize:pageSize];
}
@end

View File

@ -35,6 +35,8 @@ NS_ASSUME_NONNULL_BEGIN
@class ASCellNode;
@class ASCollectionElement;
@class ASCollectionLayoutContext;
@class ASCollectionLayoutState;
@class ASDataController;
@class ASElementMap;
@class ASLayout;
@ -136,22 +138,22 @@ extern NSString * const ASCollectionInvalidUpdateException;
*
* @discussion This method will be called on main thread.
*/
- (id)layoutContextWithElements:(ASElementMap *)elements;
- (ASCollectionLayoutContext *)layoutContextWithElements:(ASElementMap *)elements;
/**
* @abstract Prepares in advance a new layout with the given context.
* @abstract Prepares and returns a new layout for given context.
*
* @param context A context that was previously returned by `-layoutContextWithElements:`.
*
* @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 its collection/table view for information regarding items.
* As a result, clients must solely rely 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 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.
* 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 must block the calling thread but can dispatch to other theads to reduce total blocking time.
*/
- (void)prepareLayoutWithContext:(id)context;
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context;
@end

View File

@ -54,7 +54,7 @@ const static char * kASDataControllerEditingQueueContext = "kASDataControllerEdi
NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdateException";
typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *elements, NSArray<ASCellNode *> *nodes);
typedef dispatch_block_t ASDataControllerCompletionBlock;
@interface ASDataController () {
id<ASDataControllerLayoutDelegate> _layoutDelegate;
@ -151,12 +151,12 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
#pragma mark - Cell Layout
- (void)batchAllocateNodesFromElements:(NSArray<ASCollectionElement *> *)elements andLayout:(BOOL)shouldLayout batchSize:(NSInteger)batchSize batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler
- (void)batchAllocateNodesFromElements:(NSArray<ASCollectionElement *> *)elements batchSize:(NSInteger)batchSize batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler
{
ASSERT_ON_EDITING_QUEUE;
if (elements.count == 0 || _dataSource == nil) {
batchCompletionHandler(@[], @[]);
batchCompletionHandler();
return;
}
@ -171,12 +171,11 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
for (NSUInteger i = 0; i < count; i += batchSize) {
NSRange batchedRange = NSMakeRange(i, MIN(count - i, batchSize));
NSArray<ASCollectionElement *> *batchedElements = [elements subarrayWithRange:batchedRange];
NSArray<ASCellNode *> *nodes;
{
as_activity_create_for_scope("Data controller batch");
nodes = [self _allocateNodesFromElements:batchedElements andLayout:shouldLayout];
[self _allocateNodesFromElements:batchedElements];
}
batchCompletionHandler(batchedElements, nodes);
batchCompletionHandler();
}
ASSignpostEndCustom(ASSignpostDataControllerBatch, self, 0, (_dataSource != nil ? ASSignpostColorDefault : ASSignpostColorRed));
@ -195,17 +194,15 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
}
// TODO Is returned array still needed? Can it be removed?
- (NSArray<ASCellNode *> *)_allocateNodesFromElements:(NSArray<ASCollectionElement *> *)elements andLayout:(BOOL)shouldLayout
- (void)_allocateNodesFromElements:(NSArray<ASCollectionElement *> *)elements
{
ASSERT_ON_EDITING_QUEUE;
NSUInteger nodeCount = elements.count;
if (!nodeCount || _dataSource == nil) {
return @[];
return;
}
__strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *));
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
ASDispatchApply(nodeCount, queue, 0, ^(size_t i) {
RETURN_IF_NO_DATASOURCE();
@ -218,29 +215,12 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps.
}
if (shouldLayout) {
// Layout the node if the size range is valid.
ASSizeRange sizeRange = context.constrainedSize;
if (ASSizeRangeHasSignificantArea(sizeRange)) {
[self _layoutNode:node withConstrainedSize:sizeRange];
}
// Layout the node if the size range is valid.
ASSizeRange sizeRange = context.constrainedSize;
if (ASSizeRangeHasSignificantArea(sizeRange)) {
[self _layoutNode:node withConstrainedSize:sizeRange];
}
allocatedNodeBuffer[i] = node;
});
BOOL canceled = _dataSource == nil;
// Create nodes array
NSArray *nodes = canceled ? nil : [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount];
// Nil out buffer indexes to allow arc to free the stored cells.
for (int i = 0; i < nodeCount; i++) {
allocatedNodeBuffer[i] = nil;
}
free(allocatedNodeBuffer);
return nodes;
}
#pragma mark - Data Source Access (Calling _dataSource)
@ -550,8 +530,8 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
@throw e;
}
}
BOOL canDelegateLayout = (_layoutDelegate != nil);
BOOL canDelegate = (self.layoutDelegate != nil);
ASElementMap *newMap;
id layoutContext;
{
@ -569,7 +549,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
// Step 1.1: Update the mutable copies to match the data source's state
[self _updateSectionContextsInMap:mutableMap changeSet:changeSet];
ASPrimitiveTraitCollection existingTraitCollection = [self.node primitiveTraitCollection];
[self _updateElementsInMap:mutableMap changeSet:changeSet traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegateLayout) previousMap:previousMap];
[self _updateElementsInMap:mutableMap changeSet:changeSet traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegate) previousMap:previousMap];
// Step 1.2: Clone the new data
newMap = [mutableMap copy];
@ -577,37 +557,19 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
self.pendingMap = newMap;
// Step 2: Ask layout delegate for contexts
if (canDelegateLayout) {
layoutContext = [_layoutDelegate layoutContextWithElements:newMap];
if (canDelegate) {
layoutContext = [self.layoutDelegate layoutContextWithElements:newMap];
}
}
as_log_debug(ASCollectionLog(), "New content: %@", newMap.smallDescription);
Class<ASDataControllerLayoutDelegate> layoutDelegateClass = [self.layoutDelegate class];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
__block __unused os_activity_scope_state_s preparationScope = {}; // unused if deployment target < iOS10
as_activity_scope_enter(as_activity_create("Prepare nodes for collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT), &preparationScope);
// Step 3: Allocate and layout elements if can't delegate
NSArray<ASCollectionElement *> *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<ASCollectionElement *> *elements, NSArray<ASCellNode *> *nodes) {
ASSERT_ON_EDITING_QUEUE;
if (canDelegateLayout) {
[_layoutDelegate prepareLayoutWithContext:layoutContext];
}
dispatch_block_t completion = ^() {
[_mainSerialQueue performBlockOnMainThread:^{
as_activity_scope_leave(&preparationScope);
// TODO Merge the two delegate methods below
@ -625,7 +587,18 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
self.visibleMap = newMap;
}];
}];
}];
};
// Step 3: Call the layout delegate if possible. Otherwise, allocate and layout all elements
if (canDelegate) {
[layoutDelegateClass calculateLayoutWithContext:layoutContext];
completion();
} else {
NSArray<ASCollectionElement *> *elementsToProcess = ASArrayByFlatMapping(newMap,
ASCollectionElement *element,
(element.nodeIfAllocated.calculatedLayout == nil ? element : nil));
[self batchAllocateNodesFromElements:elementsToProcess batchSize:elementsToProcess.count batchCompletion:completion];
}
});
if (_usesSynchronousDataLoading) {
@ -837,7 +810,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
// 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 = [self.node primitiveTraitCollection];
for (ASCollectionElement *element in _visibleMap) {

View File

@ -69,6 +69,11 @@ ASDISPLAYNODE_EXTERN_C_END
*/
typedef NSMapTable ASPageTable;
/**
* A page to array of layout attributes table.
*/
typedef ASPageTable<id, NSMutableArray<UICollectionViewLayoutAttributes *> *> ASPageToLayoutAttributesTable;
/**
* A category for creating & using map tables meant for storing objects using ASPage as keys.
*/
@ -93,7 +98,7 @@ typedef NSMapTable ASPageTable;
*
* @param pageSize The size of each page.
*/
+ (ASPageTable<id, NSMutableArray<UICollectionViewLayoutAttributes *> *> *)pageTableWithLayoutAttributes:(id<NSFastEnumeration>)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize;
+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id<NSFastEnumeration>)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize;
/**
* Retrieves the object for a given page, or nil if the page is not found.

View File

@ -110,9 +110,9 @@ extern NSPointerArray *ASPageCoordinatesForPagesThatIntersectRect(CGRect rect, C
return [self pageTableWithValuePointerFunctions:weakObjectPointerFuncs];
}
+ (ASPageTable<id, NSMutableArray<UICollectionViewLayoutAttributes *> *> *)pageTableWithLayoutAttributes:(id<NSFastEnumeration>)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize
+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id<NSFastEnumeration>)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize
{
ASPageTable *result = [ASPageTable pageTableForStrongObjectPointers];
ASPageToLayoutAttributesTable *result = [ASPageTable pageTableForStrongObjectPointers];
for (UICollectionViewLayoutAttributes *attrs in layoutAttributesEnumerator) {
// This attrs may span multiple pages. Make sure it's registered to all of them
NSPointerArray *pages = ASPageCoordinatesForPagesThatIntersectRect(attrs.frame, contentSize, pageSize);

View File

@ -2,13 +2,8 @@
// ASCollectionLayout.h
// Texture
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
@ -25,7 +20,6 @@
NS_ASSUME_NONNULL_BEGIN
AS_SUBCLASSING_RESTRICTED
@interface ASCollectionLayout : UICollectionViewLayout
/**

View File

@ -18,27 +18,34 @@
#import <AsyncDisplayKit/ASCollectionLayout.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASCollectionLayoutCache.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext+Private.h>
#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASCollectionLayoutState+Private.h>
#import <AsyncDisplayKit/ASCollectionNode+Beta.h>
#import <AsyncDisplayKit/ASDispatch.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASPageTable.h>
static const ASRangeTuningParameters kASDefaultMeasureRangeTuningParameters = {
.leadingBufferScreenfuls = 2.0,
.trailingBufferScreenfuls = 2.0
};
static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRight | ASScrollDirectionDown);
@interface ASCollectionLayout () <ASDataControllerLayoutDelegate> {
ASDN::Mutex __instanceLock__; // Non-recursive mutex, ftw!
// Main thread only.
ASCollectionLayoutState *_layout;
// The pending state calculated ahead of time, if any.
ASCollectionLayoutState *_pendingLayout;
BOOL _layoutDelegateImplementsAdditionalInfoForLayoutWithElements;
ASCollectionLayoutCache *_layoutCache;
ASCollectionLayoutState *_layout; // Main thread only.
struct {
unsigned int implementsAdditionalInfoForLayoutWithElements:1;
} _layoutDelegateFlags;
}
@end
@ -51,30 +58,53 @@
if (self) {
ASDisplayNodeAssertNotNil(layoutDelegate, @"Collection layout delegate cannot be nil");
_layoutDelegate = layoutDelegate;
_layoutDelegateImplementsAdditionalInfoForLayoutWithElements = [layoutDelegate respondsToSelector:@selector(additionalInfoForLayoutWithElements:)];
_layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements = [layoutDelegate respondsToSelector:@selector(additionalInfoForLayoutWithElements:)];
_layoutCache = [[ASCollectionLayoutCache alloc] init];
}
return self;
}
#pragma mark - ASDataControllerLayoutDelegate
- (id)layoutContextWithElements:(ASElementMap *)elements
- (ASCollectionLayoutContext *)layoutContextWithElements:(ASElementMap *)elements
{
ASDisplayNodeAssertMainThread();
CGSize viewportSize = [self viewportSize];
CGSize viewportSize = [self _viewportSize];
id additionalInfo = nil;
if (_layoutDelegateImplementsAdditionalInfoForLayoutWithElements) {
if (_layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements) {
additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements];
}
return [[ASCollectionLayoutContext alloc] initWithViewportSize:viewportSize elements:elements additionalInfo:additionalInfo];
return [[ASCollectionLayoutContext alloc] initWithViewportSize:viewportSize
scrollableDirections:[_layoutDelegate scrollableDirections]
elements:elements
layoutDelegateClass:[_layoutDelegate class]
layoutCache:_layoutCache
additionalInfo:additionalInfo];
}
- (void)prepareLayoutWithContext:(id)context
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
ASCollectionLayoutState *layout = [_layoutDelegate calculateLayoutWithContext:context];
ASDN::MutexLocker l(__instanceLock__);
_pendingLayout = layout;
if (context.elements == nil) {
return [[ASCollectionLayoutState alloc] initWithContext:context
contentSize:CGSizeZero
elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]];
}
ASDisplayNodeAssertTrue([context.layoutDelegateClass conformsToProtocol:@protocol(ASCollectionLayoutDelegate)]);
ASCollectionLayoutState *layout = [context.layoutDelegateClass calculateLayoutWithContext:context];
[context.layoutCache setLayout:layout forContext:context];
// Measure elements in the measure range ahead of time, block on the initial rect as it'll be visible shortly
CGSize viewportSize = context.viewportSize;
// TODO Consider content offset of the collection node
CGRect initialRect = CGRectMake(0, 0, viewportSize.width, viewportSize.height);
CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(initialRect,
kASDefaultMeasureRangeTuningParameters,
context.scrollableDirections,
kASStaticScrollDirection);
[self _measureElementsInRect:measureRect blockingRect:initialRect layout:layout];
return layout;
}
#pragma mark - UICollectionViewLayout overrides
@ -83,30 +113,29 @@
{
ASDisplayNodeAssertMainThread();
[super prepareLayout];
ASCollectionLayoutContext *context = [self layoutContextWithElements:_collectionNode.visibleElements];
ASCollectionLayoutState *layout = nil;
{
ASDN::MutexLocker l(__instanceLock__);
if (_pendingLayout != nil && ASObjectIsEqual(_pendingLayout.context, context)) {
// Looks like we can use the pending layout. Great!
layout = _pendingLayout;
_pendingLayout = nil;
}
if (_layout != nil && ASObjectIsEqual(_layout.context, context)) {
// The existing layout is still valid. No-op
return;
}
if (layout == nil) {
layout = [_layoutDelegate calculateLayoutWithContext:context];
if (ASCollectionLayoutState *cachedLayout = [_layoutCache layoutForContext:context]) {
_layout = cachedLayout;
} else {
// A new layout is needed now. Calculate and apply it immediately
_layout = [ASCollectionLayout calculateLayoutWithContext:context];
}
_layout = layout;
}
- (void)invalidateLayout
{
ASDisplayNodeAssertMainThread();
[super invalidateLayout];
_layout = nil;
if (_layout != nil) {
[_layoutCache removeLayoutForContext:_layout.context];
_layout = nil;
}
}
- (CGSize)collectionViewContentSize
@ -116,25 +145,45 @@
return _layout.contentSize;
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)blockingRect
{
ASDisplayNodeAssertMainThread();
NSArray<UICollectionViewLayoutAttributes *> *result = [_layout layoutAttributesForElementsInRect:rect];
if (CGRectIsEmpty(blockingRect)) {
return nil;
}
// Measure elements in the measure range, block on the requested rect
CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(blockingRect,
kASDefaultMeasureRangeTuningParameters,
_layout.context.scrollableDirections,
kASStaticScrollDirection);
[ASCollectionLayout _measureElementsInRect:measureRect blockingRect:blockingRect layout:_layout];
NSArray<UICollectionViewLayoutAttributes *> *result = [_layout layoutAttributesForElementsInRect:blockingRect];
ASElementMap *elements = _layout.context.elements;
for (UICollectionViewLayoutAttributes *attrs in result) {
ASCollectionElement *element = [elements elementForLayoutAttributes:attrs];
[ASCollectionLayout setSize:attrs.frame.size toElement:element];
ASCollectionLayoutSetSizeToElement(attrs.frame.size, element);
}
return result;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssertMainThread();
ASCollectionElement *element = [_layout.context.elements elementForItemAtIndexPath:indexPath];
UICollectionViewLayoutAttributes *attrs = [_layout layoutAttributesForElement:element];
[ASCollectionLayout setSize:attrs.frame.size toElement:element];
ASCellNode *node = element.node;
CGSize elementSize = attrs.frame.size;
if (! CGSizeEqualToSize(elementSize, node.calculatedSize)) {
[node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(elementSize)];
}
ASCollectionLayoutSetSizeToElement(attrs.frame.size, element);
return attrs;
}
@ -142,28 +191,25 @@
{
ASCollectionElement *element = [_layout.context.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath];
UICollectionViewLayoutAttributes *attrs = [_layout layoutAttributesForElement:element];
[ASCollectionLayout setSize:attrs.frame.size toElement:element];
ASCellNode *node = element.node;
CGSize elementSize = attrs.frame.size;
if (! CGSizeEqualToSize(elementSize, node.calculatedSize)) {
[node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(elementSize)];
}
ASCollectionLayoutSetSizeToElement(attrs.frame.size, element);
return attrs;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return (! CGSizeEqualToSize([self viewportSize], newBounds.size));
return (! CGSizeEqualToSize([self _viewportSize], newBounds.size));
}
#pragma mark - Private methods
+ (void)setSize:(CGSize)size toElement:(ASCollectionElement *)element
{
ASCellNode *node = element.node;
if (! CGSizeEqualToSize(size, node.frame.size)) {
CGRect nodeFrame = CGRectZero;
nodeFrame.size = size;
node.frame = nodeFrame;
}
}
- (CGSize)viewportSize
- (CGSize)_viewportSize
{
ASCollectionNode *collectionNode = _collectionNode;
if (collectionNode != nil && !collectionNode.isNodeLoaded) {
@ -175,4 +221,120 @@
}
}
/**
* Measures all elements in the specified rect and blocks the calling thread while measuring those in the blocking rect.
*/
+ (void)_measureElementsInRect:(CGRect)rect blockingRect:(CGRect)blockingRect layout:(ASCollectionLayoutState *)layout
{
if (CGRectIsEmpty(rect) || layout.context.elements == nil) {
return;
}
BOOL hasBlockingRect = !CGRectIsEmpty(blockingRect);
if (hasBlockingRect && CGRectContainsRect(rect, blockingRect) == NO) {
ASDisplayNodeCAssert(NO, @"Blocking rect, if specified, must be within the other (outer) rect");
return;
}
// Step 1: Clamp the specified rects between the bounds of content rect
CGSize contentSize = layout.contentSize;
CGRect contentRect = CGRectMake(0, 0, contentSize.width, contentSize.height);
rect = CGRectIntersection(contentRect, rect);
if (CGRectIsNull(rect)) {
return;
}
if (hasBlockingRect) {
blockingRect = CGRectIntersection(contentRect, blockingRect);
hasBlockingRect = !CGRectIsNull(blockingRect);
}
// Step 2: Get layout attributes of all elements within the specified outer rect
ASCollectionLayoutContext *context = layout.context;
CGSize pageSize = context.viewportSize;
ASPageToLayoutAttributesTable *attrsTable = [layout getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:rect
contentSize:contentSize
pageSize:pageSize];
if (attrsTable.count == 0) {
// No elements in this rect! Bail early
return;
}
// Step 3: Split all those attributes into blocking and non-blocking buckets
// Use ordered sets here because some items may span multiple pages, and the sets will be accessed by indexes later on.
NSMutableOrderedSet<UICollectionViewLayoutAttributes *> *blockingAttrs = hasBlockingRect ? [NSMutableOrderedSet orderedSet] : nil;
NSMutableOrderedSet<UICollectionViewLayoutAttributes *> *nonBlockingAttrs = [NSMutableOrderedSet orderedSet];
for (id pagePtr in attrsTable) {
ASPageCoordinate page = (ASPageCoordinate)pagePtr;
NSArray<UICollectionViewLayoutAttributes *> *attrsInPage = [attrsTable objectForPage:page];
// Calculate the page's rect but only if it's going to be used.
CGRect pageRect = hasBlockingRect ? ASPageCoordinateGetPageRect(page, pageSize) : CGRectZero;
if (hasBlockingRect && CGRectContainsRect(blockingRect, pageRect)) {
// The page fits well within the blocking rect. All attributes in this page are blocking.
[blockingAttrs addObjectsFromArray:attrsInPage];
} else if (hasBlockingRect && CGRectIntersectsRect(blockingRect, pageRect)) {
// The page intersects the blocking rect. Some elements in this page are blocking, some are not.
for (UICollectionViewLayoutAttributes *attrs in attrsInPage) {
if (CGRectIntersectsRect(blockingRect, attrs.frame)) {
[blockingAttrs addObject:attrs];
} else {
[nonBlockingAttrs addObject:attrs];
}
}
} else {
// The page doesn't intersect the blocking rect. All elements in this page are non-blocking.
[nonBlockingAttrs addObjectsFromArray:attrsInPage];
}
}
// Step 4: Allocate and measure blocking elements' node
ASElementMap *elements = context.elements;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
if (NSUInteger count = blockingAttrs.count) {
ASDispatchApply(count, queue, 0, ^(size_t i) {
UICollectionViewLayoutAttributes *attrs = blockingAttrs[i];
ASCellNode *node = [elements elementForItemAtIndexPath:attrs.indexPath].node;
CGSize expectedSize = attrs.frame.size;
if (! CGSizeEqualToSize(expectedSize, node.calculatedSize)) {
[node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(expectedSize)];
}
});
}
// Step 5: Allocate and measure non-blocking ones
if (NSUInteger count = nonBlockingAttrs.count) {
__weak ASElementMap *weakElements = elements;
ASDispatchAsync(count, queue, 0, ^(size_t i) {
__strong ASElementMap *strongElements = weakElements;
if (strongElements) {
UICollectionViewLayoutAttributes *attrs = nonBlockingAttrs[i];
ASCellNode *node = [elements elementForItemAtIndexPath:attrs.indexPath].node;
CGSize expectedSize = attrs.frame.size;
if (! CGSizeEqualToSize(expectedSize, node.calculatedSize)) {
[node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(expectedSize)];
}
}
});
}
}
# pragma mark - Convenient inline functions
ASDISPLAYNODE_INLINE ASSizeRange ASCollectionLayoutElementSizeRangeFromSize(CGSize size)
{
// The layout delegate consulted us that this element must fit within this size,
// and the only way to achieve that without asking it again is to use an exact size range here.
return ASSizeRangeMake(size);
}
ASDISPLAYNODE_INLINE void ASCollectionLayoutSetSizeToElement(CGSize size, ASCollectionElement *element)
{
if (ASCellNode *node = element.node) {
if (! CGSizeEqualToSize(size, node.frame.size)) {
CGRect frame = CGRectZero;
frame.size = size;
node.frame = frame;
}
}
}
@end

View File

@ -0,0 +1,34 @@
//
// ASCollectionLayoutCache.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
NS_ASSUME_NONNULL_BEGIN
@class ASCollectionLayoutContext, ASCollectionLayoutState;
/// A thread-safe cache for ASCollectionLayoutContext-ASCollectionLayoutState pairs
AS_SUBCLASSING_RESTRICTED
@interface ASCollectionLayoutCache : NSObject
- (nullable ASCollectionLayoutState *)layoutForContext:(ASCollectionLayoutContext *)context;
- (void)setLayout:(ASCollectionLayoutState *)layout forContext:(ASCollectionLayoutContext *)context;
- (void)removeLayoutForContext:(ASCollectionLayoutContext *)context;
- (void)removeAllLayouts;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,90 @@
//
// ASCollectionLayoutCache.mm
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollectionLayoutCache.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASThread.h>
@implementation ASCollectionLayoutCache {
ASDN::Mutex __instanceLock__;
/**
* The underlying data structure of this cache.
*
* The outer map table is a weak to strong table. That is because ASCollectionLayoutContext doesn't (and shouldn't)
* hold a strong reference on its element map. As a result, this cache should handle the case in which
* an element map no longer exists and all contexts and layouts associated with it should be cleared.
*
* The inner map table is a standard strong to strong map.
* Since different ASCollectionLayoutContext objects with the same content are considered equal,
* "object pointer personality" can't be used as a key option.
*/
NSMapTable<ASElementMap *, NSMapTable<ASCollectionLayoutContext *, ASCollectionLayoutState *> *> *_map;
}
- (instancetype)init
{
self = [super init];
if (self) {
_map = [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory];
}
return self;
}
- (ASCollectionLayoutState *)layoutForContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
if (elements == nil) {
return nil;
}
ASDN::MutexLocker l(__instanceLock__);
return [[_map objectForKey:elements] objectForKey:context];
}
- (void)setLayout:(ASCollectionLayoutState *)layout forContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
if (layout == nil || elements == nil) {
return;
}
ASDN::MutexLocker l(__instanceLock__);
auto innerMap = [_map objectForKey:elements];
if (innerMap == nil) {
innerMap = [NSMapTable strongToStrongObjectsMapTable];
[_map setObject:innerMap forKey:elements];
}
[innerMap setObject:layout forKey:context];
}
- (void)removeLayoutForContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
if (elements == nil) {
return;
}
ASDN::MutexLocker l(__instanceLock__);
[[_map objectForKey:elements] removeObjectForKey:context];
}
- (void)removeAllLayouts
{
ASDN::MutexLocker l(__instanceLock__);
[_map removeAllObjects];
}
@end

View File

@ -17,11 +17,22 @@
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
@class ASCollectionLayoutCache;
@protocol ASCollectionLayoutDelegate;
NS_ASSUME_NONNULL_BEGIN
@interface ASCollectionLayoutContext (Private)
- (instancetype)initWithViewportSize:(CGSize)viewportSize elements:(ASElementMap *)elements additionalInfo:(nullable id)additionalInfo;
@property (nonatomic, strong, readonly) Class<ASCollectionLayoutDelegate> layoutDelegateClass;
@property (nonatomic, weak, readonly) ASCollectionLayoutCache *layoutCache;
- (instancetype)initWithViewportSize:(CGSize)viewportSize
scrollableDirections:(ASScrollDirection)scrollableDirections
elements:(ASElementMap *)elements
layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass
layoutCache:(ASCollectionLayoutCache *)layoutCache
additionalInfo:(nullable id)additionalInfo;
@end

View File

@ -0,0 +1,27 @@
//
// ASCollectionLayoutDefines.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASScrollDirection.h>
NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN
FOUNDATION_EXPORT ASSizeRange ASSizeRangeForCollectionLayoutThatFitsViewportSize(CGSize viewportSize, ASScrollDirection scrollableDirections) AS_WARN_UNUSED_RESULT;
ASDISPLAYNODE_EXTERN_C_END
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,27 @@
//
// ASCollectionLayoutDefines.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollectionLayoutDefines.h>
extern ASSizeRange ASSizeRangeForCollectionLayoutThatFitsViewportSize(CGSize viewportSize, ASScrollDirection scrollableDirections)
{
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;
}

View File

@ -0,0 +1,31 @@
//
// ASCollectionLayoutState+Private.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASPageTable.h>
NS_ASSUME_NONNULL_BEGIN
@interface ASCollectionLayoutState (Private)
/**
* Remove and returns layout attributes for unmeasured elements that intersect the specified rect
*
* @discussion This method is atomic and thread-safe
*/
- (nullable ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect
contentSize:(CGSize)contentSize
pageSize:(CGSize)pageSize;
@end
NS_ASSUME_NONNULL_END

View File

@ -16,7 +16,9 @@
//
#import <Foundation/Foundation.h>
#import <stdatomic.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
ASDISPLAYNODE_EXTERN_C_BEGIN
/**
* Like dispatch_apply, but you can set the thread count. 0 means 2*active CPUs.
@ -24,24 +26,14 @@
* Note: The actual number of threads may be lower than threadCount, if libdispatch
* decides the system can't handle it. In reality this rarely happens.
*/
static void ASDispatchApply(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)) {
if (threadCount == 0) {
threadCount = NSProcessInfo.processInfo.activeProcessorCount * 2;
}
dispatch_group_t group = dispatch_group_create();
// HACK: This is a workaround for mm files that include this in Clang4.0
// Omitting ATOMIC_VAR_INIT is okay in this case because the current
// expansion of that macro no-ops.
// TODO: Move this implementation into a m file so it's not compiled in C++
// See: https://github.com/TextureGroup/Texture/pull/426
__block atomic_size_t counter = 0;
for (NSUInteger t = 0; t < threadCount; t++) {
dispatch_group_async(group, queue, ^{
size_t i;
while ((i = atomic_fetch_add(&counter, 1)) < iterationCount) {
work(i);
}
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
};
void ASDispatchApply(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i));
/**
* Like dispatch_async, but you can set the thread count. 0 means 2*active CPUs.
*
* Note: The actual number of threads may be lower than threadCount, if libdispatch
* decides the system can't handle it. In reality this rarely happens.
*/
void ASDispatchAsync(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i));
ASDISPLAYNODE_EXTERN_C_END

View File

@ -0,0 +1,59 @@
//
// ASDispatch.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASDispatch.h>
#import <stdatomic.h>
/**
* Like dispatch_apply, but you can set the thread count. 0 means 2*active CPUs.
*
* Note: The actual number of threads may be lower than threadCount, if libdispatch
* decides the system can't handle it. In reality this rarely happens.
*/
void ASDispatchApply(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)) {
if (threadCount == 0) {
threadCount = NSProcessInfo.processInfo.activeProcessorCount * 2;
}
dispatch_group_t group = dispatch_group_create();
__block atomic_size_t counter = ATOMIC_VAR_INIT(0);
for (NSUInteger t = 0; t < threadCount; t++) {
dispatch_group_async(group, queue, ^{
size_t i;
while ((i = atomic_fetch_add(&counter, 1)) < iterationCount) {
work(i);
}
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
};
/**
* Like dispatch_async, but you can set the thread count. 0 means 2*active CPUs.
*
* Note: The actual number of threads may be lower than threadCount, if libdispatch
* decides the system can't handle it. In reality this rarely happens.
*/
void ASDispatchAsync(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)) {
if (threadCount == 0) {
threadCount = NSProcessInfo.processInfo.activeProcessorCount * 2;
}
__block atomic_size_t counter = ATOMIC_VAR_INIT(0);
for (NSUInteger t = 0; t < threadCount; t++) {
dispatch_async(queue, ^{
size_t i;
while ((i = atomic_fetch_add(&counter, 1)) < iterationCount) {
work(i);
}
});
}
};

View File

@ -0,0 +1,38 @@
//
// _ASCollectionGalleryLayoutItem.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASLayoutElement.h>
@class ASCollectionElement;
NS_ASSUME_NONNULL_BEGIN
/**
* A dummy item that represents a collection element to participate in the collection layout calculation process
* without triggering measurement on the actual node of the collection element.
*
* This item always has a fixed size that is the item size passed to it.
*/
AS_SUBCLASSING_RESTRICTED
@interface _ASGalleryLayoutItem : NSObject <ASLayoutElement>
@property (nonatomic, assign, readonly) CGSize itemSize;
@property (nonatomic, weak, readonly) ASCollectionElement *collectionElement;
- (instancetype)initWithItemSize:(CGSize)itemSize collectionElement:(ASCollectionElement *)collectionElement;
- (instancetype)init __unavailable;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,86 @@
//
// _ASCollectionGalleryLayoutItem.mm
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/_ASCollectionGalleryLayoutItem.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutElementPrivate.h>
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
#import <AsyncDisplayKit/ASLayoutSpec.h>
@implementation _ASGalleryLayoutItem {
std::atomic<ASPrimitiveTraitCollection> _primitiveTraitCollection;
}
@synthesize style;
- (instancetype)initWithItemSize:(CGSize)itemSize collectionElement:(ASCollectionElement *)collectionElement
{
self = [super init];
if (self) {
ASDisplayNodeAssert(! CGSizeEqualToSize(CGSizeZero, itemSize), @"Item size should not be zero");
ASDisplayNodeAssertNotNil(collectionElement, @"Collection element should not be nil");
_itemSize = itemSize;
_collectionElement = collectionElement;
}
return self;
}
ASLayoutElementStyleExtensibilityForwarding
ASPrimitiveTraitCollectionDefaults
ASPrimitiveTraitCollectionDeprecatedImplementation
- (ASTraitCollection *)asyncTraitCollection
{
ASDisplayNodeAssertNotSupported();
return nil;
}
- (ASLayoutElementType)layoutElementType
{
return ASLayoutElementTypeLayoutSpec;
}
- (NSArray<id<ASLayoutElement>> *)sublayoutElements
{
ASDisplayNodeAssertNotSupported();
return nil;
}
- (BOOL)implementsLayoutMethod
{
return YES;
}
ASLayoutElementLayoutCalculationDefaults
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
{
ASDisplayNodeAssert(CGSizeEqualToSize(_itemSize, ASSizeRangeClamp(constrainedSize, _itemSize)),
@"Item size %@ can't fit within the bounds of constrained size %@", NSStringFromCGSize(_itemSize), NSStringFromASSizeRange(constrainedSize));
return [ASLayout layoutWithLayoutElement:self size:_itemSize];
}
#pragma mark - ASLayoutElementAsciiArtProtocol
- (NSString *)asciiArtString
{
return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]];
}
- (NSString *)asciiArtName
{
return [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding];
}
@end

View File

@ -2,8 +2,17 @@
// ASDispatchTests.m
// Texture
//
// Created by Adlai Holler on 8/25/16.
// Copyright © 2016 Facebook. All rights reserved.
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <XCTest/XCTest.h>
@ -35,4 +44,29 @@
XCTAssertEqualObjects(indices, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, iterations)]);
}
- (void)testDispatchAsync
{
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSInteger expectedThreadCount = [NSProcessInfo processInfo].activeProcessorCount * 2;
NSLock *lock = [NSLock new];
NSMutableSet *threads = [NSMutableSet set];
NSMutableIndexSet *indices = [NSMutableIndexSet indexSet];
XCTestExpectation *expectation = [self expectationWithDescription:@"Executed all blocks"];
size_t const iterations = 1E5;
ASDispatchAsync(iterations, q, 0, ^(size_t i) {
[lock lock];
[threads addObject:[NSThread currentThread]];
XCTAssertFalse([indices containsIndex:i]);
[indices addIndex:i];
if (indices.count == iterations) {
[expectation fulfill];
}
[lock unlock];
});
[self waitForExpectationsWithTimeout:10 handler:nil];
XCTAssertLessThanOrEqual(threads.count, expectedThreadCount);
XCTAssertEqualObjects(indices, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, iterations)]);
}
@end

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Sample.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,18 +1,18 @@
//
// ViewController.m
// Sample
// Texture
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import "ViewController.h"
@ -21,6 +21,8 @@
#import "SupplementaryNode.h"
#import "ItemNode.h"
#define ASYNC_COLLECTION_LAYOUT 0
@interface ViewController () <ASCollectionDataSource, ASCollectionDelegateFlowLayout>
@property (nonatomic, strong) ASCollectionNode *collectionNode;
@ -43,8 +45,18 @@
- (void)viewDidLoad
{
[super viewDidLoad];
#if ASYNC_COLLECTION_LAYOUT
id<ASCollectionLayoutDelegate> layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionVerticalDirections
itemSize:CGSizeMake(180, 90)];
self.collectionNode = [[ASCollectionNode alloc] initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil];
#else
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.headerReferenceSize = CGSizeMake(50.0, 50.0);
layout.footerReferenceSize = CGSizeMake(50.0, 50.0);
self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
#endif
self.collectionNode = [[ASCollectionNode alloc] initWithLayoutDelegate:[[ASCollectionFlowLayoutDelegate alloc] init] layoutFacilitator:nil];
self.collectionNode.dataSource = self;
self.collectionNode.delegate = self;

View File

@ -16,6 +16,7 @@
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; };
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; };
AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; };
E5B2252C1F1791EA001E1431 /* MosaicCollectionLayoutInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = E5B2252B1F1791EA001E1431 /* MosaicCollectionLayoutInfo.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -35,6 +36,8 @@
AC3C4A691A11F47200143C57 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
E2F287D91FFDEA2A747630CE /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = "<group>"; };
E5B2252A1F1791DE001E1431 /* MosaicCollectionLayoutInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MosaicCollectionLayoutInfo.h; sourceTree = "<group>"; };
E5B2252B1F1791EA001E1431 /* MosaicCollectionLayoutInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MosaicCollectionLayoutInfo.m; sourceTree = "<group>"; };
E5D73A3A1EA6766B006418A8 /* MosaicCollectionLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MosaicCollectionLayoutDelegate.h; sourceTree = "<group>"; };
F36BCD8EBAF79797AB5C6708 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -83,6 +86,8 @@
children = (
E5D73A3A1EA6766B006418A8 /* MosaicCollectionLayoutDelegate.h */,
25A1FA841C02F7AC00193875 /* MosaicCollectionLayoutDelegate.m */,
E5B2252A1F1791DE001E1431 /* MosaicCollectionLayoutInfo.h */,
E5B2252B1F1791EA001E1431 /* MosaicCollectionLayoutInfo.m */,
AC3C4A651A11F47200143C57 /* AppDelegate.h */,
AC3C4A661A11F47200143C57 /* AppDelegate.m */,
AC3C4A681A11F47200143C57 /* ViewController.h */,
@ -152,6 +157,7 @@
TargetAttributes = {
AC3C4A5D1A11F47200143C57 = {
CreatedOnToolsVersion = 6.1;
DevelopmentTeam = XSR3D45JSF;
};
};
};
@ -244,6 +250,7 @@
AC3C4A641A11F47200143C57 /* main.m in Sources */,
80364CCA1E3D95A90094400C /* ImageCollectionViewCell.m in Sources */,
25A1FA881C02FCB000193875 /* ImageCellNode.m in Sources */,
E5B2252C1F1791EA001E1431 /* MosaicCollectionLayoutInfo.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -333,9 +340,11 @@
baseConfigurationReference = F36BCD8EBAF79797AB5C6708 /* Pods-Sample.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
DEVELOPMENT_TEAM = XSR3D45JSF;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample.CustomCollectionView;
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = 1;
};
@ -346,9 +355,11 @@
baseConfigurationReference = E2F287D91FFDEA2A747630CE /* Pods-Sample.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
DEVELOPMENT_TEAM = XSR3D45JSF;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample.CustomCollectionView;
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = 1;
};

View File

@ -11,63 +11,65 @@
//
#import "MosaicCollectionLayoutDelegate.h"
#import "MosaicCollectionLayoutInfo.h"
#import "ImageCellNode.h"
#import <AsyncDisplayKit/ASCollectionElement.h>
@implementation MosaicCollectionLayoutDelegate {
// Read-only properties
NSInteger _numberOfColumns;
CGFloat _headerHeight;
CGFloat _columnSpacing;
UIEdgeInsets _sectionInset;
UIEdgeInsets _interItemSpacing;
MosaicCollectionLayoutInfo *_info;
}
- (instancetype)initWithNumberOfColumns:(NSInteger)numberOfColumns headerHeight:(CGFloat)headerHeight
{
self = [super init];
if (self != nil) {
_numberOfColumns = numberOfColumns;
_headerHeight = headerHeight;
_columnSpacing = 10.0;
_sectionInset = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0);
_interItemSpacing = UIEdgeInsetsMake(10.0, 0, 10.0, 0);
_info = [[MosaicCollectionLayoutInfo alloc] initWithNumberOfColumns:numberOfColumns
headerHeight:headerHeight
columnSpacing:10.0
sectionInsets:UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0)
interItemSpacing:UIEdgeInsetsMake(10.0, 0, 10.0, 0)];
}
return self;
}
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
- (ASScrollDirection)scrollableDirections
{
return nil;
return ASScrollDirectionVerticalDirections;
}
- (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{
return _info;
}
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
CGFloat layoutWidth = context.viewportSize.width;
ASElementMap *elements = context.elements;
CGFloat top = 0;
MosaicCollectionLayoutInfo *info = (MosaicCollectionLayoutInfo *)context.additionalInfo;
// TODO use +[NSMapTable elementToLayoutAttributesTable]
NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *attrsMap = [NSMapTable mapTableWithKeyOptions:(NSMapTableObjectPointerPersonality | NSMapTableWeakMemory) valueOptions:NSMapTableStrongMemory];
NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *attrsMap = [NSMapTable elementToLayoutAttributesTable];
NSMutableArray *columnHeights = [NSMutableArray array];
NSInteger numberOfSections = [elements numberOfSections];
for (NSUInteger section = 0; section < numberOfSections; section++) {
NSInteger numberOfItems = [elements numberOfItemsInSection:section];
top += _sectionInset.top;
top += info.sectionInsets.top;
if (_headerHeight > 0) {
if (info.headerHeight > 0) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
ASCollectionElement *element = [elements supplementaryElementOfKind:UICollectionElementKindSectionHeader
atIndexPath:indexPath];
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withIndexPath:indexPath];
withIndexPath:indexPath];
ASSizeRange sizeRange = [self sizeRangeForHeaderOfSection:section withLayoutWidth:layoutWidth];
ASSizeRange sizeRange = [self _sizeRangeForHeaderOfSection:section withLayoutWidth:layoutWidth info:info];
CGSize size = [element.node layoutThatFits:sizeRange].size;
CGRect frame = CGRectMake(_sectionInset.left, top, size.width, size.height);
CGRect frame = CGRectMake(info.sectionInsets.left, top, size.width, size.height);
attrs.frame = frame;
[attrsMap setObject:attrs forKey:element];
@ -75,31 +77,31 @@
}
[columnHeights addObject:[NSMutableArray array]];
for (NSUInteger idx = 0; idx < _numberOfColumns; idx++) {
for (NSUInteger idx = 0; idx < info.numberOfColumns; idx++) {
[columnHeights[section] addObject:@(top)];
}
CGFloat columnWidth = [self _columnWidthForSection:section withLayoutWidth:layoutWidth];
CGFloat columnWidth = [self _columnWidthForSection:section withLayoutWidth:layoutWidth info:info];
for (NSUInteger idx = 0; idx < numberOfItems; idx++) {
NSUInteger columnIndex = [self _shortestColumnIndexInSection:section withColumnHeights:columnHeights];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:section];
ASCollectionElement *element = [elements elementForItemAtIndexPath:indexPath];
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
ASSizeRange sizeRange = [self sizeRangeForItem:element.node atIndexPath:indexPath withLayoutWidth:layoutWidth];
ASSizeRange sizeRange = [self _sizeRangeForItem:element.node atIndexPath:indexPath withLayoutWidth:layoutWidth info:info];
CGSize size = [element.node layoutThatFits:sizeRange].size;
CGPoint position = CGPointMake(_sectionInset.left + (columnWidth + _columnSpacing) * columnIndex,
[columnHeights[section][columnIndex] floatValue]);
CGPoint position = CGPointMake(info.sectionInsets.left + (columnWidth + info.columnSpacing) * columnIndex,
[columnHeights[section][columnIndex] floatValue]);
CGRect frame = CGRectMake(position.x, position.y, size.width, size.height);
attrs.frame = frame;
[attrsMap setObject:attrs forKey:element];
// TODO Profile and avoid boxing if there are significant retain/release overheads
columnHeights[section][columnIndex] = @(CGRectGetMaxY(frame) + _interItemSpacing.bottom);
columnHeights[section][columnIndex] = @(CGRectGetMaxY(frame) + info.interItemSpacing.bottom);
}
NSUInteger columnIndex = [self _tallestColumnIndexInSection:section withColumnHeights:columnHeights];
top = [columnHeights[section][columnIndex] floatValue] - _interItemSpacing.bottom + _sectionInset.bottom;
top = [columnHeights[section][columnIndex] floatValue] - info.interItemSpacing.bottom + info.sectionInsets.bottom;
for (NSUInteger idx = 0; idx < [columnHeights[section] count]; idx++) {
columnHeights[section][idx] = @(top);
@ -108,22 +110,24 @@
CGFloat contentHeight = [[[columnHeights lastObject] firstObject] floatValue];
CGSize contentSize = CGSizeMake(layoutWidth, contentHeight);
return [[ASCollectionLayoutState alloc] initWithContext:context contentSize:contentSize elementToLayoutAttributesTable:attrsMap];
return [[ASCollectionLayoutState alloc] initWithContext:context
contentSize:contentSize
elementToLayoutAttributesTable:attrsMap];
}
- (CGFloat)_widthForSection:(NSUInteger)section withLayoutWidth:(CGFloat)layoutWidth
+ (CGFloat)_columnWidthForSection:(NSUInteger)section withLayoutWidth:(CGFloat)layoutWidth info:(MosaicCollectionLayoutInfo *)info
{
return layoutWidth - _sectionInset.left - _sectionInset.right;
return ([self _widthForSection:section withLayoutWidth:layoutWidth info:info] - ((info.numberOfColumns - 1) * info.columnSpacing)) / info.numberOfColumns;
}
- (CGFloat)_columnWidthForSection:(NSUInteger)section withLayoutWidth:(CGFloat)layoutWidth
+ (CGFloat)_widthForSection:(NSUInteger)section withLayoutWidth:(CGFloat)layoutWidth info:(MosaicCollectionLayoutInfo *)info
{
return ([self _widthForSection:section withLayoutWidth:layoutWidth] - ((_numberOfColumns - 1) * _columnSpacing)) / _numberOfColumns;
return layoutWidth - info.sectionInsets.left - info.sectionInsets.right;
}
- (ASSizeRange)sizeRangeForItem:(ASCellNode *)item atIndexPath:(NSIndexPath *)indexPath withLayoutWidth:(CGFloat)layoutWidth;
+ (ASSizeRange)_sizeRangeForItem:(ASCellNode *)item atIndexPath:(NSIndexPath *)indexPath withLayoutWidth:(CGFloat)layoutWidth info:(MosaicCollectionLayoutInfo *)info
{
CGFloat itemWidth = [self _columnWidthForSection:indexPath.section withLayoutWidth:layoutWidth];
CGFloat itemWidth = [self _columnWidthForSection:indexPath.section withLayoutWidth:layoutWidth info:info];
if ([item isKindOfClass:[ImageCellNode class]]) {
return ASSizeRangeMake(CGSizeMake(itemWidth, 0), CGSizeMake(itemWidth, CGFLOAT_MAX));
} else {
@ -131,12 +135,12 @@
}
}
- (ASSizeRange)sizeRangeForHeaderOfSection:(NSInteger)section withLayoutWidth:(CGFloat)layoutWidth
+ (ASSizeRange)_sizeRangeForHeaderOfSection:(NSInteger)section withLayoutWidth:(CGFloat)layoutWidth info:(MosaicCollectionLayoutInfo *)info
{
return ASSizeRangeMake(CGSizeMake(0, _headerHeight), CGSizeMake([self _widthForSection:section withLayoutWidth:layoutWidth], _headerHeight));
return ASSizeRangeMake(CGSizeMake(0, info.headerHeight), CGSizeMake([self _widthForSection:section withLayoutWidth:layoutWidth info:info], info.headerHeight));
}
- (NSUInteger)_tallestColumnIndexInSection:(NSUInteger)section withColumnHeights:(NSArray *)columnHeights
+ (NSUInteger)_tallestColumnIndexInSection:(NSUInteger)section withColumnHeights:(NSArray *)columnHeights
{
__block NSUInteger index = 0;
__block CGFloat tallestHeight = 0;
@ -149,7 +153,7 @@
return index;
}
- (NSUInteger)_shortestColumnIndexInSection:(NSUInteger)section withColumnHeights:(NSArray *)columnHeights
+ (NSUInteger)_shortestColumnIndexInSection:(NSUInteger)section withColumnHeights:(NSArray *)columnHeights
{
__block NSUInteger index = 0;
__block CGFloat shortestHeight = CGFLOAT_MAX;

View File

@ -0,0 +1,32 @@
//
// MosaicCollectionLayoutInfo.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <UIKit/UIKit.h>
@interface MosaicCollectionLayoutInfo : NSObject
// Read-only properties
@property (nonatomic, assign, readonly) NSInteger numberOfColumns;
@property (nonatomic, assign, readonly) CGFloat headerHeight;
@property (nonatomic, assign, readonly) CGFloat columnSpacing;
@property (nonatomic, assign, readonly) UIEdgeInsets sectionInsets;
@property (nonatomic, assign, readonly) UIEdgeInsets interItemSpacing;
- (instancetype)initWithNumberOfColumns:(NSInteger)numberOfColumns
headerHeight:(CGFloat)headerHeight
columnSpacing:(CGFloat)columnSpacing
sectionInsets:(UIEdgeInsets)sectionInsets
interItemSpacing:(UIEdgeInsets)interItemSpacing NS_DESIGNATED_INITIALIZER;
- (instancetype)init __unavailable;
@end

View File

@ -0,0 +1,78 @@
//
// MosaicCollectionLayoutInfo.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import "MosaicCollectionLayoutInfo.h"
#import <AsyncDisplayKit/ASHashing.h>
@implementation MosaicCollectionLayoutInfo
- (instancetype)initWithNumberOfColumns:(NSInteger)numberOfColumns
headerHeight:(CGFloat)headerHeight
columnSpacing:(CGFloat)columnSpacing
sectionInsets:(UIEdgeInsets)sectionInsets
interItemSpacing:(UIEdgeInsets)interItemSpacing
{
self = [super init];
if (self) {
_numberOfColumns = numberOfColumns;
_headerHeight = headerHeight;
_columnSpacing = columnSpacing;
_sectionInsets = sectionInsets;
_interItemSpacing = interItemSpacing;
}
return self;
}
- (BOOL)isEqualToInfo:(MosaicCollectionLayoutInfo *)info
{
if (info == nil) {
return NO;
}
return _numberOfColumns == info.numberOfColumns
&& _headerHeight == info.headerHeight
&& _columnSpacing == info.columnSpacing
&& UIEdgeInsetsEqualToEdgeInsets(_sectionInsets, info.sectionInsets)
&& UIEdgeInsetsEqualToEdgeInsets(_interItemSpacing, info.interItemSpacing);
}
- (BOOL)isEqual:(id)other
{
if (self == other) {
return YES;
}
if (! [other isKindOfClass:[MosaicCollectionLayoutInfo class]]) {
return NO;
}
return [self isEqualToInfo:other];
}
- (NSUInteger)hash
{
struct {
NSInteger numberOfColumns;
CGFloat headerHeight;
CGFloat columnSpacing;
UIEdgeInsets sectionInsets;
UIEdgeInsets interItemSpacing;
} data = {
_numberOfColumns,
_headerHeight,
_columnSpacing,
_sectionInsets,
_interItemSpacing,
};
return ASHashBytes(&data, sizeof(data));
}
@end