Merge commit '19a9d29aa873edcf5624bed7fa9ee86662da0bfd'

# Conflicts:
#	Source/Details/ASCollectionGalleryLayoutDelegate.m
This commit is contained in:
Peter 2017-09-11 00:03:22 +03:00
commit d1da40bb0b
76 changed files with 1210 additions and 255 deletions

View File

@ -429,9 +429,11 @@
E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */; }; E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */; };
E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */; }; E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */; };
E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; }; E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; };
E5667E8C1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = E5667E8B1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h */; settings = {ATTRIBUTES = (Private, ); }; };
E5667E8E1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = E5667E8D1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.m */; };
E5711A2C1C840C81009619D4 /* ASCollectionElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASCollectionElement.h */; settings = {ATTRIBUTES = (Private, ); }; }; 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 */; }; E5711A301C840C96009619D4 /* ASCollectionElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */; };
E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */ = {isa = PBXBuildFile; fileRef = E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */; }; E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */ = {isa = PBXBuildFile; fileRef = E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */; settings = {ATTRIBUTES = (Private, ); }; };
E5775AFE1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */; }; 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, ); }; }; 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, ); }; }; E5775B021F16759300CAC9BC /* ASCollectionLayoutCache.h in Headers */ = {isa = PBXBuildFile; fileRef = E5775B011F16759300CAC9BC /* ASCollectionLayoutCache.h */; settings = {ATTRIBUTES = (Private, ); }; };
@ -458,7 +460,7 @@
E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */ = {isa = PBXBuildFile; fileRef = E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */ = {isa = PBXBuildFile; fileRef = E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */; settings = {ATTRIBUTES = (Public, ); }; };
E5E281761E71C845006B67C2 /* ASCollectionLayoutState.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */; }; 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, ); }; }; 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 */; }; E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */; };
F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */; }; F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -917,6 +919,8 @@
E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPageTable.h; sourceTree = "<group>"; }; E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPageTable.h; sourceTree = "<group>"; };
E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPageTable.m; sourceTree = "<group>"; }; E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPageTable.m; sourceTree = "<group>"; };
E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutElement.mm; sourceTree = "<group>"; }; E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutElement.mm; sourceTree = "<group>"; };
E5667E8B1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionGalleryLayoutInfo.h; sourceTree = "<group>"; };
E5667E8D1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionGalleryLayoutInfo.m; sourceTree = "<group>"; };
E5711A2A1C840C81009619D4 /* ASCollectionElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionElement.h; 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>"; }; 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>"; }; E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionGalleryLayoutItem.h; sourceTree = "<group>"; };
@ -946,7 +950,7 @@
E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutState.h; sourceTree = "<group>"; }; E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutState.h; sourceTree = "<group>"; };
E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutState.mm; 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>"; }; 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; }; E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionGalleryLayoutDelegate.mm; 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; }; 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>"; }; 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>"; }; 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>"; };
@ -1670,6 +1674,8 @@
E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */, E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */,
E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.m */, E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.m */,
E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */, E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */,
E5667E8B1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h */,
E5667E8D1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.m */,
E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */, E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */,
E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */, E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */,
); );
@ -1687,7 +1693,7 @@
E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */, E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */,
E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */, E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */,
E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */, E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */,
E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.m */, E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */,
E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */, E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */,
E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */, E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */,
); );
@ -1824,6 +1830,8 @@
CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */, CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */,
E5775B021F16759300CAC9BC /* ASCollectionLayoutCache.h in Headers */, E5775B021F16759300CAC9BC /* ASCollectionLayoutCache.h in Headers */,
E5775B001F13D25400CAC9BC /* ASCollectionLayoutState+Private.h in Headers */, E5775B001F13D25400CAC9BC /* ASCollectionLayoutState+Private.h in Headers */,
E5667E8C1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h in Headers */,
E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */,
E5855DF01EBB4D83003639AE /* ASCollectionLayoutDefines.h in Headers */, E5855DF01EBB4D83003639AE /* ASCollectionLayoutDefines.h in Headers */,
E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */, E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */,
9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */, 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */,
@ -1896,7 +1904,6 @@
254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */, 254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */,
B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */,
CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */, CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */,
E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */,
25E327571C16819500A2170C /* ASPagerNode.h in Headers */, 25E327571C16819500A2170C /* ASPagerNode.h in Headers */,
CCCCCCDB1EC3EF060087FE10 /* ASTextLine.h in Headers */, CCCCCCDB1EC3EF060087FE10 /* ASTextLine.h in Headers */,
9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */, 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */,
@ -2254,6 +2261,7 @@
CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.m in Sources */, CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.m in Sources */,
34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */, 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */,
B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */,
E5667E8E1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.m in Sources */,
25E327591C16819500A2170C /* ASPagerNode.m in Sources */, 25E327591C16819500A2170C /* ASPagerNode.m in Sources */,
636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */, 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */,
B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */, B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */,
@ -2289,7 +2297,7 @@
CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.m in Sources */, CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.m in Sources */,
CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.m in Sources */, CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.m in Sources */,
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */,
E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.m in Sources */, E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm in Sources */,
34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */,
CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */, CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */,
254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */, 254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */,

View File

@ -1,24 +1,34 @@
## master ## master
* Add your own contributions to the next release on the line below this with your name. * Add your own contributions to the next release on the line below this with your name.
- [ASStackLayoutSpec] Add lineSpacing property working with flex wrap. [Flo Vouin](https://github.com/flovouin) - [ASCollectionNode] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. [#522](https://github.com/TextureGroup/Texture/pull/522) [Scott Goodson](https://github.com/appleguy)
- [ASStackLayoutSpec] Fix flex wrap overflow in some cases using item spacing. [Flo Vouin](https://github.com/flovouin) - [Accessibility] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. [#468][Scott Goodson](https://github.com/appleguy)
- [ASNodeController] Add -nodeDidLayout callback. Allow switching retain behavior at runtime. [Scott Goodson](https://github.com/appleguy) - [ASImageNode] Enabled .clipsToBounds by default, fixing the use of .cornerRadius and clipping of GIFs. [Scott Goodson](https://github.com/appleguy) [#466](https://github.com/TextureGroup/Texture/pull/466)
- [ASCollectionView] Add delegate bridging and index space translation for missing UICollectionViewLayout properties. [Scott Goodson](https://github.com/appleguy)
- [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) [#451](https://github.com/TextureGroup/Texture/pull/451)
- Fix an issue that causes infinite layout loop in ASDisplayNode after [#428](https://github.com/TextureGroup/Texture/pull/428) [Huy Nguyen](https://github.com/nguyenhuy) [#455](https://github.com/TextureGroup/Texture/pull/455)
- Fix an issue in layout transition that causes it to unexpectedly use the old layout [Huy Nguyen](https://github.com/nguyenhuy) [#464](https://github.com/TextureGroup/Texture/pull/464) - Fix an issue in layout transition that causes it to unexpectedly use the old layout [Huy Nguyen](https://github.com/nguyenhuy) [#464](https://github.com/TextureGroup/Texture/pull/464)
- Add -[ASDisplayNode detailedLayoutDescription] property to aid debugging. [Adlai Holler](https://github.com/Adlai-Holler) [#476](https://github.com/TextureGroup/Texture/pull/476) - Add -[ASDisplayNode detailedLayoutDescription] property to aid debugging. [Adlai Holler](https://github.com/Adlai-Holler) [#476](https://github.com/TextureGroup/Texture/pull/476)
- Fix an issue that causes calculatedLayoutDidChange being called needlessly. [Huy Nguyen](https://github.com/nguyenhuy) [#490](https://github.com/TextureGroup/Texture/pull/490)
- Negate iOS 11 automatic estimated table row heights. [Christian Selig](https://github.com/christianselig) [#485](https://github.com/TextureGroup/Texture/pull/485)
- [Breaking] Add content offset bridging property to ASTableNode and ASCollectionNode. Deprecate related methods in ASTableView and ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#460](https://github.com/TextureGroup/Texture/pull/460)
- Remove re-entrant access to self.view when applying initial pending state. [Adlai Holler](https://github.com/Adlai-Holler) [#510](https://github.com/TextureGroup/Texture/pull/510)
- Small improvements in ASCollectionLayout [Huy Nguyen](https://github.com/nguyenhuy) [#509](https://github.com/TextureGroup/Texture/pull/509) [#513](https://github.com/TextureGroup/Texture/pull/513)
- Fix retain cycle between ASImageNode and PINAnimatedImage [Phil Larson](https://github.com/plarson) [#520](https://github.com/TextureGroup/Texture/pull/520)
- Change the API for disabling logging from a compiler flag to a runtime C function ASDisableLogging(). [Adlai Holler](https://github.com/Adlai-Holler) [#528](https://github.com/TextureGroup/Texture/pull/528)
- Table and collection views to consider content inset when calculating (default) element size range [Huy Nguyen](https://github.com/nguyenhuy) [#525](https://github.com/TextureGroup/Texture/pull/525)
- [ASEditableTextNode] added -editableTextNodeShouldBeginEditing to ASEditableTextNodeDelegate to mirror the corresponding method from UITextViewDelegate. [Yan S.](https://github.com/yans) [#535](https://github.com/TextureGroup/Texture/pull/535)
##2.3.5 ##2.4
- Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler)
- Overhaul logging and add activity tracing support. [Adlai Holler](https://github.com/Adlai-Holler) - Overhaul logging and add activity tracing support. [Adlai Holler](https://github.com/Adlai-Holler)
- Fix a crash where scrolling a table view after entering editing mode could lead to bad internal states in the table. [Huy Nguyen](https://github.com/nguyenhuy) [#416](https://github.com/TextureGroup/Texture/pull/416/) - Fix a crash where scrolling a table view after entering editing mode could lead to bad internal states in the table. [Huy Nguyen](https://github.com/nguyenhuy) [#416](https://github.com/TextureGroup/Texture/pull/416/)
- Fix a crash in collection view that occurs if batch updates are performed while scrolling [Huy Nguyen](https://github.com/nguyenhuy) [#378](https://github.com/TextureGroup/Texture/issues/378) - Fix a crash in collection view that occurs if batch updates are performed while scrolling [Huy Nguyen](https://github.com/nguyenhuy) [#378](https://github.com/TextureGroup/Texture/issues/378)
- Some improvements in ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#407](https://github.com/TextureGroup/Texture/pull/407) - Some improvements in ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#407](https://github.com/TextureGroup/Texture/pull/407)
- Small refactors in ASDataController [Huy Nguyen](https://github.com/TextureGroup/Texture/pull/443) [#443](https://github.com/TextureGroup/Texture/pull/443) - Small refactors in ASDataController [Huy Nguyen](https://github.com/TextureGroup/Texture/pull/443) [#443](https://github.com/TextureGroup/Texture/pull/443)
- [ASCollectionView] Add delegate bridging and index space translation for missing UICollectionViewLayout properties. [Scott Goodson](https://github.com/appleguy)
- [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) [#451](https://github.com/TextureGroup/Texture/pull/451)
- Fix an issue that causes infinite layout loop in ASDisplayNode after [#428](https://github.com/TextureGroup/Texture/pull/428) [Huy Nguyen](https://github.com/nguyenhuy) [#455](https://github.com/TextureGroup/Texture/pull/455)
- Rename ASCellNode.viewModel to ASCellNode.nodeModel to reduce collisions with subclass properties implemented by clients. [Adlai Holler](https://github.com/Adlai-Holler) [#504](https://github.com/TextureGroup/Texture/pull/504)
##2.3.4 ##2.3.4
- [Yoga] Rewrite YOGA_TREE_CONTIGUOUS mode with improved behavior and cleaner integration [Scott Goodson](https://github.com/appleguy) - [Yoga] Rewrite YOGA_TREE_CONTIGUOUS mode with improved behavior and cleaner integration [Scott Goodson](https://github.com/appleguy)

View File

@ -1,5 +1,6 @@
[ [
"^plans/", "^plans/",
"^docs/", "^docs/",
"^CI/exclude-from-build.json$" "^CI/exclude-from-build.json$",
] "^**/*.md$"
]

View File

@ -125,14 +125,14 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
* *
* This property may be set off the main thread, but this method will never be invoked concurrently on the * This property may be set off the main thread, but this method will never be invoked concurrently on the
*/ */
@property (atomic, nullable) id viewModel; @property (atomic, nullable) id nodeModel;
/** /**
* Asks the node whether it can be updated to the given view model. * Asks the node whether it can be updated to the given node model.
* *
* The default implementation returns YES if the class matches that of the current view-model. * The default implementation returns YES if the class matches that of the current view-model.
*/ */
- (BOOL)canUpdateToViewModel:(id)viewModel; - (BOOL)canUpdateToNodeModel:(id)nodeModel;
/** /**
* The backing view controller, or @c nil if the node wasn't initialized with backing view controller * The backing view controller, or @c nil if the node wasn't initialized with backing view controller

View File

@ -172,9 +172,9 @@
} }
} }
- (BOOL)canUpdateToViewModel:(id)viewModel - (BOOL)canUpdateToNodeModel:(id)nodeModel
{ {
return [self.viewModel class] == [viewModel class]; return [self.nodeModel class] == [nodeModel class];
} }
- (NSIndexPath *)indexPath - (NSIndexPath *)indexPath

View File

@ -130,6 +130,20 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector; @property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector;
/**
* The offset of the content view's origin from the collection node's origin. Defaults to CGPointZero.
*/
@property (nonatomic, assign) CGPoint contentOffset;
/**
* Sets the offset from the content nodes origin to the collection nodes origin.
*
* @param contentOffset The offset
*
* @param animated YES to animate to this new offset at a constant velocity, NO to not aniamte and immediately make the transition.
*/
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
/** /**
* Tuning parameters for a range type in full mode. * Tuning parameters for a range type in full mode.
* *
@ -240,10 +254,39 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; - (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion;
/**
* Returns YES if the ASCollectionNode is still processing changes from performBatchUpdates:.
* This is typically the concurrent allocation (calling nodeBlocks) and layout of newly inserted
* ASCellNodes. If YES is returned, then calling -waitUntilAllUpdatesAreProcessed may take tens of
* milliseconds to return as it blocks on these concurrent operations.
*
* Returns NO if ASCollectionNode is fully synchronized with the underlying UICollectionView. This
* means that until the next performBatchUpdates: is called, it is safe to compare UIKit values
* (such as from UICollectionViewLayout) with your app's data source.
*
* This method will always return NO if called immediately after -waitUntilAllUpdatesAreProcessed.
*/
@property (nonatomic, readonly) BOOL isProcessingUpdates;
/**
* Schedules a block to be performed (on the main thread) after processing of performBatchUpdates:
* is finished (completely synchronized to UIKit). The blocks will be run at the moment that
* -isProcessingUpdates changes from YES to NO;
*
* When isProcessingUpdates == NO, the block is run block immediately (before the method returns).
*
* Blocks scheduled by this mechanism are NOT guaranteed to run in the order they are scheduled.
* They may also be delayed if performBatchUpdates continues to be called; the blocks will wait until
* all running updates are finished.
*
* Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks.
*/
- (void)onDidFinishProcessingUpdates:(nullable void (^)())didFinishProcessingUpdates;
/** /**
* Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread.
*/ */
- (void)waitUntilAllUpdatesAreCommitted; - (void)waitUntilAllUpdatesAreProcessed;
/** /**
* Inserts one or more sections. * Inserts one or more sections.
@ -421,15 +464,15 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable __kindof ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - (nullable __kindof ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
/** /**
* Retrieves the view-model for the item at the given index path, if any. * Retrieves the node-model for the item at the given index path, if any.
* *
* @param indexPath The index path of the requested item. * @param indexPath The index path of the requested item.
* *
* @return The view-model for the given item, or @c nil if no item exists at the specified path or no view-model was provided. * @return The node-model for the given item, or @c nil if no item exists at the specified path or no node-model was provided.
* *
* @warning This API is beta and subject to change. We'll try to provide an easy migration path. * @warning This API is beta and subject to change. We'll try to provide an easy migration path.
*/ */
- (nullable id)viewModelForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - (nullable id)nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
/** /**
* Retrieve the index path for the item with the given node. * Retrieve the index path for the item with the given node.
@ -489,9 +532,11 @@ NS_ASSUME_NONNULL_BEGIN
* @warning This method is substantially more expensive than UICollectionView's version. * @warning This method is substantially more expensive than UICollectionView's version.
* *
* @deprecated This method is deprecated in 2.0. Use @c reloadDataWithCompletion: and * @deprecated This method is deprecated in 2.0. Use @c reloadDataWithCompletion: and
* then @c waitUntilAllUpdatesAreCommitted instead. * then @c waitUntilAllUpdatesAreProcessed instead.
*/ */
- (void)reloadDataImmediately ASDISPLAYNODE_DEPRECATED_MSG("Use -reloadData / -reloadDataWithCompletion: followed by -waitUntilAllUpdatesAreCommitted instead."); - (void)reloadDataImmediately ASDISPLAYNODE_DEPRECATED_MSG("Use -reloadData / -reloadDataWithCompletion: followed by -waitUntilAllUpdatesAreProcessed instead.");
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed.");
@end @end
@ -525,7 +570,7 @@ NS_ASSUME_NONNULL_BEGIN
* *
* @return An object that contains all the data for this item. * @return An object that contains all the data for this item.
*/ */
- (nullable id)collectionNode:(ASCollectionNode *)collectionNode viewModelForItemAtIndexPath:(NSIndexPath *)indexPath; - (nullable id)collectionNode:(ASCollectionNode *)collectionNode nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath;
/** /**
* Similar to -collectionNode:nodeForItemAtIndexPath: * Similar to -collectionNode:nodeForItemAtIndexPath:

View File

@ -50,6 +50,8 @@
@property (nonatomic, assign) BOOL usesSynchronousDataLoading; @property (nonatomic, assign) BOOL usesSynchronousDataLoading;
@property (nonatomic, assign) CGFloat leadingScreensForBatching; @property (nonatomic, assign) CGFloat leadingScreensForBatching;
@property (weak, nonatomic) id <ASCollectionViewLayoutInspecting> layoutInspector; @property (weak, nonatomic) id <ASCollectionViewLayoutInspecting> layoutInspector;
@property (nonatomic, assign) CGPoint contentOffset;
@property (nonatomic, assign) BOOL animatesContentOffset;
@end @end
@implementation _ASCollectionPendingState @implementation _ASCollectionPendingState
@ -62,6 +64,8 @@
_allowsSelection = YES; _allowsSelection = YES;
_allowsMultipleSelection = NO; _allowsMultipleSelection = NO;
_inverted = NO; _inverted = NO;
_contentOffset = CGPointZero;
_animatesContentOffset = NO;
} }
return self; return self;
} }
@ -190,6 +194,8 @@
if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) {
[view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
} }
[view setContentOffset:pendingState.contentOffset animated:pendingState.animatesContentOffset];
// Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block. // Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block.
} }
@ -435,6 +441,30 @@
} }
} }
- (void)setContentOffset:(CGPoint)contentOffset
{
[self setContentOffset:contentOffset animated:NO];
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
if ([self pendingState]) {
_pendingState.contentOffset = contentOffset;
_pendingState.animatesContentOffset = animated;
} else {
[self.view setContentOffset:contentOffset animated:animated];
}
}
- (CGPoint)contentOffset
{
if ([self pendingState]) {
return _pendingState.contentOffset;
} else {
return self.view.contentOffset;
}
}
- (ASScrollDirection)scrollDirection - (ASScrollDirection)scrollDirection
{ {
return [self isNodeLoaded] ? self.view.scrollDirection : ASScrollDirectionNone; return [self isNodeLoaded] ? self.view.scrollDirection : ASScrollDirectionNone;
@ -595,10 +625,10 @@
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node; return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node;
} }
- (id)viewModelForItemAtIndexPath:(NSIndexPath *)indexPath - (id)nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath
{ {
[self reloadDataInitiallyIfNeeded]; [self reloadDataInitiallyIfNeeded];
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].viewModel; return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].nodeModel;
} }
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
@ -676,7 +706,21 @@
[self performBatchAnimated:UIView.areAnimationsEnabled updates:updates completion:completion]; [self performBatchAnimated:UIView.areAnimationsEnabled updates:updates completion:completion];
} }
- (void)waitUntilAllUpdatesAreCommitted - (BOOL)isProcessingUpdates
{
return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO);
}
- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion
{
if (!self.nodeLoaded) {
completion();
} else {
[self.view onDidFinishProcessingUpdates:completion];
}
}
- (void)waitUntilAllUpdatesAreProcessed
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
if (self.nodeLoaded) { if (self.nodeLoaded) {
@ -684,6 +728,11 @@
} }
} }
- (void)waitUntilAllUpdatesAreCommitted
{
[self waitUntilAllUpdatesAreProcessed];
}
- (void)reloadDataWithCompletion:(void (^)())completion - (void)reloadDataWithCompletion:(void (^)())completion
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -709,7 +758,7 @@
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[self reloadData]; [self reloadData];
[self waitUntilAllUpdatesAreCommitted]; [self waitUntilAllUpdatesAreProcessed];
} }
- (void)relayoutItems - (void)relayoutItems

View File

@ -143,6 +143,11 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead."); @property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead.");
/**
* The point at which the origin of the content view is offset from the origin of the collection view.
*/
@property (nonatomic, assign) CGPoint contentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
/** /**
* The object that acts as the asynchronous delegate of the collection view * The object that acts as the asynchronous delegate of the collection view
* *
@ -293,9 +298,11 @@ NS_ASSUME_NONNULL_BEGIN
- (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/** /**
* Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. * See ASCollectionNode.h for full documentation of these methods.
*/ */
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); @property (nonatomic, readonly) BOOL isProcessingUpdates;
- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion;
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead.");
/** /**
* Registers the given kind of supplementary node for use in creating node-backed supplementary views. * Registers the given kind of supplementary node for use in creating node-backed supplementary views.
@ -409,6 +416,8 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (NSArray<__kindof ASCellNode *> *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - (NSArray<__kindof ASCellNode *> *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
@end @end
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDataSource.") ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDataSource.")

View File

@ -202,7 +202,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
unsigned int collectionViewNumberOfItemsInSection:1; unsigned int collectionViewNumberOfItemsInSection:1;
unsigned int collectionNodeNodeForItem:1; unsigned int collectionNodeNodeForItem:1;
unsigned int collectionNodeNodeBlockForItem:1; unsigned int collectionNodeNodeBlockForItem:1;
unsigned int viewModelForItem:1; unsigned int nodeModelForItem:1;
unsigned int collectionNodeNodeForSupplementaryElement:1; unsigned int collectionNodeNodeForSupplementaryElement:1;
unsigned int collectionNodeNodeBlockForSupplementaryElement:1; unsigned int collectionNodeNodeBlockForSupplementaryElement:1;
unsigned int collectionNodeSupplementaryElementKindsInSection:1; unsigned int collectionNodeSupplementaryElementKindsInSection:1;
@ -359,6 +359,16 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
[_dataController relayoutAllNodes]; [_dataController relayoutAllNodes];
} }
- (BOOL)isProcessingUpdates
{
return [_dataController isProcessingUpdates];
}
- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion
{
[_dataController onDidFinishProcessingUpdates:completion];
}
- (void)waitUntilAllUpdatesAreCommitted - (void)waitUntilAllUpdatesAreCommitted
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -367,8 +377,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd));
return; return;
} }
[_dataController waitUntilAllUpdatesAreCommitted]; [_dataController waitUntilAllUpdatesAreProcessed];
} }
- (void)setDataSource:(id<UICollectionViewDataSource>)dataSource - (void)setDataSource:(id<UICollectionViewDataSource>)dataSource
@ -431,7 +441,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)]; _asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)];
_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)]; _asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)];
_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)]; _asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)];
_asyncDataSourceFlags.viewModelForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:viewModelForItemAtIndexPath:)]; _asyncDataSourceFlags.nodeModelForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeModelForItemAtIndexPath:)];
_asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)]; _asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)];
if (_asyncDataSourceFlags.interop) { if (_asyncDataSourceFlags.interop) {
@ -1536,10 +1546,6 @@ minimumLineSpacingForSectionAtIndex:(NSInteger)section
return [self.layoutInspector scrollableDirections]; return [self.layoutInspector scrollableDirections];
} }
- (ASScrollDirection)flowLayoutScrollableDirections:(UICollectionViewFlowLayout *)flowLayout {
return (flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections;
}
- (void)layoutSubviews - (void)layoutSubviews
{ {
if (_cellsForLayoutUpdates.count > 0) { if (_cellsForLayoutUpdates.count > 0) {
@ -1662,14 +1668,14 @@ minimumLineSpacingForSectionAtIndex:(NSInteger)section
#pragma mark - ASDataControllerSource #pragma mark - ASDataControllerSource
- (id)dataController:(ASDataController *)dataController viewModelForItemAtIndexPath:(NSIndexPath *)indexPath - (id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath
{ {
if (!_asyncDataSourceFlags.viewModelForItem) { if (!_asyncDataSourceFlags.nodeModelForItem) {
return nil; return nil;
} }
GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil); GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil);
return [_asyncDataSource collectionNode:collectionNode viewModelForItemAtIndexPath:indexPath]; return [_asyncDataSource collectionNode:collectionNode nodeModelForItemAtIndexPath:indexPath];
} }
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath
@ -2197,7 +2203,7 @@ minimumLineSpacingForSectionAtIndex:(NSInteger)section
if (changedInNonScrollingDirection) { if (changedInNonScrollingDirection) {
[_dataController relayoutAllNodes]; [_dataController relayoutAllNodes];
[_dataController waitUntilAllUpdatesAreCommitted]; [_dataController waitUntilAllUpdatesAreProcessed];
// We need to ensure the size requery is done before we update our layout. // We need to ensure the size requery is done before we update our layout.
[self.collectionViewLayout invalidateLayout]; [self.collectionViewLayout invalidateLayout];
} }

View File

@ -112,6 +112,19 @@ typedef struct {
@property (nonatomic, strong, readonly) ASEventLog *eventLog; @property (nonatomic, strong, readonly) ASEventLog *eventLog;
#endif #endif
/**
* @abstract Whether this node acts as an accessibility container. If set to YES, then this node's accessibility label will represent
* an aggregation of all child nodes' accessibility labels. Nodes in this node's subtree that are also accessibility containers will
* not be included in this aggregation, and will be exposed as separate accessibility elements to UIKit.
*/
@property (nonatomic, assign) BOOL isAccessibilityContainer;
/**
* @abstract Invoked when a user performs a custom action on an accessible node. Nodes that are children of accessibility containers, have
* an accessibity label and have an interactive UIAccessibilityTrait will automatically receive custom-action handling.
*/
- (void)performAccessibilityCustomAction:(UIAccessibilityCustomAction *)action;
/** /**
* @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network. * @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network.
* Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished. * Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished.

View File

@ -520,7 +520,14 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
measurementCompletion:(void(^)())completion measurementCompletion:(void(^)())completion
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass]
ASSizeRange sizeRange;
{
ASDN::MutexLocker l(__instanceLock__);
sizeRange = [self _locked_constrainedSizeForLayoutPass];
}
[self transitionLayoutWithSizeRange:sizeRange
animated:animated animated:animated
shouldMeasureAsync:shouldMeasureAsync shouldMeasureAsync:shouldMeasureAsync
measurementCompletion:completion]; measurementCompletion:completion];
@ -850,8 +857,8 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
if (pendingLayoutTransition != nil) { if (pendingLayoutTransition != nil) {
[self _setCalculatedDisplayNodeLayout:pendingLayoutTransition.pendingLayout]; [self _setCalculatedDisplayNodeLayout:pendingLayoutTransition.pendingLayout];
[self _completeLayoutTransition:pendingLayoutTransition]; [self _completeLayoutTransition:pendingLayoutTransition];
[self _pendingLayoutTransitionDidComplete];
} }
[self _pendingLayoutTransitionDidComplete];
} }
/** /**

View File

@ -19,6 +19,7 @@
#if YOGA /* YOGA */ #if YOGA /* YOGA */
#import <AsyncDisplayKit/_ASDisplayViewAccessiblity.h>
#import <AsyncDisplayKit/ASYogaLayoutSpec.h> #import <AsyncDisplayKit/ASYogaLayoutSpec.h>
#import <AsyncDisplayKit/ASYogaUtilities.h> #import <AsyncDisplayKit/ASYogaUtilities.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h> #import <AsyncDisplayKit/ASDisplayNode+Beta.h>
@ -235,6 +236,11 @@
yogaFloatForCGFloat(rootConstrainedSize.max.height), yogaFloatForCGFloat(rootConstrainedSize.max.height),
YGDirectionInherit); YGDirectionInherit);
// Reset accessible elements, since layout may have changed.
ASPerformBlockOnMainThread(^{
[(_ASDisplayView *)self.view setAccessibleElements:nil];
});
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
[node setupYogaCalculatedLayout]; [node setupYogaCalculatedLayout];
node.yogaLayoutInProgress = NO; node.yogaLayoutInProgress = NO;

View File

@ -3095,12 +3095,13 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
- (void)_locked_applyPendingViewState - (void)_locked_applyPendingViewState
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert([self _locked_isNodeLoaded], @"Expected node to be loaded before applying pending state.");
if (_flags.layerBacked) { if (_flags.layerBacked) {
[_pendingViewState applyToLayer:self.layer]; [_pendingViewState applyToLayer:_layer];
} else { } else {
BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandling(checkFlag(Synchronous), _flags.layerBacked); BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandling(checkFlag(Synchronous), _flags.layerBacked);
[_pendingViewState applyToView:self.view withSpecialPropertiesHandling:specialPropertiesHandling]; [_pendingViewState applyToView:_view withSpecialPropertiesHandling:specialPropertiesHandling];
} }
// _ASPendingState objects can add up very quickly when adding // _ASPendingState objects can add up very quickly when adding
@ -3172,6 +3173,19 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
return measurements; return measurements;
} }
#pragma mark - Accessibility
- (void)setIsAccessibilityContainer:(BOOL)isAccessibilityContainer
{
ASDN::MutexLocker l(__instanceLock__);
_isAccessibilityContainer = isAccessibilityContainer;
}
- (BOOL)isAccessibilityContainer
{
ASDN::MutexLocker l(__instanceLock__);
return _isAccessibilityContainer;
}
#pragma mark - Debugging (Private) #pragma mark - Debugging (Private)

View File

@ -158,6 +158,13 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ASEditableTextNodeDelegate <NSObject> @protocol ASEditableTextNodeDelegate <NSObject>
@optional @optional
/**
@abstract Asks the delegate if editing should begin for the text node.
@param editableTextNode An editable text node.
@discussion YES if editing should begin; NO if editing should not begin -- the default returns YES.
*/
- (BOOL)editableTextNodeShouldBeginEditing:(ASEditableTextNode *)editableTextNode;
/** /**
@abstract Indicates to the delegate that the text node began editing. @abstract Indicates to the delegate that the text node began editing.
@param editableTextNode An editable text node. @param editableTextNode An editable text node.

View File

@ -699,6 +699,12 @@
} }
#pragma mark - UITextView Delegate #pragma mark - UITextView Delegate
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
// Delegateify.
return [self _delegateShouldBeginEditing];
}
- (void)textViewDidBeginEditing:(UITextView *)textView - (void)textViewDidBeginEditing:(UITextView *)textView
{ {
// Delegateify. // Delegateify.
@ -793,6 +799,14 @@
} }
#pragma mark - #pragma mark -
- (BOOL)_delegateShouldBeginEditing
{
if ([_delegate respondsToSelector:@selector(editableTextNodeShouldBeginEditing:)]) {
return [_delegate editableTextNodeShouldBeginEditing:self];
}
return YES;
}
- (void)_delegateDidBeginEditing - (void)_delegateDidBeginEditing
{ {
if ([_delegate respondsToSelector:@selector(editableTextNodeDidBeginEditing:)]) if ([_delegate respondsToSelector:@selector(editableTextNodeDidBeginEditing:)])

View File

@ -72,7 +72,7 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
} else { } else {
animatedImage.playbackReadyCallback = ^{ animatedImage.playbackReadyCallback = ^{
// In this case the lock is already gone we have to call the unlocked version therefore // In this case the lock is already gone we have to call the unlocked version therefore
[self setShouldAnimate:YES]; [weakSelf setShouldAnimate:YES];
}; };
} }
} }

View File

@ -176,7 +176,8 @@ typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry);
self.contentsScale = ASScreenScale(); self.contentsScale = ASScreenScale();
self.contentMode = UIViewContentModeScaleAspectFill; self.contentMode = UIViewContentModeScaleAspectFill;
self.opaque = NO; self.opaque = NO;
self.clipsToBounds = YES;
// If no backgroundColor is set to the image node and it's a subview of UITableViewCell, UITableView is setting // If no backgroundColor is set to the image node and it's a subview of UITableViewCell, UITableView is setting
// the opaque value of all subviews to YES if highlighting / selection is happening and does not set it back to the // the opaque value of all subviews to YES if highlighting / selection is happening and does not set it back to the
// initial value. With setting a explicit backgroundColor we can prevent that change. // initial value. With setting a explicit backgroundColor we can prevent that change.

View File

@ -77,6 +77,9 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
/**
* A horizontal, paging collection node.
*/
@interface ASPagerNode : ASCollectionNode @interface ASPagerNode : ASCollectionNode
/** /**
@ -86,6 +89,8 @@ NS_ASSUME_NONNULL_BEGIN
/** /**
* Initializer with custom-configured flow layout properties. * Initializer with custom-configured flow layout properties.
*
* NOTE: The flow layout must have a horizontal scroll direction.
*/ */
- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; - (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout;

View File

@ -30,7 +30,7 @@
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h> #import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
#import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h> #import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h>
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionDelegateFlowLayout, ASDelegateProxyInterceptor, ASCollectionGalleryLayoutSizeProviding> @interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionDelegateFlowLayout, ASDelegateProxyInterceptor, ASCollectionGalleryLayoutPropertiesProviding>
{ {
__weak id <ASPagerDataSource> _pagerDataSource; __weak id <ASPagerDataSource> _pagerDataSource;
ASPagerNodeProxy *_proxyDataSource; ASPagerNodeProxy *_proxyDataSource;
@ -67,6 +67,7 @@
- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; - (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout;
{ {
ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout."); ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout.");
ASDisplayNodeAssertTrue(flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal);
self = [super initWithCollectionViewLayout:flowLayout]; self = [super initWithCollectionViewLayout:flowLayout];
return self; return self;
} }
@ -76,7 +77,7 @@
ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionHorizontalDirections]; ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionHorizontalDirections];
self = [super initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil]; self = [super initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil];
if (self) { if (self) {
layoutDelegate.sizeProvider = self; layoutDelegate.propertiesProvider = self;
} }
return self; return self;
} }
@ -113,7 +114,15 @@
- (NSInteger)currentPageIndex - (NSInteger)currentPageIndex
{ {
return (self.view.contentOffset.x / CGRectGetWidth(self.view.bounds)); return (self.view.contentOffset.x / [self pageSize].width);
}
- (CGSize)pageSize
{
UIEdgeInsets contentInset = self.view.contentInset;
CGSize pageSize = self.bounds.size;
pageSize.height -= (contentInset.top + contentInset.bottom);
return pageSize;
} }
#pragma mark - Helpers #pragma mark - Helpers
@ -138,12 +147,12 @@
return indexPath.row; return indexPath.row;
} }
#pragma mark - ASCollectionGalleryLayoutSizeProviding #pragma mark - ASCollectionGalleryLayoutPropertiesProviding
- (CGSize)sizeForElements:(ASElementMap *)elements - (CGSize)sizeForElements:(ASElementMap *)elements
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
return self.bounds.size; return [self pageSize];
} }
#pragma mark - ASCollectionDataSource #pragma mark - ASCollectionDataSource
@ -180,7 +189,7 @@
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop
return ASSizeRangeMake(self.bounds.size); return ASSizeRangeMake([self pageSize]);
} }
#pragma mark - Data Source Proxy #pragma mark - Data Source Proxy

View File

@ -55,6 +55,20 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nonatomic, assign) BOOL inverted; @property (nonatomic, assign) BOOL inverted;
/**
* The offset of the content view's origin from the table node's origin. Defaults to CGPointZero.
*/
@property (nonatomic, assign) CGPoint contentOffset;
/**
* Sets the offset from the content nodes origin to the table nodes origin.
*
* @param contentOffset The offset
*
* @param animated YES to animate to this new offset at a constant velocity, NO to not aniamte and immediately make the transition.
*/
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
/** /**
* YES to automatically adjust the contentOffset when cells are inserted or deleted above * YES to automatically adjust the contentOffset when cells are inserted or deleted above
* visible cells, maintaining the users' visible scroll position. * visible cells, maintaining the users' visible scroll position.
@ -193,9 +207,38 @@ NS_ASSUME_NONNULL_BEGIN
- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; - (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion;
/** /**
* Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. * Returns YES if the ASCollectionNode is still processing changes from performBatchUpdates:.
* This is typically the concurrent allocation (calling nodeBlocks) and layout of newly inserted
* ASCellNodes. If YES is returned, then calling -waitUntilAllUpdatesAreProcessed may take tens of
* milliseconds to return as it blocks on these concurrent operations.
*
* Returns NO if ASCollectionNode is fully synchronized with the underlying UICollectionView. This
* means that until the next performBatchUpdates: is called, it is safe to compare UIKit values
* (such as from UICollectionViewLayout) with your app's data source.
*
* This method will always return NO if called immediately after -waitUntilAllUpdatesAreProcessed.
*/ */
- (void)waitUntilAllUpdatesAreCommitted; @property (nonatomic, readonly) BOOL isProcessingUpdates;
/**
* Schedules a block to be performed (on the main thread) after processing of performBatchUpdates:
* is finished (completely synchronized to UIKit). The blocks will be run at the moment that
* -isProcessingUpdates changes from YES to NO;
*
* When isProcessingUpdates == NO, the block is run block immediately (before the method returns).
*
* Blocks scheduled by this mechanism are NOT guaranteed to run in the order they are scheduled.
* They may also be delayed if performBatchUpdates continues to be called; the blocks will wait until
* all running updates are finished.
*
* Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks.
*/
- (void)onDidFinishProcessingUpdates:(nullable void (^)())didFinishProcessingUpdates;
/**
* Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread.
*/
- (void)waitUntilAllUpdatesAreProcessed;
/** /**
* Inserts one or more sections, with an option to animate the insertion. * Inserts one or more sections, with an option to animate the insertion.
@ -685,6 +728,12 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
@interface ASTableNode (Deprecated)
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed.");
@end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
#endif #endif

View File

@ -44,6 +44,8 @@
@property (nonatomic, assign) BOOL allowsMultipleSelectionDuringEditing; @property (nonatomic, assign) BOOL allowsMultipleSelectionDuringEditing;
@property (nonatomic, assign) BOOL inverted; @property (nonatomic, assign) BOOL inverted;
@property (nonatomic, assign) CGFloat leadingScreensForBatching; @property (nonatomic, assign) CGFloat leadingScreensForBatching;
@property (nonatomic, assign) CGPoint contentOffset;
@property (nonatomic, assign) BOOL animatesContentOffset;
@property (nonatomic, assign) BOOL automaticallyAdjustsContentOffset; @property (nonatomic, assign) BOOL automaticallyAdjustsContentOffset;
@end @end
@ -59,6 +61,8 @@
_allowsMultipleSelectionDuringEditing = NO; _allowsMultipleSelectionDuringEditing = NO;
_inverted = NO; _inverted = NO;
_leadingScreensForBatching = 2; _leadingScreensForBatching = 2;
_contentOffset = CGPointZero;
_animatesContentOffset = NO;
_automaticallyAdjustsContentOffset = NO; _automaticallyAdjustsContentOffset = NO;
} }
return self; return self;
@ -121,6 +125,7 @@
if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) {
[view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
} }
[view setContentOffset:pendingState.contentOffset animated:pendingState.animatesContentOffset];
} }
} }
@ -233,6 +238,33 @@
} }
} }
- (void)setContentOffset:(CGPoint)contentOffset
{
[self setContentOffset:contentOffset animated:NO];
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
_ASTablePendingState *pendingState = self.pendingState;
if (pendingState) {
pendingState.contentOffset = contentOffset;
pendingState.animatesContentOffset = animated;
} else {
ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist");
[self.view setContentOffset:contentOffset animated:animated];
}
}
- (CGPoint)contentOffset
{
_ASTablePendingState *pendingState = self.pendingState;
if (pendingState) {
return pendingState.contentOffset;
} else {
return self.view.contentOffset;
}
}
- (void)setAutomaticallyAdjustsContentOffset:(BOOL)automaticallyAdjustsContentOffset - (void)setAutomaticallyAdjustsContentOffset:(BOOL)automaticallyAdjustsContentOffset
{ {
_ASTablePendingState *pendingState = self.pendingState; _ASTablePendingState *pendingState = self.pendingState;
@ -702,7 +734,21 @@ ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock)
} }
} }
- (void)waitUntilAllUpdatesAreCommitted - (BOOL)isProcessingUpdates
{
return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO);
}
- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion
{
if (!self.nodeLoaded) {
completion();
} else {
[self.view onDidFinishProcessingUpdates:completion];
}
}
- (void)waitUntilAllUpdatesAreProcessed
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
if (self.nodeLoaded) { if (self.nodeLoaded) {
@ -710,6 +756,11 @@ ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock)
} }
} }
- (void)waitUntilAllUpdatesAreCommitted
{
[self waitUntilAllUpdatesAreProcessed];
}
#pragma mark - Debugging (Private) #pragma mark - Debugging (Private)
- (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription - (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription

View File

@ -69,6 +69,10 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nonatomic, assign) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); @property (nonatomic, assign) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
/**
* The offset of the content view's origin from the table node's origin. Defaults to CGPointZero.
*/
@property (nonatomic, assign) CGPoint contentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
/** /**
* YES to automatically adjust the contentOffset when cells are inserted or deleted above * YES to automatically adjust the contentOffset when cells are inserted or deleted above
@ -86,6 +90,12 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nonatomic, assign) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); @property (nonatomic, assign) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
@property (nonatomic, readonly, nullable) NSIndexPath *indexPathForSelectedRow ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForSelectedRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
/** /**
* Tuning parameters for a range type in full mode. * Tuning parameters for a range type in full mode.
* *
@ -140,12 +150,6 @@ NS_ASSUME_NONNULL_BEGIN
- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead.");
@property (nonatomic, readonly, nullable) NSIndexPath *indexPathForSelectedRow ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForSelectedRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead.");
- (nullable NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - (nullable NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead.");
@ -217,9 +221,11 @@ NS_ASSUME_NONNULL_BEGIN
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL completed))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL completed))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead.");
/** /**
* Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. * See ASTableNode.h for full documentation of these methods.
*/ */
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); @property (nonatomic, readonly) BOOL isProcessingUpdates;
- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion;
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASTableNode waitUntilAllUpdatesAreProcessed] instead.");
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead.");
@ -243,6 +249,8 @@ NS_ASSUME_NONNULL_BEGIN
/// Deprecated in 2.0. You should not call this method. /// Deprecated in 2.0. You should not call this method.
- (void)clearFetchedData ASDISPLAYNODE_DEPRECATED_MSG("You should not call this method directly. Intead, rely on the Interstate State callback methods."); - (void)clearFetchedData ASDISPLAYNODE_DEPRECATED_MSG("You should not call this method directly. Intead, rely on the Interstate State callback methods.");
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead.");
@end @end
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASTableDataSource.") ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASTableDataSource.")

View File

@ -346,6 +346,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
_retainedLayer = self.layer; _retainedLayer = self.layer;
} }
// iOS 11 automatically uses estimated heights, so disable those (see PR #485)
if (AS_AT_LEAST_IOS11) {
super.estimatedRowHeight = 0.0;
super.estimatedSectionHeaderHeight = 0.0;
super.estimatedSectionFooterHeight = 0.0;
}
return self; return self;
} }
@ -542,7 +549,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[self reloadData]; [self reloadData];
[_dataController waitUntilAllUpdatesAreCommitted]; [_dataController waitUntilAllUpdatesAreProcessed];
} }
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated
@ -729,6 +736,16 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
} }
} }
- (BOOL)isProcessingUpdates
{
return [_dataController isProcessingUpdates];
}
- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion
{
[_dataController onDidFinishProcessingUpdates:completion];
}
- (void)waitUntilAllUpdatesAreCommitted - (void)waitUntilAllUpdatesAreCommitted
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -737,15 +754,16 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd));
return; return;
} }
[_dataController waitUntilAllUpdatesAreCommitted]; [_dataController waitUntilAllUpdatesAreProcessed];
} }
- (void)layoutSubviews - (void)layoutSubviews
{ {
// Remeasure all rows if our row width has changed. // Remeasure all rows if our row width has changed.
_remeasuringCellNodes = YES; _remeasuringCellNodes = YES;
CGFloat constrainedWidth = self.bounds.size.width - [self sectionIndexWidth]; UIEdgeInsets contentInset = self.contentInset;
CGFloat constrainedWidth = self.bounds.size.width - [self sectionIndexWidth] - contentInset.left - contentInset.right;
if (constrainedWidth > 0 && _nodesConstrainedWidth != constrainedWidth) { if (constrainedWidth > 0 && _nodesConstrainedWidth != constrainedWidth) {
_nodesConstrainedWidth = constrainedWidth; _nodesConstrainedWidth = constrainedWidth;
@ -1622,7 +1640,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
#pragma mark - ASDataControllerSource #pragma mark - ASDataControllerSource
- (id)dataController:(ASDataController *)dataController viewModelForItemAtIndexPath:(NSIndexPath *)indexPath - (id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath
{ {
// Not currently supported for tables. Will be added when the collection API stabilizes. // Not currently supported for tables. Will be added when the collection API stabilizes.
return nil; return nil;

View File

@ -27,8 +27,13 @@
#define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00 #define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00
#endif #endif
#ifndef kCFCoreFoundationVersionNumber_iOS_11_0
#define kCFCoreFoundationVersionNumber_iOS_11_0 1438.10
#endif
#define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) #define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0)
#define AS_AT_LEAST_IOS10 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) #define AS_AT_LEAST_IOS10 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0)
#define AS_AT_LEAST_IOS11 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_11_0)
// If Yoga is available, make it available anywhere we use ASAvailability. // If Yoga is available, make it available anywhere we use ASAvailability.
// This reduces Yoga-specific code in other files. // This reduces Yoga-specific code in other files.

View File

@ -21,16 +21,27 @@
#import <os/log.h> #import <os/log.h>
#import <os/activity.h> #import <os/activity.h>
#ifndef ASEnableLogs
#define ASEnableLogs 1
#endif
#ifndef ASEnableVerboseLogging #ifndef ASEnableVerboseLogging
#define ASEnableVerboseLogging 0 #define ASEnableVerboseLogging 0
#endif #endif
ASDISPLAYNODE_EXTERN_C_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN
/**
* Disable all logging.
*
* You should only use this function if the default log level is
* annoying during development. By default, logging is run at
* the appropriate system log level (see the os_log_* functions),
* so you do not need to worry generally about the performance
* implications of log messages.
*
* For example, virtually all log messages generated by Texture
* are at the `debug` log level, which the system
* disables in production.
*/
void ASDisableLogging();
/// Log for general node events e.g. interfaceState, didLoad. /// Log for general node events e.g. interfaceState, didLoad.
#define ASNodeLogEnabled 1 #define ASNodeLogEnabled 1
os_log_t ASNodeLog(); os_log_t ASNodeLog();

View File

@ -11,27 +11,41 @@
// //
#import <AsyncDisplayKit/ASLog.h> #import <AsyncDisplayKit/ASLog.h>
#import <stdatomic.h>
static atomic_bool __ASLogEnabled = ATOMIC_VAR_INIT(YES);
void ASDisableLogging() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
atomic_store(&__ASLogEnabled, NO);
});
}
ASDISPLAYNODE_INLINE BOOL ASLoggingIsEnabled() {
return atomic_load(&__ASLogEnabled);
}
os_log_t ASNodeLog() { os_log_t ASNodeLog() {
return ASCreateOnce((ASEnableLogs && ASNodeLogEnabled) ? as_log_create("org.TextureGroup.Texture", "Node") : OS_LOG_DISABLED); return (ASNodeLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Node")) : OS_LOG_DISABLED;
} }
os_log_t ASLayoutLog() { os_log_t ASLayoutLog() {
return ASCreateOnce((ASEnableLogs && ASLayoutLogEnabled) ? as_log_create("org.TextureGroup.Texture", "Layout") : OS_LOG_DISABLED); return (ASLayoutLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Layout")) : OS_LOG_DISABLED;
} }
os_log_t ASCollectionLog() { os_log_t ASCollectionLog() {
return ASCreateOnce((ASEnableLogs && ASCollectionLogEnabled) ? as_log_create("org.TextureGroup.Texture", "Collection") : OS_LOG_DISABLED); return (ASCollectionLogEnabled && ASLoggingIsEnabled()) ?ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Collection")) : OS_LOG_DISABLED;
} }
os_log_t ASDisplayLog() { os_log_t ASDisplayLog() {
return ASCreateOnce((ASEnableLogs && ASDisplayLogEnabled) ? as_log_create("org.TextureGroup.Texture", "Display") : OS_LOG_DISABLED); return (ASDisplayLogEnabled && ASLoggingIsEnabled()) ?ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Display")) : OS_LOG_DISABLED;
} }
os_log_t ASImageLoadingLog() { os_log_t ASImageLoadingLog() {
return ASCreateOnce((ASEnableLogs && ASImageLoadingLogEnabled) ? as_log_create("org.TextureGroup.Texture", "ImageLoading") : OS_LOG_DISABLED); return (ASImageLoadingLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "ImageLoading")) : OS_LOG_DISABLED;
} }
os_log_t ASMainThreadDeallocationLog() { os_log_t ASMainThreadDeallocationLog() {
return ASCreateOnce((ASEnableLogs && ASMainThreadDeallocationLogEnabled) ? as_log_create("org.TextureGroup.Texture", "MainDealloc") : OS_LOG_DISABLED); return (ASMainThreadDeallocationLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "MainDealloc")) : OS_LOG_DISABLED;
} }

View File

@ -32,9 +32,9 @@ AS_SUBCLASSING_RESTRICTED
@property (nonatomic, assign) ASSizeRange constrainedSize; @property (nonatomic, assign) ASSizeRange constrainedSize;
@property (nonatomic, readonly, weak) id<ASRangeManagingNode> owningNode; @property (nonatomic, readonly, weak) id<ASRangeManagingNode> owningNode;
@property (nonatomic, assign) ASPrimitiveTraitCollection traitCollection; @property (nonatomic, assign) ASPrimitiveTraitCollection traitCollection;
@property (nonatomic, readonly, nullable) id viewModel; @property (nonatomic, readonly, nullable) id nodeModel;
- (instancetype)initWithViewModel:(nullable id)viewModel - (instancetype)initWithNodeModel:(nullable id)nodeModel
nodeBlock:(ASCellNodeBlock)nodeBlock nodeBlock:(ASCellNodeBlock)nodeBlock
supplementaryElementKind:(nullable NSString *)supplementaryElementKind supplementaryElementKind:(nullable NSString *)supplementaryElementKind
constrainedSize:(ASSizeRange)constrainedSize constrainedSize:(ASSizeRange)constrainedSize

View File

@ -32,7 +32,7 @@
ASCellNode *_node; ASCellNode *_node;
} }
- (instancetype)initWithViewModel:(id)viewModel - (instancetype)initWithNodeModel:(id)nodeModel
nodeBlock:(ASCellNodeBlock)nodeBlock nodeBlock:(ASCellNodeBlock)nodeBlock
supplementaryElementKind:(NSString *)supplementaryElementKind supplementaryElementKind:(NSString *)supplementaryElementKind
constrainedSize:(ASSizeRange)constrainedSize constrainedSize:(ASSizeRange)constrainedSize
@ -42,7 +42,7 @@
NSAssert(nodeBlock != nil, @"Node block must not be nil"); NSAssert(nodeBlock != nil, @"Node block must not be nil");
self = [super init]; self = [super init];
if (self) { if (self) {
_viewModel = viewModel; _nodeModel = nodeModel;
_nodeBlock = nodeBlock; _nodeBlock = nodeBlock;
_supplementaryElementKind = [supplementaryElementKind copy]; _supplementaryElementKind = [supplementaryElementKind copy];
_constrainedSize = constrainedSize; _constrainedSize = constrainedSize;
@ -65,7 +65,7 @@
node.owningNode = _owningNode; node.owningNode = _owningNode;
node.collectionElement = self; node.collectionElement = self;
ASTraitCollectionPropagateDown(node, _traitCollection); ASTraitCollectionPropagateDown(node, _traitCollection);
node.viewModel = _viewModel; node.nodeModel = _nodeModel;
_node = node; _node = node;
} }
return _node; return _node;

View File

@ -78,8 +78,9 @@
ASSizeRange sizeRange = ASSizeRangeForCollectionLayoutThatFitsViewportSize(context.viewportSize, context.scrollableDirections); ASSizeRange sizeRange = ASSizeRangeForCollectionLayoutThatFitsViewportSize(context.viewportSize, context.scrollableDirections);
ASLayout *layout = [stackSpec layoutThatFits:sizeRange]; ASLayout *layout = [stackSpec layoutThatFits:sizeRange];
return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nonnull(ASLayout * _Nonnull sublayout) { return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) {
return ((ASCellNode *)sublayout.layoutElement).collectionElement; ASCellNode *node = ASDynamicCast(sublayout.layoutElement, ASCellNode);
return node ? node.collectionElement : nil;
}]; }];
} }

View File

@ -19,7 +19,7 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@protocol ASCollectionGalleryLayoutSizeProviding <NSObject> @protocol ASCollectionGalleryLayoutPropertiesProviding <NSObject>
/** /**
* Returns the fixed size of each and every element. * Returns the fixed size of each and every element.
@ -32,6 +32,51 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (CGSize)sizeForElements:(ASElementMap *)elements; - (CGSize)sizeForElements:(ASElementMap *)elements;
@optional
/**
* Returns the minumum spacing to use between lines of items.
*
* @discussion This method will only be called on main thread.
*
* @discussion For a vertically scrolling layout, this value represents the minimum spacing between rows.
* For a horizontally scrolling one, it represents the minimum spacing between columns.
* It is not applied between the first line and the header, or between the last line and the footer.
* This is the same behavior as UICollectionViewFlowLayout's minimumLineSpacing.
*
* @param elements All elements in the layout.
*
* @return The interitem spacing
*/
- (CGFloat)minimumLineSpacingForElements:(ASElementMap *)elements;
/**
* Returns the minumum spacing to use between items in the same row or column, depending on the scroll directions.
*
* @discussion This method will only be called on main thread.
*
* @discussion For a vertically scrolling layout, this value represents the minimum spacing between items in the same row.
* For a horizontally scrolling one, it represents the minimum spacing between items in the same column.
* It is considered while fitting items into lines, but the actual final spacing between some items might be larger.
* This is the same behavior as UICollectionViewFlowLayout's minimumInteritemSpacing.
*
* @param elements All elements in the layout.
*
* @return The interitem spacing
*/
- (CGFloat)minimumInteritemSpacingForElements:(ASElementMap *)elements;
/**
* Returns the margins of each section.
*
* @discussion This method will only be called on main thread.
*
* @param elements All elements in the layout.
*
* @return The margins used to layout content in a section
*/
- (UIEdgeInsets)sectionInsetForElements:(ASElementMap *)elements;
@end @end
/** /**
@ -42,8 +87,13 @@ NS_ASSUME_NONNULL_BEGIN
AS_SUBCLASSING_RESTRICTED AS_SUBCLASSING_RESTRICTED
@interface ASCollectionGalleryLayoutDelegate : NSObject <ASCollectionLayoutDelegate> @interface ASCollectionGalleryLayoutDelegate : NSObject <ASCollectionLayoutDelegate>
@property (nonatomic, weak) id<ASCollectionGalleryLayoutSizeProviding> sizeProvider; @property (nonatomic, weak) id<ASCollectionGalleryLayoutPropertiesProviding> propertiesProvider;
/**
* Designated initializer.
*
* @param scrollableDirections The scrollable directions of this layout. Must be either vertical or horizontal directions.
*/
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER; - (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER;
- (instancetype)init __unavailable; - (instancetype)init __unavailable;

View File

@ -0,0 +1,144 @@
//
// ASCollectionGalleryLayoutDelegate.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
//
#ifndef MINIMAL_ASDK
#import <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>
#import <AsyncDisplayKit/_ASCollectionGalleryLayoutInfo.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/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
#pragma mark - ASCollectionGalleryLayoutDelegate
@implementation ASCollectionGalleryLayoutDelegate {
ASScrollDirection _scrollableDirections;
struct {
unsigned int minimumLineSpacingForElements:1;
unsigned int minimumInteritemSpacingForElements:1;
unsigned int sectionInsetForElements:1;
} _propertiesProviderFlags;
}
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections
{
self = [super init];
if (self) {
// Scrollable directions must be either vertical or horizontal, but not both
ASDisplayNodeAssertTrue(ASScrollDirectionContainsVerticalDirection(scrollableDirections)
|| ASScrollDirectionContainsHorizontalDirection(scrollableDirections));
ASDisplayNodeAssertFalse(ASScrollDirectionContainsVerticalDirection(scrollableDirections)
&& ASScrollDirectionContainsHorizontalDirection(scrollableDirections));
_scrollableDirections = scrollableDirections;
}
return self;
}
- (ASScrollDirection)scrollableDirections
{
ASDisplayNodeAssertMainThread();
return _scrollableDirections;
}
- (void)setPropertiesProvider:(id<ASCollectionGalleryLayoutPropertiesProviding>)propertiesProvider
{
ASDisplayNodeAssertMainThread();
if (propertiesProvider == nil) {
_propertiesProvider = nil;
_propertiesProviderFlags = {};
} else {
_propertiesProvider = propertiesProvider;
_propertiesProviderFlags.minimumLineSpacingForElements = [_propertiesProvider respondsToSelector:@selector(minimumLineSpacingForElements:)];
_propertiesProviderFlags.minimumInteritemSpacingForElements = [_propertiesProvider respondsToSelector:@selector(minimumInteritemSpacingForElements:)];
_propertiesProviderFlags.sectionInsetForElements = [_propertiesProvider respondsToSelector:@selector(sectionInsetForElements:)];
}
}
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{
ASDisplayNodeAssertMainThread();
id<ASCollectionGalleryLayoutPropertiesProviding> propertiesProvider = _propertiesProvider;
if (propertiesProvider == nil) {
return nil;
}
CGSize itemSize = [propertiesProvider sizeForElements:elements];
UIEdgeInsets sectionInset = _propertiesProviderFlags.sectionInsetForElements ? [propertiesProvider sectionInsetForElements:elements] : UIEdgeInsetsZero;
CGFloat lineSpacing = _propertiesProviderFlags.minimumLineSpacingForElements ? [propertiesProvider minimumLineSpacingForElements:elements] : 0.0;
CGFloat interitemSpacing = _propertiesProviderFlags.minimumInteritemSpacingForElements ? [propertiesProvider minimumInteritemSpacingForElements:elements] : 0.0;
return [[_ASCollectionGalleryLayoutInfo alloc] initWithItemSize:itemSize
minimumLineSpacing:lineSpacing
minimumInteritemSpacing:interitemSpacing
sectionInset:sectionInset];
}
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
CGSize pageSize = context.viewportSize;
ASScrollDirection scrollableDirections = context.scrollableDirections;
_ASCollectionGalleryLayoutInfo *info = ASDynamicCast(context.additionalInfo, _ASCollectionGalleryLayoutInfo);
CGSize itemSize = info.itemSize;
if (info == nil || CGSizeEqualToSize(CGSizeZero, itemSize)) {
return [[ASCollectionLayoutState alloc] initWithContext:context];
}
NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements,
ASCollectionElement *element,
[[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]);
if (children.count == 0) {
return [[ASCollectionLayoutState alloc] initWithContext:context];
}
// Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element
ASStackLayoutDirection stackDirection = ASScrollDirectionContainsVerticalDirection(scrollableDirections)
? ASStackLayoutDirectionHorizontal
: ASStackLayoutDirectionVertical;
ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:stackDirection
spacing:info.minimumInteritemSpacing
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStart
flexWrap:ASStackLayoutFlexWrapWrap
alignContent:ASStackLayoutAlignContentStart
lineSpacing:info.minimumLineSpacing
children:children];
stackSpec.concurrent = YES;
ASLayoutSpec *finalSpec = stackSpec;
UIEdgeInsets sectionInset = info.sectionInset;
if (UIEdgeInsetsEqualToEdgeInsets(sectionInset, UIEdgeInsetsZero) == NO) {
finalSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:sectionInset child:stackSpec];
}
ASLayout *layout = [finalSpec layoutThatFits:ASSizeRangeForCollectionLayoutThatFitsViewportSize(pageSize, scrollableDirections)];
return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) {
_ASGalleryLayoutItem *item = ASDynamicCast(sublayout.layoutElement, _ASGalleryLayoutItem);
return item ? item.collectionElement : nil;
}];
}
@end
#endif

View File

@ -29,6 +29,7 @@ AS_SUBCLASSING_RESTRICTED
@interface ASCollectionLayoutContext : NSObject @interface ASCollectionLayoutContext : NSObject
@property (nonatomic, assign, readonly) CGSize viewportSize; @property (nonatomic, assign, readonly) CGSize viewportSize;
@property (nonatomic, assign, readonly) CGPoint initialContentOffset;
@property (nonatomic, assign, readonly) ASScrollDirection scrollableDirections; @property (nonatomic, assign, readonly) ASScrollDirection scrollableDirections;
@property (nonatomic, weak, readonly) ASElementMap *elements; @property (nonatomic, weak, readonly) ASElementMap *elements;
@property (nonatomic, strong, readonly, nullable) id additionalInfo; @property (nonatomic, strong, readonly, nullable) id additionalInfo;

View File

@ -24,13 +24,11 @@
@implementation ASCollectionLayoutContext { @implementation ASCollectionLayoutContext {
Class<ASCollectionLayoutDelegate> _layoutDelegateClass; 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; __weak ASCollectionLayoutCache *_layoutCache;
} }
- (instancetype)initWithViewportSize:(CGSize)viewportSize - (instancetype)initWithViewportSize:(CGSize)viewportSize
initialContentOffset:(CGPoint)initialContentOffset
scrollableDirections:(ASScrollDirection)scrollableDirections scrollableDirections:(ASScrollDirection)scrollableDirections
elements:(ASElementMap *)elements elements:(ASElementMap *)elements
layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass
@ -40,6 +38,7 @@
self = [super init]; self = [super init];
if (self) { if (self) {
_viewportSize = viewportSize; _viewportSize = viewportSize;
_initialContentOffset = initialContentOffset;
_scrollableDirections = scrollableDirections; _scrollableDirections = scrollableDirections;
_elements = elements; _elements = elements;
_layoutDelegateClass = layoutDelegateClass; _layoutDelegateClass = layoutDelegateClass;
@ -59,6 +58,8 @@
return _layoutCache; return _layoutCache;
} }
// NOTE: Some properties, like initialContentOffset and layoutCache are ignored in -isEqualToContext: and -hash.
// That is because contexts can be equal regardless of the content offsets or layout caches.
- (BOOL)isEqualToContext:(ASCollectionLayoutContext *)context - (BOOL)isEqualToContext:(ASCollectionLayoutContext *)context
{ {
if (context == nil) { if (context == nil) {

View File

@ -25,6 +25,8 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
typedef ASCollectionElement * _Nullable (^ASCollectionLayoutStateGetElementBlock)(ASLayout *);
@interface NSMapTable (ASCollectionLayoutConvenience) @interface NSMapTable (ASCollectionLayoutConvenience)
+ (NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)elementToLayoutAttributesTable; + (NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)elementToLayoutAttributesTable;
@ -72,11 +74,11 @@ AS_SUBCLASSING_RESTRICTED
* *
* @param layout The layout describes size and position of all elements. * @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. * @param getElementBlock A block that can retrieve the collection element from a sublayout of the root layout.
*/ */
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - (instancetype)initWithContext:(ASCollectionLayoutContext *)context
layout:(ASLayout *)layout layout:(ASLayout *)layout
getElementBlock:(ASCollectionElement *(^)(ASLayout *))getElementBlock; getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock;
/** /**
* Returns all layout attributes present in this object. * Returns all layout attributes present in this object.

View File

@ -22,9 +22,12 @@
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h> #import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASElementMap.h> #import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASLayout.h> #import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutSpecUtilities.h>
#import <AsyncDisplayKit/ASPageTable.h> #import <AsyncDisplayKit/ASPageTable.h>
#import <AsyncDisplayKit/ASThread.h> #import <AsyncDisplayKit/ASThread.h>
#import <queue>
@implementation NSMapTable (ASCollectionLayoutConvenience) @implementation NSMapTable (ASCollectionLayoutConvenience)
+ (NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)elementToLayoutAttributesTable + (NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)elementToLayoutAttributesTable
@ -50,30 +53,49 @@ elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]];
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - (instancetype)initWithContext:(ASCollectionLayoutContext *)context
layout:(ASLayout *)layout layout:(ASLayout *)layout
getElementBlock:(ASCollectionElement *(^)(ASLayout *))getElementBlock getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock
{ {
ASElementMap *elements = context.elements; ASElementMap *elements = context.elements;
NSMapTable *table = [NSMapTable elementToLayoutAttributesTable]; NSMapTable *table = [NSMapTable elementToLayoutAttributesTable];
for (ASLayout *sublayout in layout.sublayouts) { // Traverse the given layout tree in breadth first fashion. Generate layout attributes for all included elements along the way.
ASCollectionElement *element = getElementBlock(sublayout); struct Context {
if (element == nil) { ASLayout *layout;
ASDisplayNodeFailAssert(@"Element not found!"); CGPoint absolutePosition;
continue; };
std::queue<Context> queue;
queue.push({layout, CGPointZero});
while (!queue.empty()) {
Context context = queue.front();
queue.pop();
ASLayout *layout = context.layout;
const CGPoint absolutePosition = context.absolutePosition;
ASCollectionElement *element = getElementBlock(layout);
if (element != nil) {
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];
}
CGRect frame = layout.frame;
frame.origin = absolutePosition;
attrs.frame = frame;
[table setObject:attrs forKey:element];
} }
NSIndexPath *indexPath = [elements indexPathForElement:element]; // Add all sublayouts to process in next step
NSString *supplementaryElementKind = element.supplementaryElementKind; for (ASLayout *sublayout in layout.sublayouts) {
queue.push({sublayout, absolutePosition + sublayout.position});
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]; return [self initWithContext:context contentSize:layout.size elementToLayoutAttributesTable:table];
@ -150,9 +172,10 @@ elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]];
} }
- (ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect - (ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect
contentSize:(CGSize)contentSize
pageSize:(CGSize)pageSize
{ {
CGSize pageSize = _context.viewportSize;
CGSize contentSize = _contentSize;
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if (_unmeasuredPageToLayoutAttributesTable.count == 0 || CGRectIsNull(rect) || CGRectIsEmpty(rect) || CGSizeEqualToSize(CGSizeZero, contentSize) || CGSizeEqualToSize(CGSizeZero, pageSize)) { if (_unmeasuredPageToLayoutAttributesTable.count == 0 || CGRectIsNull(rect) || CGRectIsEmpty(rect) || CGSizeEqualToSize(CGSizeZero, contentSize) || CGSizeEqualToSize(CGSizeZero, pageSize)) {
return nil; return nil;

View File

@ -27,9 +27,12 @@
// of the collection view // of the collection view
ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView) { ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView) {
CGSize maxSize = collectionView.bounds.size; CGSize maxSize = collectionView.bounds.size;
UIEdgeInsets contentInset = collectionView.contentInset;
if (ASScrollDirectionContainsHorizontalDirection(collectionView.scrollableDirections)) { if (ASScrollDirectionContainsHorizontalDirection(collectionView.scrollableDirections)) {
maxSize.width = CGFLOAT_MAX; maxSize.width = CGFLOAT_MAX;
maxSize.height -= (contentInset.top + contentInset.bottom);
} else { } else {
maxSize.width -= (contentInset.left + contentInset.right);
maxSize.height = CGFLOAT_MAX; maxSize.height = CGFLOAT_MAX;
} }
return ASSizeRangeMake(CGSizeZero, maxSize); return ASSizeRangeMake(CGSizeZero, maxSize);

View File

@ -79,7 +79,7 @@ extern NSString * const ASCollectionInvalidUpdateException;
*/ */
- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size; - (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size;
- (nullable id)dataController:(ASDataController *)dataController viewModelForItemAtIndexPath:(NSIndexPath *)indexPath; - (nullable id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath;
@optional @optional
@ -255,7 +255,12 @@ extern NSString * const ASCollectionInvalidUpdateException;
*/ */
- (void)relayoutNodes:(id<NSFastEnumeration>)nodes nodesSizeChanged:(NSMutableArray * _Nonnull)nodesSizesChanged; - (void)relayoutNodes:(id<NSFastEnumeration>)nodes nodesSizeChanged:(NSMutableArray * _Nonnull)nodesSizesChanged;
- (void)waitUntilAllUpdatesAreCommitted; /**
* See ASCollectionNode.h for full documentation of these methods.
*/
@property (nonatomic, readonly) BOOL isProcessingUpdates;
- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion;
- (void)waitUntilAllUpdatesAreProcessed;
/** /**
* Notifies the data controller object that its environment has changed. The object will request its environment delegate for new information * Notifies the data controller object that its environment has changed. The object will request its environment delegate for new information

View File

@ -332,18 +332,18 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
id<ASRangeManagingNode> node = self.node; id<ASRangeManagingNode> node = self.node;
for (NSIndexPath *indexPath in indexPaths) { for (NSIndexPath *indexPath in indexPaths) {
ASCellNodeBlock nodeBlock; ASCellNodeBlock nodeBlock;
id viewModel; id nodeModel;
if (isRowKind) { if (isRowKind) {
viewModel = [dataSource dataController:self viewModelForItemAtIndexPath:indexPath]; nodeModel = [dataSource dataController:self nodeModelForItemAtIndexPath:indexPath];
// Get the prior element and attempt to update the existing cell node. // Get the prior element and attempt to update the existing cell node.
if (viewModel != nil && !changeSet.includesReloadData) { if (nodeModel != nil && !changeSet.includesReloadData) {
NSIndexPath *oldIndexPath = [changeSet oldIndexPathForNewIndexPath:indexPath]; NSIndexPath *oldIndexPath = [changeSet oldIndexPathForNewIndexPath:indexPath];
if (oldIndexPath != nil) { if (oldIndexPath != nil) {
ASCollectionElement *oldElement = [previousMap elementForItemAtIndexPath:oldIndexPath]; ASCollectionElement *oldElement = [previousMap elementForItemAtIndexPath:oldIndexPath];
ASCellNode *oldNode = oldElement.node; ASCellNode *oldNode = oldElement.node;
if ([oldNode canUpdateToViewModel:viewModel]) { if ([oldNode canUpdateToNodeModel:nodeModel]) {
// Just wrap the node in a block. The collection element will -setViewModel: // Just wrap the node in a block. The collection element will -setNodeModel:
nodeBlock = ^{ nodeBlock = ^{
return oldNode; return oldNode;
}; };
@ -362,7 +362,7 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
} }
ASCollectionElement *element = [[ASCollectionElement alloc] initWithViewModel:viewModel ASCollectionElement *element = [[ASCollectionElement alloc] initWithNodeModel:nodeModel
nodeBlock:nodeBlock nodeBlock:nodeBlock
supplementaryElementKind:isRowKind ? nil : kind supplementaryElementKind:isRowKind ? nil : kind
constrainedSize:constrainedSize constrainedSize:constrainedSize
@ -432,13 +432,42 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
#pragma mark - Batching (External API) #pragma mark - Batching (External API)
- (void)waitUntilAllUpdatesAreCommitted - (void)waitUntilAllUpdatesAreProcessed
{ {
// Schedule block in main serial queue to wait until all operations are finished that are // Schedule block in main serial queue to wait until all operations are finished that are
// where scheduled while waiting for the _editingTransactionQueue to finish // where scheduled while waiting for the _editingTransactionQueue to finish
[self _scheduleBlockOnMainSerialQueue:^{ }]; [self _scheduleBlockOnMainSerialQueue:^{ }];
} }
- (BOOL)isProcessingUpdates
{
ASDisplayNodeAssertMainThread();
if (_mainSerialQueue.numberOfScheduledBlocks > 0) {
return YES;
} else if (dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_NOW) != 0) {
// After waiting for zero duration, a nonzero value is returned if blocks are still running.
return YES;
}
// Both the _mainSerialQueue and _editingTransactionQueue are drained; we are fully quiesced.
return NO;
}
- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion
{
ASDisplayNodeAssertMainThread();
if ([self isProcessingUpdates] == NO) {
ASPerformBlockOnMainThread(completion);
} else {
dispatch_async(_editingTransactionQueue, ^{
// Retry the block. If we're done processing updates, it'll run immediately, otherwise
// wait again for updates to quiesce completely.
[_mainSerialQueue performBlockOnMainThread:^{
[self onDidFinishProcessingUpdates:completion];
}];
});
}
}
- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -565,7 +594,7 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
}); });
if (_usesSynchronousDataLoading) { if (_usesSynchronousDataLoading) {
[self waitUntilAllUpdatesAreCommitted]; [self waitUntilAllUpdatesAreProcessed];
} }
} }

View File

@ -21,6 +21,7 @@
AS_SUBCLASSING_RESTRICTED AS_SUBCLASSING_RESTRICTED
@interface ASMainSerialQueue : NSObject @interface ASMainSerialQueue : NSObject
@property (nonatomic, readonly) NSUInteger numberOfScheduledBlocks;
- (void)performBlockOnMainThread:(dispatch_block_t)block; - (void)performBlockOnMainThread:(dispatch_block_t)block;
@end @end

View File

@ -40,6 +40,12 @@
return self; return self;
} }
- (NSUInteger)numberOfScheduledBlocks
{
ASDN::MutexLocker l(_serialQueueLock);
return _blocks.count;
}
- (void)performBlockOnMainThread:(dispatch_block_t)block - (void)performBlockOnMainThread:(dispatch_block_t)block
{ {
ASDN::MutexLocker l(_serialQueueLock); ASDN::MutexLocker l(_serialQueueLock);

View File

@ -23,9 +23,21 @@
#import <AsyncDisplayKit/ASDisplayNode+Beta.h> #import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h> #import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <queue>
NS_INLINE UIAccessibilityTraits InteractiveAccessibilityTraitsMask() {
return UIAccessibilityTraitLink | UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitButton;
}
#pragma mark - UIAccessibilityElement #pragma mark - UIAccessibilityElement
typedef NSComparisonResult (^SortAccessibilityElementsComparator)(UIAccessibilityElement *, UIAccessibilityElement *); @protocol ASAccessibilityElementPositioning
@property (nonatomic, readonly) CGRect accessibilityFrame;
@end
typedef NSComparisonResult (^SortAccessibilityElementsComparator)(id<ASAccessibilityElementPositioning>, id<ASAccessibilityElementPositioning>);
/// Sort accessiblity elements first by y and than by x origin. /// Sort accessiblity elements first by y and than by x origin.
static void SortAccessibilityElements(NSMutableArray *elements) static void SortAccessibilityElements(NSMutableArray *elements)
@ -35,7 +47,7 @@ static void SortAccessibilityElements(NSMutableArray *elements)
static SortAccessibilityElementsComparator comparator = nil; static SortAccessibilityElementsComparator comparator = nil;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
comparator = ^NSComparisonResult(UIAccessibilityElement *a, UIAccessibilityElement *b) { comparator = ^NSComparisonResult(id<ASAccessibilityElementPositioning> a, id<ASAccessibilityElementPositioning> b) {
CGPoint originA = a.accessibilityFrame.origin; CGPoint originA = a.accessibilityFrame.origin;
CGPoint originB = b.accessibilityFrame.origin; CGPoint originB = b.accessibilityFrame.origin;
if (originA.y == originB.y) { if (originA.y == originB.y) {
@ -50,7 +62,7 @@ static void SortAccessibilityElements(NSMutableArray *elements)
[elements sortUsingComparator:comparator]; [elements sortUsingComparator:comparator];
} }
@interface ASAccessibilityElement : UIAccessibilityElement @interface ASAccessibilityElement : UIAccessibilityElement<ASAccessibilityElementPositioning>
@property (nonatomic, strong) ASDisplayNode *node; @property (nonatomic, strong) ASDisplayNode *node;
@property (nonatomic, strong) ASDisplayNode *containerNode; @property (nonatomic, strong) ASDisplayNode *containerNode;
@ -85,6 +97,25 @@ static void SortAccessibilityElements(NSMutableArray *elements)
#pragma mark - _ASDisplayView / UIAccessibilityContainer #pragma mark - _ASDisplayView / UIAccessibilityContainer
@interface ASAccessibilityCustomAction : UIAccessibilityCustomAction<ASAccessibilityElementPositioning>
@property (nonatomic, strong) UIView *container;
@property (nonatomic, strong) ASDisplayNode *node;
@property (nonatomic, strong) ASDisplayNode *containerNode;
@end
@implementation ASAccessibilityCustomAction
- (CGRect)accessibilityFrame
{
CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node];
accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.container);
return accessibilityFrame;
}
@end
/// Collect all subnodes for the given node by walking down the subnode tree and calculates the screen coordinates based on the containerNode and container /// Collect all subnodes for the given node by walking down the subnode tree and calculates the screen coordinates based on the containerNode and container
static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplayNode *containerNode, id container, NSMutableArray *elements) static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplayNode *containerNode, id container, NSMutableArray *elements)
{ {
@ -100,12 +131,64 @@ static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplay
}); });
} }
static void CollectAccessibilityElementsForContainer(ASDisplayNode *container, _ASDisplayView *view, NSMutableArray *elements) {
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:container containerNode:container];
NSMutableArray<ASAccessibilityElement *> *labeledNodes = [NSMutableArray array];
NSMutableArray<ASAccessibilityCustomAction *> *actions = [NSMutableArray array];
std::queue<ASDisplayNode *> queue;
queue.push(container);
ASDisplayNode *node;
while (!queue.empty()) {
node = queue.front();
queue.pop();
if (node != container && node.isAccessibilityContainer) {
CollectAccessibilityElementsForContainer(node, view, elements);
continue;
}
if (node.accessibilityLabel.length > 0) {
if (node.accessibilityTraits & InteractiveAccessibilityTraitsMask()) {
ASAccessibilityCustomAction *action = [[ASAccessibilityCustomAction alloc] initWithName:node.accessibilityLabel target:node selector:@selector(performAccessibilityCustomAction:)];
action.node = node;
action.containerNode = node.supernode;
action.container = node.supernode.view;
[actions addObject:action];
} else {
// Even though not surfaced to UIKit, create a non-interactive element for purposes of building sorted aggregated label.
ASAccessibilityElement *nonInteractiveElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:node containerNode:container];
[labeledNodes addObject:nonInteractiveElement];
}
}
for (ASDisplayNode *subnode in node.subnodes) {
queue.push(subnode);
}
}
SortAccessibilityElements(labeledNodes);
NSArray *labels = [labeledNodes valueForKey:@"accessibilityLabel"];
accessiblityElement.accessibilityLabel = [labels componentsJoinedByString:@", "];
SortAccessibilityElements(actions);
accessiblityElement.accessibilityCustomActions = actions;
[elements addObject:accessiblityElement];
}
/// Collect all accessibliity elements for a given view and view node /// Collect all accessibliity elements for a given view and view node
static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableArray *elements) static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableArray *elements)
{ {
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray"); ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
ASDisplayNode *node = view.asyncdisplaykit_node; ASDisplayNode *node = view.asyncdisplaykit_node;
if (node.isAccessibilityContainer) {
CollectAccessibilityElementsForContainer(node, view, elements);
return;
}
// Handle rasterize case // Handle rasterize case
if (node.rasterizesSubtree) { if (node.rasterizesSubtree) {

View File

@ -258,7 +258,7 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
} else if (sublayoutsCount > 0){ } else if (sublayoutsCount > 0){
std::vector<Context> sublayoutContexts; std::vector<Context> sublayoutContexts;
for (ASLayout *sublayout in sublayouts) { for (ASLayout *sublayout in sublayouts) {
sublayoutContexts.push_back({sublayout, context.absolutePosition + sublayout.position}); sublayoutContexts.push_back({sublayout, absolutePosition + sublayout.position});
} }
queue.insert(queue.cbegin(), sublayoutContexts.begin(), sublayoutContexts.end()); queue.insert(queue.cbegin(), sublayoutContexts.begin(), sublayoutContexts.end());
} }

View File

@ -72,11 +72,13 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
CGSize viewportSize = [self _viewportSize]; CGSize viewportSize = [self _viewportSize];
CGPoint contentOffset = _collectionNode.contentOffset;
id additionalInfo = nil; id additionalInfo = nil;
if (_layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements) { if (_layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements) {
additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements]; additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements];
} }
return [[ASCollectionLayoutContext alloc] initWithViewportSize:viewportSize return [[ASCollectionLayoutContext alloc] initWithViewportSize:viewportSize
initialContentOffset:contentOffset
scrollableDirections:[_layoutDelegate scrollableDirections] scrollableDirections:[_layoutDelegate scrollableDirections]
elements:elements elements:elements
layoutDelegateClass:[_layoutDelegate class] layoutDelegateClass:[_layoutDelegate class]
@ -93,15 +95,19 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
ASCollectionLayoutState *layout = [context.layoutDelegateClass calculateLayoutWithContext:context]; ASCollectionLayoutState *layout = [context.layoutDelegateClass calculateLayoutWithContext:context];
[context.layoutCache setLayout:layout forContext: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 // Measure elements in the measure range ahead of time
CGSize viewportSize = context.viewportSize; CGSize viewportSize = context.viewportSize;
// TODO Consider content offset of the collection node CGPoint contentOffset = context.initialContentOffset;
CGRect initialRect = CGRectMake(0, 0, viewportSize.width, viewportSize.height); CGRect initialRect = CGRectMake(contentOffset.x, contentOffset.y, viewportSize.width, viewportSize.height);
CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(initialRect, CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(initialRect,
kASDefaultMeasureRangeTuningParameters, kASDefaultMeasureRangeTuningParameters,
context.scrollableDirections, context.scrollableDirections,
kASStaticScrollDirection); kASStaticScrollDirection);
[self _measureElementsInRect:measureRect blockingRect:initialRect layout:layout]; // The first call to -layoutAttributesForElementsInRect: will be with a rect that is way bigger than initialRect here.
// If we only block on initialRect, a few elements that are outside of initialRect but inside measureRect
// may not be available by the time -layoutAttributesForElementsInRect: is called.
// Since this method is usually run off main, let's spawn more threads to measure and block on all elements in measureRect.
[self _measureElementsInRect:measureRect blockingRect:measureRect layout:layout];
return layout; return layout;
} }
@ -140,8 +146,9 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
- (CGSize)collectionViewContentSize - (CGSize)collectionViewContentSize
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertNotNil(_layout, @"Collection layout state should not be nil at this point"); // The content size can be queried right after a layout invalidation (https://github.com/TextureGroup/Texture/pull/509).
return _layout.contentSize; // In that case, return zero.
return _layout ? _layout.contentSize : CGSizeZero;
} }
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)blockingRect - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)blockingRect
@ -247,11 +254,7 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
} }
// Step 2: Get layout attributes of all elements within the specified outer rect // Step 2: Get layout attributes of all elements within the specified outer rect
ASCollectionLayoutContext *context = layout.context; ASPageToLayoutAttributesTable *attrsTable = [layout getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:rect];
CGSize pageSize = context.viewportSize;
ASPageToLayoutAttributesTable *attrsTable = [layout getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:rect
contentSize:contentSize
pageSize:pageSize];
if (attrsTable.count == 0) { if (attrsTable.count == 0) {
// No elements in this rect! Bail early // No elements in this rect! Bail early
return; return;
@ -259,6 +262,8 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
// Step 3: Split all those attributes into blocking and non-blocking buckets // 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. // Use ordered sets here because some items may span multiple pages, and the sets will be accessed by indexes later on.
ASCollectionLayoutContext *context = layout.context;
CGSize pageSize = context.viewportSize;
NSMutableOrderedSet<UICollectionViewLayoutAttributes *> *blockingAttrs = hasBlockingRect ? [NSMutableOrderedSet orderedSet] : nil; NSMutableOrderedSet<UICollectionViewLayoutAttributes *> *blockingAttrs = hasBlockingRect ? [NSMutableOrderedSet orderedSet] : nil;
NSMutableOrderedSet<UICollectionViewLayoutAttributes *> *nonBlockingAttrs = [NSMutableOrderedSet orderedSet]; NSMutableOrderedSet<UICollectionViewLayoutAttributes *> *nonBlockingAttrs = [NSMutableOrderedSet orderedSet];
for (id pagePtr in attrsTable) { for (id pagePtr in attrsTable) {

View File

@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak, readonly) ASCollectionLayoutCache *layoutCache; @property (nonatomic, weak, readonly) ASCollectionLayoutCache *layoutCache;
- (instancetype)initWithViewportSize:(CGSize)viewportSize - (instancetype)initWithViewportSize:(CGSize)viewportSize
initialContentOffset:(CGPoint)initialContentOffset
scrollableDirections:(ASScrollDirection)scrollableDirections scrollableDirections:(ASScrollDirection)scrollableDirections
elements:(ASElementMap *)elements elements:(ASElementMap *)elements
layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass

View File

@ -24,9 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
* *
* @discussion This method is atomic and thread-safe * @discussion This method is atomic and thread-safe
*/ */
- (nullable ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect - (nullable ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect;
contentSize:(CGSize)contentSize
pageSize:(CGSize)pageSize;
@end @end

View File

@ -76,6 +76,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector; @property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector;
@property (nonatomic, assign) CGPoint contentOffset;
/** /**
* Tuning parameters for a range type in full mode. * Tuning parameters for a range type in full mode.
* *
@ -293,6 +295,8 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; - (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT;
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -195,6 +195,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
NSArray *_accessibilityHeaderElements; NSArray *_accessibilityHeaderElements;
CGPoint _accessibilityActivationPoint; CGPoint _accessibilityActivationPoint;
UIBezierPath *_accessibilityPath; UIBezierPath *_accessibilityPath;
BOOL _isAccessibilityContainer;
// performance measurement // performance measurement
ASDisplayNodePerformanceMeasurementOptions _measurementOptions; ASDisplayNodePerformanceMeasurementOptions _measurementOptions;

View File

@ -33,6 +33,19 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak) id<ASTableDelegate> asyncDelegate; @property (nonatomic, weak) id<ASTableDelegate> asyncDelegate;
@property (nonatomic, weak) id<ASTableDataSource> asyncDataSource; @property (nonatomic, weak) id<ASTableDataSource> asyncDataSource;
@property (nonatomic, assign) CGPoint contentOffset;
@property (nonatomic, assign) BOOL automaticallyAdjustsContentOffset;
@property (nonatomic, assign) BOOL inverted;
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows;
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForSelectedRows;
@property (nonatomic, readonly, nullable) NSIndexPath *indexPathForSelectedRow;
/**
* The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called.
*
* Defaults to two screenfuls.
*/
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
/** /**
* Initializer. * Initializer.
@ -44,10 +57,6 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style; - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style;
@property (nonatomic, assign) BOOL automaticallyAdjustsContentOffset;
@property (nonatomic, assign) BOOL inverted;
/** /**
* Tuning parameters for a range type in full mode. * Tuning parameters for a range type in full mode.
* *
@ -109,12 +118,6 @@ NS_ASSUME_NONNULL_BEGIN
- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition; - (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition;
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows;
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForSelectedRows;
@property (nonatomic, readonly, nullable) NSIndexPath *indexPathForSelectedRow;
- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point; - (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point;
- (nullable NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect; - (nullable NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect;
@ -135,13 +138,6 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; - (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT;
/**
* The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called.
*
* Defaults to two screenfuls.
*/
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
/** /**
* Reload everything from scratch, destroying the working range and all cached nodes. * Reload everything from scratch, destroying the working range and all cached nodes.
* *
@ -311,5 +307,7 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -485,7 +485,8 @@ static CGFloat computeItemsStackDimensionSum(const std::vector<ASStackLayoutSpec
}); });
// Sum up the childrens' dimensions (including spacing) in the stack direction. // Sum up the childrens' dimensions (including spacing) in the stack direction.
const CGFloat childStackDimensionSum = std::accumulate(items.begin(), items.end(), childSpacingSum, const CGFloat childStackDimensionSum = std::accumulate(items.begin(), items.end(),
childSpacingSum,
[&](CGFloat x, const ASStackLayoutSpecItem &l) { [&](CGFloat x, const ASStackLayoutSpecItem &l) {
return x + stackDimension(style.direction, l.layout.size); return x + stackDimension(style.direction, l.layout.size);
}); });
@ -647,22 +648,25 @@ static std::vector<ASStackUnpositionedLine> collectChildrenIntoLines(const std::
std::vector<ASStackUnpositionedLine> lines; std::vector<ASStackUnpositionedLine> lines;
std::vector<ASStackLayoutSpecItem> lineItems; std::vector<ASStackLayoutSpecItem> lineItems;
CGFloat lineStackDimensionSum = 0; CGFloat lineStackDimensionSum = 0;
CGFloat interitemSpacing = 0;
for(auto it = items.begin(); it != items.end(); ++it) { for(auto it = items.begin(); it != items.end(); ++it) {
const auto &item = *it; const auto &item = *it;
const CGFloat itemStackDimension = stackDimension(style.direction, item.layout.size); const CGFloat itemStackDimension = stackDimension(style.direction, item.layout.size);
const CGFloat itemAndSpacingStackDimension = (lineItems.empty() ? 0.0 : style.spacing) + item.child.style.spacingBefore + itemStackDimension + item.child.style.spacingAfter; const CGFloat itemAndSpacingStackDimension = item.child.style.spacingBefore + itemStackDimension + item.child.style.spacingAfter;
const BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + itemAndSpacingStackDimension, style, sizeRange) < 0); const BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + interitemSpacing + itemAndSpacingStackDimension, style, sizeRange) < 0);
const BOOL breakCurrentLine = negativeViolationIfAddItem && !lineItems.empty(); const BOOL breakCurrentLine = negativeViolationIfAddItem && !lineItems.empty();
if (breakCurrentLine) { if (breakCurrentLine) {
lines.push_back({.items = std::vector<ASStackLayoutSpecItem> (lineItems)}); lines.push_back({.items = std::vector<ASStackLayoutSpecItem> (lineItems)});
lineItems.clear(); lineItems.clear();
lineStackDimensionSum = 0; lineStackDimensionSum = 0;
interitemSpacing = 0;
} }
lineItems.push_back(std::move(item)); lineItems.push_back(std::move(item));
lineStackDimensionSum += itemAndSpacingStackDimension; lineStackDimensionSum += interitemSpacing + itemAndSpacingStackDimension;
interitemSpacing = style.spacing;
} }
// Handle last line // Handle last line

View File

@ -0,0 +1,30 @@
//
// _ASCollectionGalleryLayoutInfo.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 _ASCollectionGalleryLayoutInfo : NSObject
// Read-only properties
@property (nonatomic, assign, readonly) CGSize itemSize;
@property (nonatomic, assign, readonly) CGFloat minimumLineSpacing;
@property (nonatomic, assign, readonly) CGFloat minimumInteritemSpacing;
@property (nonatomic, assign, readonly) UIEdgeInsets sectionInset;
- (instancetype)initWithItemSize:(CGSize)itemSize
minimumLineSpacing:(CGFloat)minimumLineSpacing
minimumInteritemSpacing:(CGFloat)minimumInteritemSpacing
sectionInset:(UIEdgeInsets)sectionInset NS_DESIGNATED_INITIALIZER;
- (instancetype)init __unavailable;
@end

View File

@ -0,0 +1,72 @@
//
// _ASCollectionGalleryLayoutInfo.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/_ASCollectionGalleryLayoutInfo.h>
#import <AsyncDisplayKit/ASHashing.h>
@implementation _ASCollectionGalleryLayoutInfo
- (instancetype)initWithItemSize:(CGSize)itemSize
minimumLineSpacing:(CGFloat)minimumLineSpacing
minimumInteritemSpacing:(CGFloat)minimumInteritemSpacing
sectionInset:(UIEdgeInsets)sectionInset
{
self = [super init];
if (self) {
_itemSize = itemSize;
_minimumLineSpacing = minimumLineSpacing;
_minimumInteritemSpacing = minimumInteritemSpacing;
_sectionInset = sectionInset;
}
return self;
}
- (BOOL)isEqualToInfo:(_ASCollectionGalleryLayoutInfo *)info
{
if (info == nil) {
return NO;
}
return CGSizeEqualToSize(_itemSize, info.itemSize)
&& _minimumLineSpacing == info.minimumLineSpacing
&& _minimumInteritemSpacing == info.minimumInteritemSpacing
&& UIEdgeInsetsEqualToEdgeInsets(_sectionInset, info.sectionInset);
}
- (BOOL)isEqual:(id)other
{
if (self == other) {
return YES;
}
if (! [other isKindOfClass:[_ASCollectionGalleryLayoutInfo class]]) {
return NO;
}
return [self isEqualToInfo:other];
}
- (NSUInteger)hash
{
struct {
CGSize itemSize;
CGFloat minimumLineSpacing;
CGFloat minimumInteritemSpacing;
UIEdgeInsets sectionInset;
} data = {
_itemSize,
_minimumLineSpacing,
_minimumInteritemSpacing,
_sectionInset,
};
return ASHashBytes(&data, sizeof(data));
}
@end

View File

@ -38,7 +38,12 @@ ASDISPLAYNODE_EXTERN_C_BEGIN
@interface ASDisplayNode (ASResizableContents) <ASResizableContents> @interface ASDisplayNode (ASResizableContents) <ASResizableContents>
@end @end
// This function can operate on either an ASDisplayNode (including un-loaded) or CALayer directly. /**
This function can operate on either an ASDisplayNode (including un-loaded) or CALayer directly. More info about resizing mode: https://github.com/TextureGroup/Texture/issues/439
@param obj ASDisplayNode, CALayer or object that conforms to `ASResizableContents` protocol
@param image Image you would like to resize
*/
extern void ASDisplayNodeSetResizableContents(id<ASResizableContents> obj, UIImage *image); extern void ASDisplayNodeSetResizableContents(id<ASResizableContents> obj, UIImage *image);
/** /**

View File

@ -26,20 +26,16 @@ extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UI
extern void ASDisplayNodeSetResizableContents(id<ASResizableContents> obj, UIImage *image) extern void ASDisplayNodeSetResizableContents(id<ASResizableContents> obj, UIImage *image)
{ {
// FIXME: This method does not currently handle UIImageResizingModeTile, which is the default on iOS 6.
// I'm not sure of a way to use CALayer directly to perform such tiling on the GPU, though the stretch is handled by the GPU,
// and CALayer.h documents the fact that contentsCenter is used to stretch the pixels.
if (image) { if (image) {
ASDisplayNodeCAssert(image.resizingMode == UIImageResizingModeStretch || UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero),
@"Image insets must be all-zero or resizingMode has to be UIImageResizingModeStretch. XCode assets default value is UIImageResizingModeTile which is not supported by Texture because of GPU-accelerated CALayer features.");
// Image may not actually be stretchable in one or both dimensions; this is handled // Image may not actually be stretchable in one or both dimensions; this is handled
obj.contents = (id)[image CGImage]; obj.contents = (id)[image CGImage];
obj.contentsScale = [image scale]; obj.contentsScale = [image scale];
obj.rasterizationScale = [image scale]; obj.rasterizationScale = [image scale];
CGSize imageSize = [image size]; CGSize imageSize = [image size];
ASDisplayNodeCAssert(image.resizingMode == UIImageResizingModeStretch || UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero),
@"the resizing mode of image should be stretch; if not, then its insets must be all-zero");
UIEdgeInsets insets = [image capInsets]; UIEdgeInsets insets = [image capInsets];
// These are lifted from what UIImageView does by experimentation. Without these exact values, the stretching is slightly off. // These are lifted from what UIImageView does by experimentation. Without these exact values, the stretching is slightly off.

View File

@ -24,7 +24,7 @@
@end @end
@interface ASTestSection : NSObject <ASSectionContext> @interface ASTestSection : NSObject <ASSectionContext>
@property (nonatomic, readonly) NSMutableArray *viewModels; @property (nonatomic, readonly) NSMutableArray *nodeModels;
@end @end
@implementation ASCollectionModernDataSourceTests { @implementation ASCollectionModernDataSourceTests {
@ -41,10 +41,10 @@
// Default is 2 sections: 2 items in first, 1 item in second. // Default is 2 sections: 2 items in first, 1 item in second.
sections = [NSMutableArray array]; sections = [NSMutableArray array];
[sections addObject:[ASTestSection new]]; [sections addObject:[ASTestSection new]];
[sections[0].viewModels addObject:[NSObject new]]; [sections[0].nodeModels addObject:[NSObject new]];
[sections[0].viewModels addObject:[NSObject new]]; [sections[0].nodeModels addObject:[NSObject new]];
[sections addObject:[ASTestSection new]]; [sections addObject:[ASTestSection new]];
[sections[1].viewModels addObject:[NSObject new]]; [sections[1].nodeModels addObject:[NSObject new]];
window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
viewController = [[UIViewController alloc] init]; viewController = [[UIViewController alloc] init];
@ -60,7 +60,7 @@
@selector(numberOfSectionsInCollectionNode:), @selector(numberOfSectionsInCollectionNode:),
@selector(collectionNode:numberOfItemsInSection:), @selector(collectionNode:numberOfItemsInSection:),
@selector(collectionNode:nodeBlockForItemAtIndexPath:), @selector(collectionNode:nodeBlockForItemAtIndexPath:),
@selector(collectionNode:viewModelForItemAtIndexPath:), @selector(collectionNode:nodeModelForItemAtIndexPath:),
@selector(collectionNode:contextForSection:), @selector(collectionNode:contextForSection:),
nil]; nil];
[mockDataSource setExpectationOrderMatters:YES]; [mockDataSource setExpectationOrderMatters:YES];
@ -71,7 +71,7 @@
- (void)tearDown - (void)tearDown
{ {
[collectionNode waitUntilAllUpdatesAreCommitted]; [collectionNode waitUntilAllUpdatesAreProcessed];
[super tearDown]; [super tearDown];
} }
@ -112,7 +112,7 @@
skippedReloadIndexPaths:nil]; skippedReloadIndexPaths:nil];
} }
- (void)testReloadingAnItemWithACompatibleViewModel - (void)testReloadingAnItemWithACompatibleNodeModel
{ {
[self loadInitialData]; [self loadInitialData];
@ -120,15 +120,15 @@
NSIndexPath *reloadedPath = [NSIndexPath indexPathForItem:1 inSection:0]; NSIndexPath *reloadedPath = [NSIndexPath indexPathForItem:1 inSection:0];
NSIndexPath *deletedPath = [NSIndexPath indexPathForItem:0 inSection:0]; NSIndexPath *deletedPath = [NSIndexPath indexPathForItem:0 inSection:0];
id viewModel = [NSObject new]; id nodeModel = [NSObject new];
// Cell node should get -canUpdateToViewModel: // Cell node should get -canUpdateToNodeModel:
id mockCellNode = [collectionNode nodeForItemAtIndexPath:reloadedPath]; id mockCellNode = [collectionNode nodeForItemAtIndexPath:reloadedPath];
OCMExpect([mockCellNode canUpdateToViewModel:viewModel]) OCMExpect([mockCellNode canUpdateToNodeModel:nodeModel])
.andReturn(YES); .andReturn(YES);
[self performUpdateReloadingSections:nil [self performUpdateReloadingSections:nil
reloadingItems:@{ reloadedPath: viewModel } reloadingItems:@{ reloadedPath: nodeModel }
reloadMappings:@{ reloadedPath: [NSIndexPath indexPathForItem:0 inSection:0] } reloadMappings:@{ reloadedPath: [NSIndexPath indexPathForItem:0 inSection:0] }
insertingItems:nil insertingItems:nil
deletingItems:@[ deletedPath ] deletingItems:@[ deletedPath ]
@ -168,12 +168,12 @@
// It reads the contents for each item. // It reads the contents for each item.
for (NSInteger section = 0; section < sections.count; section++) { for (NSInteger section = 0; section < sections.count; section++) {
NSArray *viewModels = sections[section].viewModels; NSArray *nodeModels = sections[section].nodeModels;
// For each item: // For each item:
for (NSInteger i = 0; i < viewModels.count; i++) { for (NSInteger i = 0; i < nodeModels.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section]; NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
[self expectViewModelMethodForItemAtIndexPath:indexPath viewModel:viewModels[i]]; [self expectNodeModelMethodForItemAtIndexPath:indexPath nodeModel:nodeModels[i]];
[self expectNodeBlockMethodForItemAtIndexPath:indexPath]; [self expectNodeBlockMethodForItemAtIndexPath:indexPath];
} }
} }
@ -201,14 +201,14 @@
// Note: Skip fast enumeration for readability. // Note: Skip fast enumeration for readability.
for (NSInteger section = 0; section < sections.count; section++) { for (NSInteger section = 0; section < sections.count; section++) {
OCMExpect([mockDataSource collectionNode:collectionNode numberOfItemsInSection:section]) OCMExpect([mockDataSource collectionNode:collectionNode numberOfItemsInSection:section])
.andReturn(sections[section].viewModels.count); .andReturn(sections[section].nodeModels.count);
} }
} }
- (void)expectViewModelMethodForItemAtIndexPath:(NSIndexPath *)indexPath viewModel:(id)viewModel - (void)expectNodeModelMethodForItemAtIndexPath:(NSIndexPath *)indexPath nodeModel:(id)nodeModel
{ {
OCMExpect([mockDataSource collectionNode:collectionNode viewModelForItemAtIndexPath:indexPath]) OCMExpect([mockDataSource collectionNode:collectionNode nodeModelForItemAtIndexPath:indexPath])
.andReturn(viewModel); .andReturn(nodeModel);
} }
- (void)expectContextMethodForSection:(NSInteger)section - (void)expectContextMethodForSection:(NSInteger)section
@ -240,21 +240,21 @@
for (NSInteger section = 0; section < sections.count; section++) { for (NSInteger section = 0; section < sections.count; section++) {
ASTestSection *sectionObject = sections[section]; ASTestSection *sectionObject = sections[section];
NSArray *viewModels = sectionObject.viewModels; NSArray *nodeModels = sectionObject.nodeModels;
// Assert section object // Assert section object
XCTAssertEqualObjects([collectionNode contextForSection:section], sectionObject); XCTAssertEqualObjects([collectionNode contextForSection:section], sectionObject);
// Assert item count // Assert item count
XCTAssertEqual([collectionNode numberOfItemsInSection:section], viewModels.count); XCTAssertEqual([collectionNode numberOfItemsInSection:section], nodeModels.count);
for (NSInteger item = 0; item < viewModels.count; item++) { for (NSInteger item = 0; item < nodeModels.count; item++) {
// Assert view model // Assert node model
// Could use pointer equality but the error message is less readable. // Could use pointer equality but the error message is less readable.
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section]; NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
id viewModel = viewModels[indexPath.item]; id nodeModel = nodeModels[indexPath.item];
XCTAssertEqualObjects(viewModel, [collectionNode viewModelForItemAtIndexPath:indexPath]); XCTAssertEqualObjects(nodeModel, [collectionNode nodeModelForItemAtIndexPath:indexPath]);
ASCellNode *node = [collectionNode nodeForItemAtIndexPath:indexPath]; ASCellNode *node = [collectionNode nodeForItemAtIndexPath:indexPath];
XCTAssertEqualObjects(node.viewModel, viewModel); XCTAssertEqualObjects(node.nodeModel, nodeModel);
} }
} }
} }
@ -263,7 +263,7 @@
* Updates the collection node, with expectations and assertions about the call-order and the correctness of the * Updates the collection node, with expectations and assertions about the call-order and the correctness of the
* new data. You should update the data source _before_ calling this method. * new data. You should update the data source _before_ calling this method.
* *
* skippedReloadIndexPaths are the old index paths for nodes that should use -canUpdateToViewModel: instead of being refetched. * skippedReloadIndexPaths are the old index paths for nodes that should use -canUpdateToNodeModel: instead of being refetched.
*/ */
- (void)performUpdateReloadingSections:(NSDictionary<NSNumber *, id> *)reloadedSections - (void)performUpdateReloadingSections:(NSDictionary<NSNumber *, id> *)reloadedSections
reloadingItems:(NSDictionary<NSIndexPath *, id> *)reloadedItems reloadingItems:(NSDictionary<NSIndexPath *, id> *)reloadedItems
@ -275,7 +275,7 @@
[collectionNode performBatchUpdates:^{ [collectionNode performBatchUpdates:^{
// First update our data source. // First update our data source.
[reloadedItems enumerateKeysAndObjectsUsingBlock:^(NSIndexPath * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { [reloadedItems enumerateKeysAndObjectsUsingBlock:^(NSIndexPath * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
sections[key.section].viewModels[key.item] = obj; sections[key.section].nodeModels[key.item] = obj;
}]; }];
[reloadedSections enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { [reloadedSections enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
sections[key.integerValue] = obj; sections[key.integerValue] = obj;
@ -283,13 +283,13 @@
// Deletion paths, sorted descending // Deletion paths, sorted descending
for (NSIndexPath *indexPath in [deletedItems sortedArrayUsingSelector:@selector(compare:)].reverseObjectEnumerator) { for (NSIndexPath *indexPath in [deletedItems sortedArrayUsingSelector:@selector(compare:)].reverseObjectEnumerator) {
[sections[indexPath.section].viewModels removeObjectAtIndex:indexPath.item]; [sections[indexPath.section].nodeModels removeObjectAtIndex:indexPath.item];
} }
// Insertion paths, sorted ascending. // Insertion paths, sorted ascending.
NSArray *insertionsSortedAcending = [insertedItems.allKeys sortedArrayUsingSelector:@selector(compare:)]; NSArray *insertionsSortedAcending = [insertedItems.allKeys sortedArrayUsingSelector:@selector(compare:)];
for (NSIndexPath *indexPath in insertionsSortedAcending) { for (NSIndexPath *indexPath in insertionsSortedAcending) {
[sections[indexPath.section].viewModels insertObject:insertedItems[indexPath] atIndex:indexPath.item]; [sections[indexPath.section].nodeModels insertObject:insertedItems[indexPath] atIndex:indexPath.item];
} }
// Then update the collection node. // Then update the collection node.
@ -314,10 +314,10 @@
// Go through reloaded sections and add all their items into `insertsPlusReloads` // Go through reloaded sections and add all their items into `insertsPlusReloads`
[reloadedSectionIndexes enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) { [reloadedSectionIndexes enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) {
[self expectContextMethodForSection:section]; [self expectContextMethodForSection:section];
NSArray *viewModels = sections[section].viewModels; NSArray *nodeModels = sections[section].nodeModels;
for (NSInteger i = 0; i < viewModels.count; i++) { for (NSInteger i = 0; i < nodeModels.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section]; NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
insertsPlusReloads[indexPath] = viewModels[i]; insertsPlusReloads[indexPath] = nodeModels[i];
} }
}]; }];
@ -326,7 +326,7 @@
}]; }];
for (NSIndexPath *indexPath in [insertsPlusReloads.allKeys sortedArrayUsingSelector:@selector(compare:)]) { for (NSIndexPath *indexPath in [insertsPlusReloads.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
[self expectViewModelMethodForItemAtIndexPath:indexPath viewModel:insertsPlusReloads[indexPath]]; [self expectNodeModelMethodForItemAtIndexPath:indexPath nodeModel:insertsPlusReloads[indexPath]];
NSIndexPath *oldIndexPath = [reloadMappings allKeysForObject:indexPath].firstObject; NSIndexPath *oldIndexPath = [reloadMappings allKeysForObject:indexPath].firstObject;
BOOL isSkippedReload = oldIndexPath && [skippedReloadIndexPaths containsObject:oldIndexPath]; BOOL isSkippedReload = oldIndexPath && [skippedReloadIndexPaths containsObject:oldIndexPath];
if (!isSkippedReload) { if (!isSkippedReload) {
@ -335,7 +335,7 @@
} }
} completion:nil]; } completion:nil];
// Assert that the counts and view models are all correct now. // Assert that the counts and node models are all correct now.
[self assertCollectionNodeContent]; [self assertCollectionNodeContent];
} }
@ -345,9 +345,9 @@
@implementation ASTestCellNode @implementation ASTestCellNode
- (BOOL)canUpdateToViewModel:(id)viewModel - (BOOL)canUpdateToNodeModel:(id)nodeModel
{ {
// Our tests default to NO for migrating view models. We use OCMExpect to return YES when we specifically want to. // Our tests default to NO for migrating node models. We use OCMExpect to return YES when we specifically want to.
return NO; return NO;
} }
@ -360,7 +360,7 @@
- (instancetype)init - (instancetype)init
{ {
if (self = [super init]) { if (self = [super init]) {
_viewModels = [NSMutableArray array]; _nodeModels = [NSMutableArray array];
} }
return self; return self;
} }

View File

@ -260,7 +260,7 @@
[window makeKeyAndVisible]; [window makeKeyAndVisible];
[testController.collectionNode reloadData]; [testController.collectionNode reloadData];
[testController.collectionNode waitUntilAllUpdatesAreCommitted]; [testController.collectionNode waitUntilAllUpdatesAreProcessed];
[testController.collectionView layoutIfNeeded]; [testController.collectionView layoutIfNeeded];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
@ -397,7 +397,7 @@
window.rootViewController = testController;\ window.rootViewController = testController;\
\ \
[cn reloadData];\ [cn reloadData];\
[cn waitUntilAllUpdatesAreCommitted]; \ [cn waitUntilAllUpdatesAreProcessed]; \
[testController.collectionView layoutIfNeeded]; [testController.collectionView layoutIfNeeded];
- (void)testThatSubmittingAValidInsertDoesNotThrowAnException - (void)testThatSubmittingAValidInsertDoesNotThrowAnException
@ -620,7 +620,7 @@
[window makeKeyAndVisible]; [window makeKeyAndVisible];
for (NSInteger i = 0; i < 2; i++) { for (NSInteger i = 0; i < 2; i++) {
// NOTE: waitUntilAllUpdatesAreCommitted or reloadDataImmediately is not sufficient here!! // NOTE: waitUntilAllUpdatesAreProcessed or reloadDataImmediately is not sufficient here!!
XCTestExpectation *done = [self expectationWithDescription:[NSString stringWithFormat:@"Reload #%td complete", i]]; XCTestExpectation *done = [self expectationWithDescription:[NSString stringWithFormat:@"Reload #%td complete", i]];
[cn reloadDataWithCompletion:^{ [cn reloadDataWithCompletion:^{
[done fulfill]; [done fulfill];
@ -755,7 +755,7 @@
del.sectionGeneration++; del.sectionGeneration++;
[cn reloadData]; [cn reloadData];
[cn waitUntilAllUpdatesAreCommitted]; [cn waitUntilAllUpdatesAreProcessed];
NSInteger sectionCount = del->_itemCounts.size(); NSInteger sectionCount = del->_itemCounts.size();
for (NSInteger section = 0; section < sectionCount; section++) { for (NSInteger section = 0; section < sectionCount; section++) {
@ -857,7 +857,7 @@
[window layoutIfNeeded]; [window layoutIfNeeded];
ASCollectionNode *cn = testController.collectionNode; ASCollectionNode *cn = testController.collectionNode;
[cn waitUntilAllUpdatesAreCommitted]; [cn waitUntilAllUpdatesAreProcessed];
[cn.view layoutIfNeeded]; [cn.view layoutIfNeeded];
ASCellNode *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASCellNode *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
XCTAssertTrue(node.visible); XCTAssertTrue(node.visible);
@ -880,7 +880,7 @@
[window layoutIfNeeded]; [window layoutIfNeeded];
ASCollectionNode *cn = testController.collectionNode; ASCollectionNode *cn = testController.collectionNode;
[cn waitUntilAllUpdatesAreCommitted]; [cn waitUntilAllUpdatesAreProcessed];
XCTAssertGreaterThan(cn.bounds.size.height, cn.view.contentSize.height, @"Expected initial data not to fill collection view area."); XCTAssertGreaterThan(cn.bounds.size.height, cn.view.contentSize.height, @"Expected initial data not to fill collection view area.");
__block NSUInteger batchFetchCount = 0; __block NSUInteger batchFetchCount = 0;
@ -926,7 +926,7 @@
[window layoutIfNeeded]; [window layoutIfNeeded];
ASCollectionNode *cn = testController.collectionNode; ASCollectionNode *cn = testController.collectionNode;
[cn waitUntilAllUpdatesAreCommitted]; [cn waitUntilAllUpdatesAreProcessed];
__block NSUInteger batchFetchCount = 0; __block NSUInteger batchFetchCount = 0;
XCTestExpectation *e = [self expectationWithDescription:@"Batch fetching completed"]; XCTestExpectation *e = [self expectationWithDescription:@"Batch fetching completed"];
@ -1020,7 +1020,7 @@
[view layoutIfNeeded]; [view layoutIfNeeded];
// Wait for ASDK reload to finish // Wait for ASDK reload to finish
[cn waitUntilAllUpdatesAreCommitted]; [cn waitUntilAllUpdatesAreProcessed];
// Force UIKit to read updated data & range controller to update and account for it // Force UIKit to read updated data & range controller to update and account for it
[cn.view layoutIfNeeded]; [cn.view layoutIfNeeded];
[self waitForExpectationsWithTimeout:60 handler:nil]; [self waitForExpectationsWithTimeout:60 handler:nil];
@ -1050,8 +1050,17 @@
// Trigger the initial reload to start // Trigger the initial reload to start
[window layoutIfNeeded]; [window layoutIfNeeded];
// Test the APIs that monitor ASCollectionNode update handling
XCTAssertTrue(cn.isProcessingUpdates, @"ASCollectionNode should still be processing updates after initial layoutIfNeeded call (reloadData)");
[cn onDidFinishProcessingUpdates:^{
XCTAssertTrue(!cn.isProcessingUpdates, @"ASCollectionNode should no longer be processing updates inside -onDidFinishProcessingUpdates: block");
}];
// Wait for ASDK reload to finish // Wait for ASDK reload to finish
[cn waitUntilAllUpdatesAreCommitted]; [cn waitUntilAllUpdatesAreProcessed];
XCTAssertTrue(!cn.isProcessingUpdates, @"ASCollectionNode should no longer be processing updates after -wait call");
// Force UIKit to read updated data & range controller to update and account for it // Force UIKit to read updated data & range controller to update and account for it
[cn.view layoutIfNeeded]; [cn.view layoutIfNeeded];
@ -1093,7 +1102,7 @@
traitCollection.containerSize = screenBounds.size; traitCollection.containerSize = screenBounds.size;
cn.primitiveTraitCollection = traitCollection; cn.primitiveTraitCollection = traitCollection;
[cn waitUntilAllUpdatesAreCommitted]; [cn waitUntilAllUpdatesAreProcessed];
[cn.view layoutIfNeeded]; [cn.view layoutIfNeeded];
// Assert that the new trait collection is picked up by all cell nodes, including ones that were not allocated but are forced to allocate now // Assert that the new trait collection is picked up by all cell nodes, including ones that were not allocated but are forced to allocate now
@ -1124,7 +1133,7 @@
[window makeKeyAndVisible]; [window makeKeyAndVisible];
[window layoutIfNeeded]; [window layoutIfNeeded];
[cn waitUntilAllUpdatesAreCommitted]; [cn waitUntilAllUpdatesAreProcessed];
for (NSInteger i = 0; i < itemCount; i++) { for (NSInteger i = 0; i < itemCount; i++) {
ASTextCellNodeWithSetSelectedCounter *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; ASTextCellNodeWithSetSelectedCounter *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
XCTAssert(node.automaticallyManagesSubnodes, @"Expected test cell node to use automatic subnode management. Can modify the test with a different class if needed."); XCTAssert(node.automaticallyManagesSubnodes, @"Expected test cell node to use automatic subnode management. Can modify the test with a different class if needed.");

View File

@ -2,8 +2,17 @@
// ASPagerNodeTests.m // ASPagerNodeTests.m
// Texture // Texture
// //
// Created by Luke Parham on 11/6/16. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// Copyright © 2016 Facebook. 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> #import <XCTest/XCTest.h>
@ -128,7 +137,7 @@
#pragma clang diagnostic pop #pragma clang diagnostic pop
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame)); XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame));
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame)); XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame));
XCTAssertEqual(pagerNode.view.contentOffset.y, 0); XCTAssertEqual(pagerNode.contentOffset.y, 0);
XCTAssertEqual(pagerNode.view.contentInset.top, 0); XCTAssertEqual(pagerNode.view.contentInset.top, 0);
e = [self expectationWithDescription:@"Transition completed"]; e = [self expectationWithDescription:@"Transition completed"];
@ -158,7 +167,7 @@
#pragma clang diagnostic pop #pragma clang diagnostic pop
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame)); XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame));
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame)); XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame));
XCTAssertEqual(pagerNode.view.contentOffset.y, 0); XCTAssertEqual(pagerNode.contentOffset.y, 0);
XCTAssertEqual(pagerNode.view.contentInset.top, 0); XCTAssertEqual(pagerNode.view.contentInset.top, 0);
} }

View File

@ -110,6 +110,7 @@ static NSArray<ASTextNode*> *defaultTextNodes()
alignItems:style.alignItems alignItems:style.alignItems
flexWrap:style.flexWrap flexWrap:style.flexWrap
alignContent:style.alignContent alignContent:style.alignContent
lineSpacing:style.lineSpacing
children:children]; children:children];
[self testStackLayoutSpec:stackLayoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier]; [self testStackLayoutSpec:stackLayoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier];
@ -163,6 +164,7 @@ static NSArray<ASTextNode*> *defaultTextNodes()
} }
- (void)testStackLayoutSpecWithAlignContent:(ASStackLayoutAlignContent)alignContent - (void)testStackLayoutSpecWithAlignContent:(ASStackLayoutAlignContent)alignContent
lineSpacing:(CGFloat)lineSpacing
sizeRange:(ASSizeRange)sizeRange sizeRange:(ASSizeRange)sizeRange
identifier:(NSString *)identifier identifier:(NSString *)identifier
{ {
@ -170,8 +172,9 @@ static NSArray<ASTextNode*> *defaultTextNodes()
.direction = ASStackLayoutDirectionHorizontal, .direction = ASStackLayoutDirectionHorizontal,
.flexWrap = ASStackLayoutFlexWrapWrap, .flexWrap = ASStackLayoutFlexWrapWrap,
.alignContent = alignContent, .alignContent = alignContent,
.lineSpacing = lineSpacing,
}; };
CGSize subnodeSize = {50, 50}; CGSize subnodeSize = {50, 50};
NSArray<ASDisplayNode *> *subnodes = @[ NSArray<ASDisplayNode *> *subnodes = @[
ASDisplayNodeWithBackgroundColor([UIColor redColor], subnodeSize), ASDisplayNodeWithBackgroundColor([UIColor redColor], subnodeSize),
@ -181,10 +184,17 @@ static NSArray<ASTextNode*> *defaultTextNodes()
ASDisplayNodeWithBackgroundColor([UIColor greenColor], subnodeSize), ASDisplayNodeWithBackgroundColor([UIColor greenColor], subnodeSize),
ASDisplayNodeWithBackgroundColor([UIColor cyanColor], subnodeSize), ASDisplayNodeWithBackgroundColor([UIColor cyanColor], subnodeSize),
]; ];
[self testStackLayoutSpecWithStyle:style sizeRange:sizeRange subnodes:subnodes identifier:identifier]; [self testStackLayoutSpecWithStyle:style sizeRange:sizeRange subnodes:subnodes identifier:identifier];
} }
- (void)testStackLayoutSpecWithAlignContent:(ASStackLayoutAlignContent)alignContent
sizeRange:(ASSizeRange)sizeRange
identifier:(NSString *)identifier
{
[self testStackLayoutSpecWithAlignContent:alignContent lineSpacing:0.0 sizeRange:sizeRange identifier:identifier];
}
#pragma mark - #pragma mark -
- (void)testDefaultStackLayoutElementFlexProperties - (void)testDefaultStackLayoutElementFlexProperties
@ -1209,6 +1219,77 @@ static NSArray<ASTextNode*> *defaultTextNodes()
[self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:children identifier:nil]; [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:children identifier:nil];
} }
#pragma mark - Flex wrap and item spacings test
- (void)testFlexWrapWithItemSpacings
{
ASStackLayoutSpecStyle style = {
.spacing = 50,
.direction = ASStackLayoutDirectionHorizontal,
.flexWrap = ASStackLayoutFlexWrapWrap,
.alignContent = ASStackLayoutAlignContentStart,
.lineSpacing = 5,
};
CGSize subnodeSize = {50, 50};
NSArray<ASDisplayNode *> *subnodes = @[
ASDisplayNodeWithBackgroundColor([UIColor redColor], subnodeSize),
ASDisplayNodeWithBackgroundColor([UIColor yellowColor], subnodeSize),
ASDisplayNodeWithBackgroundColor([UIColor blueColor], subnodeSize),
];
for (ASDisplayNode *subnode in subnodes) {
subnode.style.spacingBefore = 5;
subnode.style.spacingAfter = 5;
}
// 3 items, each item has a size of {50, 50}
// width is 230px, enough to fit all items without taking all spacings into account
// Test that all spacings are included and therefore the last item is pushed to a second line.
// See: https://github.com/TextureGroup/Texture/pull/472
static ASSizeRange kSize = {{230, 300}, {230, 300}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testFlexWrapWithItemSpacingsBeingResetOnNewLines
{
ASStackLayoutSpecStyle style = {
.spacing = 5,
.direction = ASStackLayoutDirectionHorizontal,
.flexWrap = ASStackLayoutFlexWrapWrap,
.alignContent = ASStackLayoutAlignContentStart,
.lineSpacing = 5,
};
CGSize subnodeSize = {50, 50};
NSArray<ASDisplayNode *> *subnodes = @[
// 1st line
ASDisplayNodeWithBackgroundColor([UIColor redColor], subnodeSize),
ASDisplayNodeWithBackgroundColor([UIColor yellowColor], subnodeSize),
ASDisplayNodeWithBackgroundColor([UIColor blueColor], subnodeSize),
// 2nd line
ASDisplayNodeWithBackgroundColor([UIColor magentaColor], subnodeSize),
ASDisplayNodeWithBackgroundColor([UIColor greenColor], subnodeSize),
ASDisplayNodeWithBackgroundColor([UIColor cyanColor], subnodeSize),
// 3rd line
ASDisplayNodeWithBackgroundColor([UIColor brownColor], subnodeSize),
ASDisplayNodeWithBackgroundColor([UIColor orangeColor], subnodeSize),
ASDisplayNodeWithBackgroundColor([UIColor purpleColor], subnodeSize),
];
for (ASDisplayNode *subnode in subnodes) {
subnode.style.spacingBefore = 5;
subnode.style.spacingAfter = 5;
}
// 3 lines, each line has 3 items, each item has a size of {50, 50}
// width is 190px, enough to fit 3 items into a line
// Test that interitem spacing is reset on new lines. Otherwise, lines after the 1st line would have only 2 items.
// See: https://github.com/TextureGroup/Texture/pull/472
static ASSizeRange kSize = {{190, 300}, {190, 300}};
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
#pragma mark - Content alignment tests #pragma mark - Content alignment tests
- (void)testAlignContentUnderflow - (void)testAlignContentUnderflow
@ -1282,4 +1363,33 @@ static NSArray<ASTextNode*> *defaultTextNodes()
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
} }
#pragma mark - Line spacing tests
- (void)testAlignContentAndLineSpacingUnderflow
{
// 3 lines, each line has 2 items, each item has a size of {50, 50}
// 10px between lines
// width is 110px. It's 10px bigger than the required width of each line (110px vs 100px) to test that items are still correctly collected into lines
static ASSizeRange kSize = {{110, 320}, {110, 320}};
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentStart lineSpacing:10 sizeRange:kSize identifier:@"alignContentStart"];
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentCenter lineSpacing:10 sizeRange:kSize identifier:@"alignContentCenter"];
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentEnd lineSpacing:10 sizeRange:kSize identifier:@"alignContentEnd"];
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceBetween lineSpacing:10 sizeRange:kSize identifier:@"alignContentSpaceBetween"];
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceAround lineSpacing:10 sizeRange:kSize identifier:@"alignContentSpaceAround"];
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentStretch lineSpacing:10 sizeRange:kSize identifier:@"alignContentStretch"];
}
- (void)testAlignContentAndLineSpacingOverflow
{
// 6 lines, each line has 1 item, each item has a size of {50, 50}
// 10px between lines
// width is 40px. It's 10px smaller than the width of each item (40px vs 50px) to test that items are still correctly collected into lines
static ASSizeRange kSize = {{40, 310}, {40, 310}};
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentStart lineSpacing:10 sizeRange:kSize identifier:@"alignContentStart"];
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentCenter lineSpacing:10 sizeRange:kSize identifier:@"alignContentCenter"];
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentEnd lineSpacing:10 sizeRange:kSize identifier:@"alignContentEnd"];
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceBetween lineSpacing:10 sizeRange:kSize identifier:@"alignContentSpaceBetween"];
[self testStackLayoutSpecWithAlignContent:ASStackLayoutAlignContentSpaceAround lineSpacing:10 sizeRange:kSize identifier:@"alignContentSpaceAround"];
}
@end @end

View File

@ -610,7 +610,7 @@
[UITableView as_recordEditingCallsIntoArray:selectors]; [UITableView as_recordEditingCallsIntoArray:selectors];
XCTAssertGreaterThan(node.numberOfSections, 0); XCTAssertGreaterThan(node.numberOfSections, 0);
[node waitUntilAllUpdatesAreCommitted]; [node waitUntilAllUpdatesAreProcessed];
XCTAssertGreaterThan(node.view.numberOfSections, 0); XCTAssertGreaterThan(node.view.numberOfSections, 0);
// The first reloadData call helps prevent UITableView from calling it multiple times while ASDataController is working. // The first reloadData call helps prevent UITableView from calling it multiple times while ASDataController is working.
@ -635,13 +635,13 @@
// Load initial data. // Load initial data.
XCTAssertGreaterThan(node.numberOfSections, 0); XCTAssertGreaterThan(node.numberOfSections, 0);
[node waitUntilAllUpdatesAreCommitted]; [node waitUntilAllUpdatesAreProcessed];
XCTAssertGreaterThan(node.view.numberOfSections, 0); XCTAssertGreaterThan(node.view.numberOfSections, 0);
// Reload data. // Reload data.
[UITableView as_recordEditingCallsIntoArray:selectors]; [UITableView as_recordEditingCallsIntoArray:selectors];
[node reloadData]; [node reloadData];
[node waitUntilAllUpdatesAreCommitted]; [node waitUntilAllUpdatesAreProcessed];
// Assert that the beginning of the call pattern is correct. // Assert that the beginning of the call pattern is correct.
// There is currently noise that comes after that we will allow for this test. // There is currently noise that comes after that we will allow for this test.
@ -668,7 +668,7 @@
// Trigger data load BEFORE first layout pass, to ensure constrained size is correct. // Trigger data load BEFORE first layout pass, to ensure constrained size is correct.
XCTAssertGreaterThan(node.numberOfSections, 0); XCTAssertGreaterThan(node.numberOfSections, 0);
[node waitUntilAllUpdatesAreCommitted]; [node waitUntilAllUpdatesAreProcessed];
ASSizeRange expectedSizeRange = ASSizeRangeMake(CGSizeMake(cellWidth, 0)); ASSizeRange expectedSizeRange = ASSizeRangeMake(CGSizeMake(cellWidth, 0));
expectedSizeRange.max.height = CGFLOAT_MAX; expectedSizeRange.max.height = CGFLOAT_MAX;
@ -703,7 +703,7 @@
// So we need to force a new layout pass so that the table will pick up a new constrained size and apply to its node. // So we need to force a new layout pass so that the table will pick up a new constrained size and apply to its node.
[node setNeedsLayout]; [node setNeedsLayout];
[node.view layoutIfNeeded]; [node.view layoutIfNeeded];
[node waitUntilAllUpdatesAreCommitted]; [node waitUntilAllUpdatesAreProcessed];
UITableViewCell *cell = [node.view cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; UITableViewCell *cell = [node.view cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
XCTAssertNotNil(cell); XCTAssertNotNil(cell);
@ -758,7 +758,7 @@
[window makeKeyAndVisible]; [window makeKeyAndVisible];
[window layoutIfNeeded]; [window layoutIfNeeded];
[node waitUntilAllUpdatesAreCommitted]; [node waitUntilAllUpdatesAreProcessed];
XCTAssertEqual(node.view.numberOfSections, NumberOfSections); XCTAssertEqual(node.view.numberOfSections, NumberOfSections);
ASXCTAssertEqualRects(CGRectMake(0, 32, 375, 44), [node rectForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]], @"This text requires very specific geometry. The rect for the first row should match up."); ASXCTAssertEqualRects(CGRectMake(0, 32, 375, 44), [node rectForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]], @"This text requires very specific geometry. The rect for the first row should match up.");
@ -812,10 +812,10 @@
node.dataSource = ds; node.dataSource = ds;
[node.view layoutIfNeeded]; [node.view layoutIfNeeded];
[node waitUntilAllUpdatesAreCommitted]; [node waitUntilAllUpdatesAreProcessed];
CGFloat rowHeight = [node.view rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].size.height; CGFloat rowHeight = [node.view rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].size.height;
// Scroll to row (0,1) + 10pt // Scroll to row (0,1) + 10pt
node.view.contentOffset = CGPointMake(0, rowHeight + 10); node.contentOffset = CGPointMake(0, rowHeight + 10);
[node performBatchAnimated:NO updates:^{ [node performBatchAnimated:NO updates:^{
// Delete row 0 from all sections. // Delete row 0 from all sections.
@ -825,11 +825,11 @@
[node deleteRowsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:i]] withRowAnimation:UITableViewRowAnimationAutomatic]; [node deleteRowsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:i]] withRowAnimation:UITableViewRowAnimationAutomatic];
} }
} completion:nil]; } completion:nil];
[node waitUntilAllUpdatesAreCommitted]; [node waitUntilAllUpdatesAreProcessed];
// Now that row (0,0) is deleted, we should have slid up to be at just 10 // Now that row (0,0) is deleted, we should have slid up to be at just 10
// i.e. we should have subtracted the deleted row height from our content offset. // i.e. we should have subtracted the deleted row height from our content offset.
XCTAssertEqual(node.view.contentOffset.y, 10); XCTAssertEqual(node.contentOffset.y, 10);
} }
@end @end

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |spec| Pod::Spec.new do |spec|
spec.name = 'Texture' spec.name = 'Texture'
spec.version = '2.3.4' spec.version = '2.4'
spec.license = { :type => 'BSD and Apache 2', } spec.license = { :type => 'BSD and Apache 2', }
spec.homepage = 'http://texturegroup.org' spec.homepage = 'http://texturegroup.org'
spec.authors = { 'Huy Nguyen' => 'huy@pinterest.com', 'Garrett Moon' => 'garrett@excitedpixel.com', 'Scott Goodson' => 'scottgoodson@gmail.com', 'Michael Schneider' => 'schneider@pinterest.com', 'Adlai Holler' => 'adlai@pinterest.com' } spec.authors = { 'Huy Nguyen' => 'huy@pinterest.com', 'Garrett Moon' => 'garrett@excitedpixel.com', 'Scott Goodson' => 'scottgoodson@gmail.com', 'Michael Schneider' => 'schneider@pinterest.com', 'Adlai Holler' => 'adlai@pinterest.com' }

View File

@ -17,7 +17,7 @@ When it comes to corner rounding, many developers stick with CALayer's `.cornerR
## CALayer's .cornerRadius is Expensive ## CALayer's .cornerRadius is Expensive
Why is `.cornerRadius` so expensive? Use of CALayer's `.cornerRadius` property triggers off-screen rendering to perform the clipping operation on every frame - 60 FPS during scrolling - even if the content in that area isn't changing! This means that the GPU has to switch contexts on every frame, between compositing the overall frame + additional passes for each use of `.cornerRadius`. Why is `.cornerRadius` so expensive? Use of CALayer's `.cornerRadius` property triggers offscreen rendering to perform the clipping operation on every frame - 60 FPS during scrolling - even if the content in that area isn't changing! This means that the GPU has to switch contexts on every frame, between compositing the overall frame + additional passes for each use of `.cornerRadius`.
Importantly, these costs don't show up in the Time Profiler, because they affect work done by the CoreAnimation Render Server on your app's behalf. This intensive thrash annihilates performance for a lot of devices. On the iPhone 4, 4S, and 5 / 5C (along with comparable iPads / iPods), expect to see notably degraded performance. On the iPhone 5S and newer, even if you can't see the impact directly, it will reduce headroom so that it takes less to cause a frame drop. Importantly, these costs don't show up in the Time Profiler, because they affect work done by the CoreAnimation Render Server on your app's behalf. This intensive thrash annihilates performance for a lot of devices. On the iPhone 4, 4S, and 5 / 5C (along with comparable iPads / iPods), expect to see notably degraded performance. On the iPhone 5S and newer, even if you can't see the impact directly, it will reduce headroom so that it takes less to cause a frame drop.
@ -53,11 +53,11 @@ The final consideration is to determine if all four corners cover the same node
### Precomposited Corners ### Precomposited Corners
Precomposited corners refer to corners drawn using bezier paths to clip the content in a CGContext / UIGraphicsContext. In this scenario, the corners become part of the image itself — and are "baked in" to the single CALayer. There are two types of precomposited corners. Precomposited corners refer to corners drawn using bezier paths to clip the content in a CGContext / UIGraphicsContext (`[path clip]`). In this scenario, the corners become part of the image itself — and are "baked in" to the single CALayer. There are two types of precomposited corners.
The absolute best method is to use **precomposited opaque corners**. This is the most efficient method available, resulting in zero alpha blending (although this is much less critical than avoiding offscreen rendering). Unfortunately, this method is also the least flexible; the background behind the corners will need to be a solid color if the rounded image needs to move around on top of it. It's possible, but tricky to make precomposited corners with a textured or photo background - usually it's best to use precomposited alpha corners instead'.' The absolute best method is to use **precomposited opaque corners**. This is the most efficient method available, resulting in zero alpha blending (although this is much less critical than avoiding offscreen rendering). Unfortunately, this method is also the least flexible; the background behind the corners will need to be a solid color if the rounded image needs to move around on top of it. It's possible, but tricky to make precomposited corners with a textured or photo background - usually it's best to use precomposited alpha corners instead'.'
The second method involves using bezier paths with **precomposited alpha corners** (`[path clip]`). This method is pretty flexible and should be one of the most frequently used. It does incur the cost of alpha blending across the full size of the content, and including an alpha channel increases memory impact by 25% over opaque precompositing - but these costs are tiny on modern devices, and a different order of magnitude than `.cornerRadius` offscreen rendering. The second method involves using bezier paths with **precomposited alpha corners**. This method is pretty flexible and should be one of the most frequently used. It does incur the cost of alpha blending across the full size of the content, and including an alpha channel increases memory impact by 25% over opaque precompositing - but these costs are tiny on modern devices, and a different order of magnitude than `.cornerRadius` offscreen rendering.
A key limitation of precomposited corners is that the corners must only touch one node and not intersect with any subnodes. If either of these conditions exist, clip corners must be used. A key limitation of precomposited corners is that the corners must only touch one node and not intersect with any subnodes. If either of these conditions exist, clip corners must be used.

View File

@ -19,7 +19,7 @@ The most important thing to remember is that your init method must be capable of
### `-didLoad` ### `-didLoad`
This method is conceptually similar to UIViewController's `-viewDidLoad` method; its called once and is the point where the backing view has been loaded. It is guaranteed to be called on the **main thread** and is the appropriate place to do any UIKit things (such as adding gesture recognizers, touching the view / layer, initializing UIKIt objects). This method is conceptually similar to UIViewController's `-viewDidLoad` method; its called once and is the point where the backing view has been loaded. It is guaranteed to be called on the **main thread** and is the appropriate place to do any UIKit things (such as adding gesture recognizers, touching the view / layer, initializing UIKit objects).
### `-layoutSpecThatFits:` ### `-layoutSpecThatFits:`

View File

@ -125,6 +125,8 @@ permalink: /showcase.html
<a href="https://itunes.apple.com/us/app/classdojo/id552602056"><img class="roundrect" src="http://is4.mzstatic.com/image/thumb/Purple122/v4/83/b1/ef/83b1efa6-bda8-3c80-fd66-4a461f638e07/source/350x350bb.jpg" style="width:100px;height:100px;"></a> <a href="https://itunes.apple.com/us/app/classdojo/id552602056"><img class="roundrect" src="http://is4.mzstatic.com/image/thumb/Purple122/v4/83/b1/ef/83b1efa6-bda8-3c80-fd66-4a461f638e07/source/350x350bb.jpg" style="width:100px;height:100px;"></a>
<br /> <br />
<b>ClassDojo</b> <b>ClassDojo</b>
<br>
<a href="https://engineering.classdojo.com/blog/2017/07/12/asyncdisplaykit">Powering Class Story With Texture</a>
</td> </td>
</tr> </tr>

View File

@ -16,14 +16,14 @@
// //
#import "ViewController.h" #import "ViewController.h"
#import "AppDelegate.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h> #import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "SupplementaryNode.h" #import "SupplementaryNode.h"
#import "ItemNode.h" #import "ItemNode.h"
#define ASYNC_COLLECTION_LAYOUT 0 #define ASYNC_COLLECTION_LAYOUT 0
@interface ViewController () <ASCollectionDataSource, ASCollectionDelegateFlowLayout, ASCollectionGalleryLayoutSizeProviding> @interface ViewController () <ASCollectionDataSource, ASCollectionDelegateFlowLayout, ASCollectionGalleryLayoutPropertiesProviding>
@property (nonatomic, strong) ASCollectionNode *collectionNode; @property (nonatomic, strong) ASCollectionNode *collectionNode;
@property (nonatomic, strong) NSArray *data; @property (nonatomic, strong) NSArray *data;
@ -48,13 +48,15 @@
#if ASYNC_COLLECTION_LAYOUT #if ASYNC_COLLECTION_LAYOUT
ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionVerticalDirections]; ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionVerticalDirections];
layoutDelegate.sizeProvider = self; layoutDelegate.propertiesProvider = self;
self.collectionNode = [[ASCollectionNode alloc] initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil]; self.collectionNode = [[ASCollectionNode alloc] initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil];
#else #else
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.headerReferenceSize = CGSizeMake(50.0, 50.0); layout.headerReferenceSize = CGSizeMake(50.0, 50.0);
layout.footerReferenceSize = CGSizeMake(50.0, 50.0); layout.footerReferenceSize = CGSizeMake(50.0, 50.0);
self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
[self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
[self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter];
#endif #endif
self.collectionNode.dataSource = self; self.collectionNode.dataSource = self;
@ -82,18 +84,18 @@
{ {
NSLog(@"ViewController is not nil"); NSLog(@"ViewController is not nil");
strongSelf->_data = [[NSArray alloc] init]; strongSelf->_data = [[NSArray alloc] init];
[strongSelf->_collectionView performBatchUpdates:^{ [strongSelf->_collectionNode performBatchUpdates:^{
[strongSelf->_collectionView insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]]; [strongSelf->_collectionNode insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]];
} completion:nil]; } completion:nil];
NSLog(@"ViewController finished updating collectionView"); NSLog(@"ViewController finished updating collectionNode");
} }
else { else {
NSLog(@"ViewController is nil - won't update collectionView"); NSLog(@"ViewController is nil - won't update collectionNode");
} }
}; };
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES]; [self.navigationController popViewControllerAnimated:YES];
}); });
#endif #endif
@ -108,7 +110,7 @@
[self.collectionNode reloadData]; [self.collectionNode reloadData];
} }
#pragma mark - ASCollectionGalleryLayoutSizeProviding #pragma mark - ASCollectionGalleryLayoutPropertiesProviding
- (CGSize)sizeForElements:(ASElementMap *)elements - (CGSize)sizeForElements:(ASElementMap *)elements
{ {