Merge commit '19a9d29aa873edcf5624bed7fa9ee86662da0bfd'
# Conflicts: # Source/Details/ASCollectionGalleryLayoutDelegate.m
@ -429,9 +429,11 @@
|
||||
E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */; };
|
||||
E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */; };
|
||||
E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; };
|
||||
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, ); }; };
|
||||
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 */; };
|
||||
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, ); }; };
|
||||
@ -458,7 +460,7 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -917,6 +919,8 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -946,7 +950,7 @@
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
@ -1670,6 +1674,8 @@
|
||||
E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */,
|
||||
E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.m */,
|
||||
E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */,
|
||||
E5667E8B1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h */,
|
||||
E5667E8D1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.m */,
|
||||
E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */,
|
||||
E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */,
|
||||
);
|
||||
@ -1687,7 +1693,7 @@
|
||||
E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */,
|
||||
E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */,
|
||||
E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */,
|
||||
E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.m */,
|
||||
E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */,
|
||||
E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */,
|
||||
E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */,
|
||||
);
|
||||
@ -1824,6 +1830,8 @@
|
||||
CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */,
|
||||
E5775B021F16759300CAC9BC /* ASCollectionLayoutCache.h in Headers */,
|
||||
E5775B001F13D25400CAC9BC /* ASCollectionLayoutState+Private.h in Headers */,
|
||||
E5667E8C1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h in Headers */,
|
||||
E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */,
|
||||
E5855DF01EBB4D83003639AE /* ASCollectionLayoutDefines.h in Headers */,
|
||||
E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */,
|
||||
9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */,
|
||||
@ -1896,7 +1904,6 @@
|
||||
254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */,
|
||||
B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */,
|
||||
CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */,
|
||||
E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */,
|
||||
25E327571C16819500A2170C /* ASPagerNode.h in Headers */,
|
||||
CCCCCCDB1EC3EF060087FE10 /* ASTextLine.h in Headers */,
|
||||
9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */,
|
||||
@ -2254,6 +2261,7 @@
|
||||
CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.m in Sources */,
|
||||
34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */,
|
||||
B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */,
|
||||
E5667E8E1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.m in Sources */,
|
||||
25E327591C16819500A2170C /* ASPagerNode.m in Sources */,
|
||||
636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */,
|
||||
B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */,
|
||||
@ -2289,7 +2297,7 @@
|
||||
CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.m in Sources */,
|
||||
CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.m in Sources */,
|
||||
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */,
|
||||
E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.m in Sources */,
|
||||
E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm in Sources */,
|
||||
34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */,
|
||||
CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */,
|
||||
254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */,
|
||||
|
||||
28
CHANGELOG.md
@ -1,24 +1,34 @@
|
||||
## master
|
||||
|
||||
* 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)
|
||||
- [ASStackLayoutSpec] Fix flex wrap overflow in some cases using item spacing. [Flo Vouin](https://github.com/flovouin)
|
||||
- [ASNodeController] Add -nodeDidLayout callback. Allow switching retain behavior at runtime. [Scott Goodson](https://github.com/appleguy)
|
||||
- [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)
|
||||
- [ASCollectionNode] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. [#522](https://github.com/TextureGroup/Texture/pull/522) [Scott Goodson](https://github.com/appleguy)
|
||||
- [Accessibility] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. [#468][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)
|
||||
- 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)
|
||||
- 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)
|
||||
- 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 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)
|
||||
- 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
|
||||
- [Yoga] Rewrite YOGA_TREE_CONTIGUOUS mode with improved behavior and cleaner integration [Scott Goodson](https://github.com/appleguy)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
[
|
||||
"^plans/",
|
||||
"^docs/",
|
||||
"^CI/exclude-from-build.json$"
|
||||
]
|
||||
"^CI/exclude-from-build.json$",
|
||||
"^**/*.md$"
|
||||
]
|
||||
|
||||
@ -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
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
- (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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -130,6 +130,20 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@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 node’s origin to the collection node’s 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.
|
||||
*
|
||||
@ -240,10 +254,39 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (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.
|
||||
*/
|
||||
- (void)waitUntilAllUpdatesAreCommitted;
|
||||
- (void)waitUntilAllUpdatesAreProcessed;
|
||||
|
||||
/**
|
||||
* Inserts one or more sections.
|
||||
@ -421,15 +464,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
- (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.
|
||||
@ -489,9 +532,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* @warning This method is substantially more expensive than UICollectionView's version.
|
||||
*
|
||||
* @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
|
||||
|
||||
@ -525,7 +570,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* @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:
|
||||
|
||||
@ -50,6 +50,8 @@
|
||||
@property (nonatomic, assign) BOOL usesSynchronousDataLoading;
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
|
||||
@property (weak, nonatomic) id <ASCollectionViewLayoutInspecting> layoutInspector;
|
||||
@property (nonatomic, assign) CGPoint contentOffset;
|
||||
@property (nonatomic, assign) BOOL animatesContentOffset;
|
||||
@end
|
||||
|
||||
@implementation _ASCollectionPendingState
|
||||
@ -62,6 +64,8 @@
|
||||
_allowsSelection = YES;
|
||||
_allowsMultipleSelection = NO;
|
||||
_inverted = NO;
|
||||
_contentOffset = CGPointZero;
|
||||
_animatesContentOffset = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -190,6 +194,8 @@
|
||||
if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) {
|
||||
[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.
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
return [self isNodeLoaded] ? self.view.scrollDirection : ASScrollDirectionNone;
|
||||
@ -595,10 +625,10 @@
|
||||
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node;
|
||||
}
|
||||
|
||||
- (id)viewModelForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
- (id)nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
[self reloadDataInitiallyIfNeeded];
|
||||
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].viewModel;
|
||||
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].nodeModel;
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
|
||||
@ -676,7 +706,21 @@
|
||||
[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();
|
||||
if (self.nodeLoaded) {
|
||||
@ -684,6 +728,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)waitUntilAllUpdatesAreCommitted
|
||||
{
|
||||
[self waitUntilAllUpdatesAreProcessed];
|
||||
}
|
||||
|
||||
- (void)reloadDataWithCompletion:(void (^)())completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
@ -709,7 +758,7 @@
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self reloadData];
|
||||
[self waitUntilAllUpdatesAreCommitted];
|
||||
[self waitUntilAllUpdatesAreProcessed];
|
||||
}
|
||||
|
||||
- (void)relayoutItems
|
||||
|
||||
@ -143,6 +143,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@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
|
||||
*
|
||||
@ -293,9 +298,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (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.
|
||||
@ -409,6 +416,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (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
|
||||
|
||||
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDataSource.")
|
||||
|
||||
@ -202,7 +202,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
unsigned int collectionViewNumberOfItemsInSection:1;
|
||||
unsigned int collectionNodeNodeForItem:1;
|
||||
unsigned int collectionNodeNodeBlockForItem:1;
|
||||
unsigned int viewModelForItem:1;
|
||||
unsigned int nodeModelForItem:1;
|
||||
unsigned int collectionNodeNodeForSupplementaryElement:1;
|
||||
unsigned int collectionNodeNodeBlockForSupplementaryElement:1;
|
||||
unsigned int collectionNodeSupplementaryElementKindsInSection:1;
|
||||
@ -359,6 +359,16 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
[_dataController relayoutAllNodes];
|
||||
}
|
||||
|
||||
- (BOOL)isProcessingUpdates
|
||||
{
|
||||
return [_dataController isProcessingUpdates];
|
||||
}
|
||||
|
||||
- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion
|
||||
{
|
||||
[_dataController onDidFinishProcessingUpdates:completion];
|
||||
}
|
||||
|
||||
- (void)waitUntilAllUpdatesAreCommitted
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
@ -367,8 +377,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd));
|
||||
return;
|
||||
}
|
||||
|
||||
[_dataController waitUntilAllUpdatesAreCommitted];
|
||||
|
||||
[_dataController waitUntilAllUpdatesAreProcessed];
|
||||
}
|
||||
|
||||
- (void)setDataSource:(id<UICollectionViewDataSource>)dataSource
|
||||
@ -431,7 +441,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)];
|
||||
_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)];
|
||||
_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)];
|
||||
if (_asyncDataSourceFlags.interop) {
|
||||
@ -1536,10 +1546,6 @@ minimumLineSpacingForSectionAtIndex:(NSInteger)section
|
||||
return [self.layoutInspector scrollableDirections];
|
||||
}
|
||||
|
||||
- (ASScrollDirection)flowLayoutScrollableDirections:(UICollectionViewFlowLayout *)flowLayout {
|
||||
return (flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (_cellsForLayoutUpdates.count > 0) {
|
||||
@ -1662,14 +1668,14 @@ minimumLineSpacingForSectionAtIndex:(NSInteger)section
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
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
|
||||
@ -2197,7 +2203,7 @@ minimumLineSpacingForSectionAtIndex:(NSInteger)section
|
||||
|
||||
if (changedInNonScrollingDirection) {
|
||||
[_dataController relayoutAllNodes];
|
||||
[_dataController waitUntilAllUpdatesAreCommitted];
|
||||
[_dataController waitUntilAllUpdatesAreProcessed];
|
||||
// We need to ensure the size requery is done before we update our layout.
|
||||
[self.collectionViewLayout invalidateLayout];
|
||||
}
|
||||
|
||||
@ -112,6 +112,19 @@ typedef struct {
|
||||
@property (nonatomic, strong, readonly) ASEventLog *eventLog;
|
||||
#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.
|
||||
* Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished.
|
||||
|
||||
@ -520,7 +520,14 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
measurementCompletion:(void(^)())completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass]
|
||||
|
||||
ASSizeRange sizeRange;
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
sizeRange = [self _locked_constrainedSizeForLayoutPass];
|
||||
}
|
||||
|
||||
[self transitionLayoutWithSizeRange:sizeRange
|
||||
animated:animated
|
||||
shouldMeasureAsync:shouldMeasureAsync
|
||||
measurementCompletion:completion];
|
||||
@ -850,8 +857,8 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
if (pendingLayoutTransition != nil) {
|
||||
[self _setCalculatedDisplayNodeLayout:pendingLayoutTransition.pendingLayout];
|
||||
[self _completeLayoutTransition:pendingLayoutTransition];
|
||||
[self _pendingLayoutTransitionDidComplete];
|
||||
}
|
||||
[self _pendingLayoutTransitionDidComplete];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
#if YOGA /* YOGA */
|
||||
|
||||
#import <AsyncDisplayKit/_ASDisplayViewAccessiblity.h>
|
||||
#import <AsyncDisplayKit/ASYogaLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASYogaUtilities.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
@ -235,6 +236,11 @@
|
||||
yogaFloatForCGFloat(rootConstrainedSize.max.height),
|
||||
YGDirectionInherit);
|
||||
|
||||
// Reset accessible elements, since layout may have changed.
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[(_ASDisplayView *)self.view setAccessibleElements:nil];
|
||||
});
|
||||
|
||||
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node setupYogaCalculatedLayout];
|
||||
node.yogaLayoutInProgress = NO;
|
||||
|
||||
@ -3095,12 +3095,13 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
- (void)_locked_applyPendingViewState
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert([self _locked_isNodeLoaded], @"Expected node to be loaded before applying pending state.");
|
||||
|
||||
if (_flags.layerBacked) {
|
||||
[_pendingViewState applyToLayer:self.layer];
|
||||
[_pendingViewState applyToLayer:_layer];
|
||||
} else {
|
||||
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
|
||||
@ -3172,6 +3173,19 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
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)
|
||||
|
||||
|
||||
@ -158,6 +158,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@protocol ASEditableTextNodeDelegate <NSObject>
|
||||
|
||||
@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.
|
||||
@param editableTextNode An editable text node.
|
||||
|
||||
@ -699,6 +699,12 @@
|
||||
}
|
||||
|
||||
#pragma mark - UITextView Delegate
|
||||
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
|
||||
{
|
||||
// Delegateify.
|
||||
return [self _delegateShouldBeginEditing];
|
||||
}
|
||||
|
||||
- (void)textViewDidBeginEditing:(UITextView *)textView
|
||||
{
|
||||
// Delegateify.
|
||||
@ -793,6 +799,14 @@
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
- (BOOL)_delegateShouldBeginEditing
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(editableTextNodeShouldBeginEditing:)]) {
|
||||
return [_delegate editableTextNodeShouldBeginEditing:self];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)_delegateDidBeginEditing
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(editableTextNodeDidBeginEditing:)])
|
||||
|
||||
@ -72,7 +72,7 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
} else {
|
||||
animatedImage.playbackReadyCallback = ^{
|
||||
// In this case the lock is already gone we have to call the unlocked version therefore
|
||||
[self setShouldAnimate:YES];
|
||||
[weakSelf setShouldAnimate:YES];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +176,8 @@ typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry);
|
||||
self.contentsScale = ASScreenScale();
|
||||
self.contentMode = UIViewContentModeScaleAspectFill;
|
||||
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
|
||||
// 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.
|
||||
|
||||
@ -77,6 +77,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* A horizontal, paging collection node.
|
||||
*/
|
||||
@interface ASPagerNode : ASCollectionNode
|
||||
|
||||
/**
|
||||
@ -86,6 +89,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Initializer with custom-configured flow layout properties.
|
||||
*
|
||||
* NOTE: The flow layout must have a horizontal scroll direction.
|
||||
*/
|
||||
- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout;
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
|
||||
#import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h>
|
||||
|
||||
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionDelegateFlowLayout, ASDelegateProxyInterceptor, ASCollectionGalleryLayoutSizeProviding>
|
||||
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionDelegateFlowLayout, ASDelegateProxyInterceptor, ASCollectionGalleryLayoutPropertiesProviding>
|
||||
{
|
||||
__weak id <ASPagerDataSource> _pagerDataSource;
|
||||
ASPagerNodeProxy *_proxyDataSource;
|
||||
@ -67,6 +67,7 @@
|
||||
- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout;
|
||||
{
|
||||
ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout.");
|
||||
ASDisplayNodeAssertTrue(flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal);
|
||||
self = [super initWithCollectionViewLayout:flowLayout];
|
||||
return self;
|
||||
}
|
||||
@ -76,7 +77,7 @@
|
||||
ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionHorizontalDirections];
|
||||
self = [super initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil];
|
||||
if (self) {
|
||||
layoutDelegate.sizeProvider = self;
|
||||
layoutDelegate.propertiesProvider = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -113,7 +114,15 @@
|
||||
|
||||
- (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
|
||||
@ -138,12 +147,12 @@
|
||||
return indexPath.row;
|
||||
}
|
||||
|
||||
#pragma mark - ASCollectionGalleryLayoutSizeProviding
|
||||
#pragma mark - ASCollectionGalleryLayoutPropertiesProviding
|
||||
|
||||
- (CGSize)sizeForElements:(ASElementMap *)elements
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return self.bounds.size;
|
||||
return [self pageSize];
|
||||
}
|
||||
|
||||
#pragma mark - ASCollectionDataSource
|
||||
@ -180,7 +189,7 @@
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
return ASSizeRangeMake(self.bounds.size);
|
||||
return ASSizeRangeMake([self pageSize]);
|
||||
}
|
||||
|
||||
#pragma mark - Data Source Proxy
|
||||
|
||||
@ -55,6 +55,20 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@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 node’s origin to the table node’s 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
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -685,6 +728,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTableNode (Deprecated)
|
||||
|
||||
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed.");
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
||||
|
||||
@ -44,6 +44,8 @@
|
||||
@property (nonatomic, assign) BOOL allowsMultipleSelectionDuringEditing;
|
||||
@property (nonatomic, assign) BOOL inverted;
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
|
||||
@property (nonatomic, assign) CGPoint contentOffset;
|
||||
@property (nonatomic, assign) BOOL animatesContentOffset;
|
||||
@property (nonatomic, assign) BOOL automaticallyAdjustsContentOffset;
|
||||
@end
|
||||
|
||||
@ -59,6 +61,8 @@
|
||||
_allowsMultipleSelectionDuringEditing = NO;
|
||||
_inverted = NO;
|
||||
_leadingScreensForBatching = 2;
|
||||
_contentOffset = CGPointZero;
|
||||
_animatesContentOffset = NO;
|
||||
_automaticallyAdjustsContentOffset = NO;
|
||||
}
|
||||
return self;
|
||||
@ -121,6 +125,7 @@
|
||||
if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) {
|
||||
[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
|
||||
{
|
||||
_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();
|
||||
if (self.nodeLoaded) {
|
||||
@ -710,6 +756,11 @@ ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock)
|
||||
}
|
||||
}
|
||||
|
||||
- (void)waitUntilAllUpdatesAreCommitted
|
||||
{
|
||||
[self waitUntilAllUpdatesAreProcessed];
|
||||
}
|
||||
|
||||
#pragma mark - Debugging (Private)
|
||||
|
||||
- (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
|
||||
|
||||
@ -69,6 +69,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@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
|
||||
@ -86,6 +90,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@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.
|
||||
*
|
||||
@ -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.");
|
||||
|
||||
@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 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.");
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
|
||||
@ -243,6 +249,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// 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)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead.");
|
||||
|
||||
@end
|
||||
|
||||
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASTableDataSource.")
|
||||
|
||||
@ -346,6 +346,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
_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;
|
||||
}
|
||||
|
||||
@ -542,7 +549,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self reloadData];
|
||||
[_dataController waitUntilAllUpdatesAreCommitted];
|
||||
[_dataController waitUntilAllUpdatesAreProcessed];
|
||||
}
|
||||
|
||||
- (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
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
@ -737,15 +754,16 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd));
|
||||
return;
|
||||
}
|
||||
|
||||
[_dataController waitUntilAllUpdatesAreCommitted];
|
||||
|
||||
[_dataController waitUntilAllUpdatesAreProcessed];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
// Remeasure all rows if our row width has changed.
|
||||
_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) {
|
||||
_nodesConstrainedWidth = constrainedWidth;
|
||||
|
||||
@ -1622,7 +1640,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
#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.
|
||||
return nil;
|
||||
|
||||
@ -27,8 +27,13 @@
|
||||
#define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00
|
||||
#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_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.
|
||||
// This reduces Yoga-specific code in other files.
|
||||
|
||||
@ -21,16 +21,27 @@
|
||||
#import <os/log.h>
|
||||
#import <os/activity.h>
|
||||
|
||||
#ifndef ASEnableLogs
|
||||
#define ASEnableLogs 1
|
||||
#endif
|
||||
|
||||
#ifndef ASEnableVerboseLogging
|
||||
#define ASEnableVerboseLogging 0
|
||||
#endif
|
||||
|
||||
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.
|
||||
#define ASNodeLogEnabled 1
|
||||
os_log_t ASNodeLog();
|
||||
|
||||
@ -11,27 +11,41 @@
|
||||
//
|
||||
|
||||
#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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -32,9 +32,9 @@ AS_SUBCLASSING_RESTRICTED
|
||||
@property (nonatomic, assign) ASSizeRange constrainedSize;
|
||||
@property (nonatomic, readonly, weak) id<ASRangeManagingNode> owningNode;
|
||||
@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
|
||||
supplementaryElementKind:(nullable NSString *)supplementaryElementKind
|
||||
constrainedSize:(ASSizeRange)constrainedSize
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
ASCellNode *_node;
|
||||
}
|
||||
|
||||
- (instancetype)initWithViewModel:(id)viewModel
|
||||
- (instancetype)initWithNodeModel:(id)nodeModel
|
||||
nodeBlock:(ASCellNodeBlock)nodeBlock
|
||||
supplementaryElementKind:(NSString *)supplementaryElementKind
|
||||
constrainedSize:(ASSizeRange)constrainedSize
|
||||
@ -42,7 +42,7 @@
|
||||
NSAssert(nodeBlock != nil, @"Node block must not be nil");
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_viewModel = viewModel;
|
||||
_nodeModel = nodeModel;
|
||||
_nodeBlock = nodeBlock;
|
||||
_supplementaryElementKind = [supplementaryElementKind copy];
|
||||
_constrainedSize = constrainedSize;
|
||||
@ -65,7 +65,7 @@
|
||||
node.owningNode = _owningNode;
|
||||
node.collectionElement = self;
|
||||
ASTraitCollectionPropagateDown(node, _traitCollection);
|
||||
node.viewModel = _viewModel;
|
||||
node.nodeModel = _nodeModel;
|
||||
_node = node;
|
||||
}
|
||||
return _node;
|
||||
|
||||
@ -78,8 +78,9 @@
|
||||
ASSizeRange sizeRange = ASSizeRangeForCollectionLayoutThatFitsViewportSize(context.viewportSize, context.scrollableDirections);
|
||||
ASLayout *layout = [stackSpec layoutThatFits:sizeRange];
|
||||
|
||||
return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nonnull(ASLayout * _Nonnull sublayout) {
|
||||
return ((ASCellNode *)sublayout.layoutElement).collectionElement;
|
||||
return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) {
|
||||
ASCellNode *node = ASDynamicCast(sublayout.layoutElement, ASCellNode);
|
||||
return node ? node.collectionElement : nil;
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASCollectionGalleryLayoutSizeProviding <NSObject>
|
||||
@protocol ASCollectionGalleryLayoutPropertiesProviding <NSObject>
|
||||
|
||||
/**
|
||||
* Returns the fixed size of each and every element.
|
||||
@ -32,6 +32,51 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (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
|
||||
|
||||
/**
|
||||
@ -42,8 +87,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@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)init __unavailable;
|
||||
|
||||
144
Source/Details/ASCollectionGalleryLayoutDelegate.mm
Normal 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
|
||||
@ -29,6 +29,7 @@ AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASCollectionLayoutContext : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) CGSize viewportSize;
|
||||
@property (nonatomic, assign, readonly) CGPoint initialContentOffset;
|
||||
@property (nonatomic, assign, readonly) ASScrollDirection scrollableDirections;
|
||||
@property (nonatomic, weak, readonly) ASElementMap *elements;
|
||||
@property (nonatomic, strong, readonly, nullable) id additionalInfo;
|
||||
|
||||
@ -24,13 +24,11 @@
|
||||
|
||||
@implementation ASCollectionLayoutContext {
|
||||
Class<ASCollectionLayoutDelegate> _layoutDelegateClass;
|
||||
|
||||
// This ivar doesn't directly involve in the layout calculation process, i.e contexts can be equal regardless of the layout caches.
|
||||
// As a result, this ivar is ignored in -isEqualToContext: and -hash.
|
||||
__weak ASCollectionLayoutCache *_layoutCache;
|
||||
}
|
||||
|
||||
- (instancetype)initWithViewportSize:(CGSize)viewportSize
|
||||
initialContentOffset:(CGPoint)initialContentOffset
|
||||
scrollableDirections:(ASScrollDirection)scrollableDirections
|
||||
elements:(ASElementMap *)elements
|
||||
layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass
|
||||
@ -40,6 +38,7 @@
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_viewportSize = viewportSize;
|
||||
_initialContentOffset = initialContentOffset;
|
||||
_scrollableDirections = scrollableDirections;
|
||||
_elements = elements;
|
||||
_layoutDelegateClass = layoutDelegateClass;
|
||||
@ -59,6 +58,8 @@
|
||||
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
|
||||
{
|
||||
if (context == nil) {
|
||||
|
||||
@ -25,6 +25,8 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef ASCollectionElement * _Nullable (^ASCollectionLayoutStateGetElementBlock)(ASLayout *);
|
||||
|
||||
@interface NSMapTable (ASCollectionLayoutConvenience)
|
||||
|
||||
+ (NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)elementToLayoutAttributesTable;
|
||||
@ -72,11 +74,11 @@ AS_SUBCLASSING_RESTRICTED
|
||||
*
|
||||
* @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
|
||||
layout:(ASLayout *)layout
|
||||
getElementBlock:(ASCollectionElement *(^)(ASLayout *))getElementBlock;
|
||||
getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock;
|
||||
|
||||
/**
|
||||
* Returns all layout attributes present in this object.
|
||||
|
||||
@ -22,9 +22,12 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpecUtilities.h>
|
||||
#import <AsyncDisplayKit/ASPageTable.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
#import <queue>
|
||||
|
||||
@implementation NSMapTable (ASCollectionLayoutConvenience)
|
||||
|
||||
+ (NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)elementToLayoutAttributesTable
|
||||
@ -50,30 +53,49 @@ elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]];
|
||||
|
||||
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context
|
||||
layout:(ASLayout *)layout
|
||||
getElementBlock:(ASCollectionElement *(^)(ASLayout *))getElementBlock
|
||||
getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock
|
||||
{
|
||||
ASElementMap *elements = context.elements;
|
||||
NSMapTable *table = [NSMapTable elementToLayoutAttributesTable];
|
||||
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
ASCollectionElement *element = getElementBlock(sublayout);
|
||||
if (element == nil) {
|
||||
ASDisplayNodeFailAssert(@"Element not found!");
|
||||
continue;
|
||||
|
||||
// Traverse the given layout tree in breadth first fashion. Generate layout attributes for all included elements along the way.
|
||||
struct Context {
|
||||
ASLayout *layout;
|
||||
CGPoint absolutePosition;
|
||||
};
|
||||
|
||||
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];
|
||||
NSString *supplementaryElementKind = element.supplementaryElementKind;
|
||||
|
||||
UICollectionViewLayoutAttributes *attrs;
|
||||
if (supplementaryElementKind == nil) {
|
||||
attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
|
||||
} else {
|
||||
attrs = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:supplementaryElementKind withIndexPath:indexPath];
|
||||
|
||||
// Add all sublayouts to process in next step
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
queue.push({sublayout, absolutePosition + sublayout.position});
|
||||
}
|
||||
|
||||
attrs.frame = sublayout.frame;
|
||||
[table setObject:attrs forKey:element];
|
||||
}
|
||||
|
||||
return [self initWithContext:context contentSize:layout.size elementToLayoutAttributesTable:table];
|
||||
@ -150,9 +172,10 @@ elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]];
|
||||
}
|
||||
|
||||
- (ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect
|
||||
contentSize:(CGSize)contentSize
|
||||
pageSize:(CGSize)pageSize
|
||||
{
|
||||
CGSize pageSize = _context.viewportSize;
|
||||
CGSize contentSize = _contentSize;
|
||||
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (_unmeasuredPageToLayoutAttributesTable.count == 0 || CGRectIsNull(rect) || CGRectIsEmpty(rect) || CGSizeEqualToSize(CGSizeZero, contentSize) || CGSizeEqualToSize(CGSizeZero, pageSize)) {
|
||||
return nil;
|
||||
|
||||
@ -27,9 +27,12 @@
|
||||
// of the collection view
|
||||
ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView) {
|
||||
CGSize maxSize = collectionView.bounds.size;
|
||||
UIEdgeInsets contentInset = collectionView.contentInset;
|
||||
if (ASScrollDirectionContainsHorizontalDirection(collectionView.scrollableDirections)) {
|
||||
maxSize.width = CGFLOAT_MAX;
|
||||
maxSize.height -= (contentInset.top + contentInset.bottom);
|
||||
} else {
|
||||
maxSize.width -= (contentInset.left + contentInset.right);
|
||||
maxSize.height = CGFLOAT_MAX;
|
||||
}
|
||||
return ASSizeRangeMake(CGSizeZero, maxSize);
|
||||
|
||||
@ -79,7 +79,7 @@ extern NSString * const ASCollectionInvalidUpdateException;
|
||||
*/
|
||||
- (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
|
||||
|
||||
@ -255,7 +255,12 @@ extern NSString * const ASCollectionInvalidUpdateException;
|
||||
*/
|
||||
- (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
|
||||
|
||||
@ -332,18 +332,18 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
|
||||
id<ASRangeManagingNode> node = self.node;
|
||||
for (NSIndexPath *indexPath in indexPaths) {
|
||||
ASCellNodeBlock nodeBlock;
|
||||
id viewModel;
|
||||
id nodeModel;
|
||||
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.
|
||||
if (viewModel != nil && !changeSet.includesReloadData) {
|
||||
if (nodeModel != nil && !changeSet.includesReloadData) {
|
||||
NSIndexPath *oldIndexPath = [changeSet oldIndexPathForNewIndexPath:indexPath];
|
||||
if (oldIndexPath != nil) {
|
||||
ASCollectionElement *oldElement = [previousMap elementForItemAtIndexPath:oldIndexPath];
|
||||
ASCellNode *oldNode = oldElement.node;
|
||||
if ([oldNode canUpdateToViewModel:viewModel]) {
|
||||
// Just wrap the node in a block. The collection element will -setViewModel:
|
||||
if ([oldNode canUpdateToNodeModel:nodeModel]) {
|
||||
// Just wrap the node in a block. The collection element will -setNodeModel:
|
||||
nodeBlock = ^{
|
||||
return oldNode;
|
||||
};
|
||||
@ -362,7 +362,7 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
|
||||
constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
||||
}
|
||||
|
||||
ASCollectionElement *element = [[ASCollectionElement alloc] initWithViewModel:viewModel
|
||||
ASCollectionElement *element = [[ASCollectionElement alloc] initWithNodeModel:nodeModel
|
||||
nodeBlock:nodeBlock
|
||||
supplementaryElementKind:isRowKind ? nil : kind
|
||||
constrainedSize:constrainedSize
|
||||
@ -432,13 +432,42 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
|
||||
|
||||
#pragma mark - Batching (External API)
|
||||
|
||||
- (void)waitUntilAllUpdatesAreCommitted
|
||||
- (void)waitUntilAllUpdatesAreProcessed
|
||||
{
|
||||
// Schedule block in main serial queue to wait until all operations are finished that are
|
||||
// where scheduled while waiting for the _editingTransactionQueue to finish
|
||||
[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
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
@ -565,7 +594,7 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
|
||||
});
|
||||
|
||||
if (_usesSynchronousDataLoading) {
|
||||
[self waitUntilAllUpdatesAreCommitted];
|
||||
[self waitUntilAllUpdatesAreProcessed];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASMainSerialQueue : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSUInteger numberOfScheduledBlocks;
|
||||
- (void)performBlockOnMainThread:(dispatch_block_t)block;
|
||||
|
||||
@end
|
||||
|
||||
@ -40,6 +40,12 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfScheduledBlocks
|
||||
{
|
||||
ASDN::MutexLocker l(_serialQueueLock);
|
||||
return _blocks.count;
|
||||
}
|
||||
|
||||
- (void)performBlockOnMainThread:(dispatch_block_t)block
|
||||
{
|
||||
ASDN::MutexLocker l(_serialQueueLock);
|
||||
|
||||
@ -23,9 +23,21 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
|
||||
#import <queue>
|
||||
|
||||
NS_INLINE UIAccessibilityTraits InteractiveAccessibilityTraitsMask() {
|
||||
return UIAccessibilityTraitLink | UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitButton;
|
||||
}
|
||||
|
||||
#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.
|
||||
static void SortAccessibilityElements(NSMutableArray *elements)
|
||||
@ -35,7 +47,7 @@ static void SortAccessibilityElements(NSMutableArray *elements)
|
||||
static SortAccessibilityElementsComparator comparator = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
comparator = ^NSComparisonResult(UIAccessibilityElement *a, UIAccessibilityElement *b) {
|
||||
comparator = ^NSComparisonResult(id<ASAccessibilityElementPositioning> a, id<ASAccessibilityElementPositioning> b) {
|
||||
CGPoint originA = a.accessibilityFrame.origin;
|
||||
CGPoint originB = b.accessibilityFrame.origin;
|
||||
if (originA.y == originB.y) {
|
||||
@ -50,7 +62,7 @@ static void SortAccessibilityElements(NSMutableArray *elements)
|
||||
[elements sortUsingComparator:comparator];
|
||||
}
|
||||
|
||||
@interface ASAccessibilityElement : UIAccessibilityElement
|
||||
@interface ASAccessibilityElement : UIAccessibilityElement<ASAccessibilityElementPositioning>
|
||||
|
||||
@property (nonatomic, strong) ASDisplayNode *node;
|
||||
@property (nonatomic, strong) ASDisplayNode *containerNode;
|
||||
@ -85,6 +97,25 @@ static void SortAccessibilityElements(NSMutableArray *elements)
|
||||
|
||||
#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
|
||||
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
|
||||
static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableArray *elements)
|
||||
{
|
||||
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
|
||||
|
||||
ASDisplayNode *node = view.asyncdisplaykit_node;
|
||||
|
||||
if (node.isAccessibilityContainer) {
|
||||
CollectAccessibilityElementsForContainer(node, view, elements);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle rasterize case
|
||||
if (node.rasterizesSubtree) {
|
||||
|
||||
@ -258,7 +258,7 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
|
||||
} else if (sublayoutsCount > 0){
|
||||
std::vector<Context> sublayoutContexts;
|
||||
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());
|
||||
}
|
||||
|
||||
@ -72,11 +72,13 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
CGSize viewportSize = [self _viewportSize];
|
||||
CGPoint contentOffset = _collectionNode.contentOffset;
|
||||
id additionalInfo = nil;
|
||||
if (_layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements) {
|
||||
additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements];
|
||||
}
|
||||
return [[ASCollectionLayoutContext alloc] initWithViewportSize:viewportSize
|
||||
initialContentOffset:contentOffset
|
||||
scrollableDirections:[_layoutDelegate scrollableDirections]
|
||||
elements:elements
|
||||
layoutDelegateClass:[_layoutDelegate class]
|
||||
@ -93,15 +95,19 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
|
||||
ASCollectionLayoutState *layout = [context.layoutDelegateClass calculateLayoutWithContext:context];
|
||||
[context.layoutCache setLayout:layout forContext:context];
|
||||
|
||||
// Measure elements in the measure range ahead of time, block on the initial rect as it'll be visible shortly
|
||||
// Measure elements in the measure range ahead of time
|
||||
CGSize viewportSize = context.viewportSize;
|
||||
// TODO Consider content offset of the collection node
|
||||
CGRect initialRect = CGRectMake(0, 0, viewportSize.width, viewportSize.height);
|
||||
CGPoint contentOffset = context.initialContentOffset;
|
||||
CGRect initialRect = CGRectMake(contentOffset.x, contentOffset.y, viewportSize.width, viewportSize.height);
|
||||
CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(initialRect,
|
||||
kASDefaultMeasureRangeTuningParameters,
|
||||
context.scrollableDirections,
|
||||
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;
|
||||
}
|
||||
@ -140,8 +146,9 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
|
||||
- (CGSize)collectionViewContentSize
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssertNotNil(_layout, @"Collection layout state should not be nil at this point");
|
||||
return _layout.contentSize;
|
||||
// The content size can be queried right after a layout invalidation (https://github.com/TextureGroup/Texture/pull/509).
|
||||
// In that case, return zero.
|
||||
return _layout ? _layout.contentSize : CGSizeZero;
|
||||
}
|
||||
|
||||
- (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
|
||||
ASCollectionLayoutContext *context = layout.context;
|
||||
CGSize pageSize = context.viewportSize;
|
||||
ASPageToLayoutAttributesTable *attrsTable = [layout getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:rect
|
||||
contentSize:contentSize
|
||||
pageSize:pageSize];
|
||||
ASPageToLayoutAttributesTable *attrsTable = [layout getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:rect];
|
||||
if (attrsTable.count == 0) {
|
||||
// No elements in this rect! Bail early
|
||||
return;
|
||||
@ -259,6 +262,8 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
|
||||
|
||||
// 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.
|
||||
ASCollectionLayoutContext *context = layout.context;
|
||||
CGSize pageSize = context.viewportSize;
|
||||
NSMutableOrderedSet<UICollectionViewLayoutAttributes *> *blockingAttrs = hasBlockingRect ? [NSMutableOrderedSet orderedSet] : nil;
|
||||
NSMutableOrderedSet<UICollectionViewLayoutAttributes *> *nonBlockingAttrs = [NSMutableOrderedSet orderedSet];
|
||||
for (id pagePtr in attrsTable) {
|
||||
|
||||
@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, weak, readonly) ASCollectionLayoutCache *layoutCache;
|
||||
|
||||
- (instancetype)initWithViewportSize:(CGSize)viewportSize
|
||||
initialContentOffset:(CGPoint)initialContentOffset
|
||||
scrollableDirections:(ASScrollDirection)scrollableDirections
|
||||
elements:(ASElementMap *)elements
|
||||
layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass
|
||||
|
||||
@ -24,9 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* @discussion This method is atomic and thread-safe
|
||||
*/
|
||||
- (nullable ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect
|
||||
contentSize:(CGSize)contentSize
|
||||
pageSize:(CGSize)pageSize;
|
||||
- (nullable ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@ -76,6 +76,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector;
|
||||
|
||||
@property (nonatomic, assign) CGPoint contentOffset;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -195,6 +195,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
||||
NSArray *_accessibilityHeaderElements;
|
||||
CGPoint _accessibilityActivationPoint;
|
||||
UIBezierPath *_accessibilityPath;
|
||||
BOOL _isAccessibilityContainer;
|
||||
|
||||
// performance measurement
|
||||
ASDisplayNodePerformanceMeasurementOptions _measurementOptions;
|
||||
|
||||
@ -33,6 +33,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@property (nonatomic, weak) id<ASTableDelegate> asyncDelegate;
|
||||
@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.
|
||||
@ -44,10 +57,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (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.
|
||||
*
|
||||
@ -109,12 +118,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (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 NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect;
|
||||
@ -135,13 +138,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (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.
|
||||
*
|
||||
@ -311,5 +307,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
|
||||
|
||||
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -485,7 +485,8 @@ static CGFloat computeItemsStackDimensionSum(const std::vector<ASStackLayoutSpec
|
||||
});
|
||||
|
||||
// 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) {
|
||||
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<ASStackLayoutSpecItem> lineItems;
|
||||
CGFloat lineStackDimensionSum = 0;
|
||||
CGFloat interitemSpacing = 0;
|
||||
|
||||
for(auto it = items.begin(); it != items.end(); ++it) {
|
||||
const auto &item = *it;
|
||||
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 BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + itemAndSpacingStackDimension, style, sizeRange) < 0);
|
||||
const CGFloat itemAndSpacingStackDimension = item.child.style.spacingBefore + itemStackDimension + item.child.style.spacingAfter;
|
||||
const BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + interitemSpacing + itemAndSpacingStackDimension, style, sizeRange) < 0);
|
||||
const BOOL breakCurrentLine = negativeViolationIfAddItem && !lineItems.empty();
|
||||
|
||||
if (breakCurrentLine) {
|
||||
lines.push_back({.items = std::vector<ASStackLayoutSpecItem> (lineItems)});
|
||||
lineItems.clear();
|
||||
lineStackDimensionSum = 0;
|
||||
interitemSpacing = 0;
|
||||
}
|
||||
|
||||
lineItems.push_back(std::move(item));
|
||||
lineStackDimensionSum += itemAndSpacingStackDimension;
|
||||
lineStackDimensionSum += interitemSpacing + itemAndSpacingStackDimension;
|
||||
interitemSpacing = style.spacing;
|
||||
}
|
||||
|
||||
// Handle last line
|
||||
|
||||
30
Source/Private/_ASCollectionGalleryLayoutInfo.h
Normal 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
|
||||
72
Source/Private/_ASCollectionGalleryLayoutInfo.m
Normal 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
|
||||
@ -38,7 +38,12 @@ ASDISPLAYNODE_EXTERN_C_BEGIN
|
||||
@interface ASDisplayNode (ASResizableContents) <ASResizableContents>
|
||||
@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);
|
||||
|
||||
/**
|
||||
|
||||
@ -26,20 +26,16 @@ extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UI
|
||||
|
||||
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) {
|
||||
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
|
||||
obj.contents = (id)[image CGImage];
|
||||
obj.contentsScale = [image scale];
|
||||
obj.rasterizationScale = [image scale];
|
||||
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];
|
||||
|
||||
// These are lifted from what UIImageView does by experimentation. Without these exact values, the stretching is slightly off.
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
@end
|
||||
|
||||
@interface ASTestSection : NSObject <ASSectionContext>
|
||||
@property (nonatomic, readonly) NSMutableArray *viewModels;
|
||||
@property (nonatomic, readonly) NSMutableArray *nodeModels;
|
||||
@end
|
||||
|
||||
@implementation ASCollectionModernDataSourceTests {
|
||||
@ -41,10 +41,10 @@
|
||||
// Default is 2 sections: 2 items in first, 1 item in second.
|
||||
sections = [NSMutableArray array];
|
||||
[sections addObject:[ASTestSection new]];
|
||||
[sections[0].viewModels addObject:[NSObject new]];
|
||||
[sections[0].viewModels addObject:[NSObject new]];
|
||||
[sections[0].nodeModels addObject:[NSObject new]];
|
||||
[sections[0].nodeModels addObject:[NSObject 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)];
|
||||
viewController = [[UIViewController alloc] init];
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
@selector(numberOfSectionsInCollectionNode:),
|
||||
@selector(collectionNode:numberOfItemsInSection:),
|
||||
@selector(collectionNode:nodeBlockForItemAtIndexPath:),
|
||||
@selector(collectionNode:viewModelForItemAtIndexPath:),
|
||||
@selector(collectionNode:nodeModelForItemAtIndexPath:),
|
||||
@selector(collectionNode:contextForSection:),
|
||||
nil];
|
||||
[mockDataSource setExpectationOrderMatters:YES];
|
||||
@ -71,7 +71,7 @@
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
[collectionNode waitUntilAllUpdatesAreCommitted];
|
||||
[collectionNode waitUntilAllUpdatesAreProcessed];
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@
|
||||
skippedReloadIndexPaths:nil];
|
||||
}
|
||||
|
||||
- (void)testReloadingAnItemWithACompatibleViewModel
|
||||
- (void)testReloadingAnItemWithACompatibleNodeModel
|
||||
{
|
||||
[self loadInitialData];
|
||||
|
||||
@ -120,15 +120,15 @@
|
||||
NSIndexPath *reloadedPath = [NSIndexPath indexPathForItem:1 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];
|
||||
OCMExpect([mockCellNode canUpdateToViewModel:viewModel])
|
||||
OCMExpect([mockCellNode canUpdateToNodeModel:nodeModel])
|
||||
.andReturn(YES);
|
||||
|
||||
[self performUpdateReloadingSections:nil
|
||||
reloadingItems:@{ reloadedPath: viewModel }
|
||||
reloadingItems:@{ reloadedPath: nodeModel }
|
||||
reloadMappings:@{ reloadedPath: [NSIndexPath indexPathForItem:0 inSection:0] }
|
||||
insertingItems:nil
|
||||
deletingItems:@[ deletedPath ]
|
||||
@ -168,12 +168,12 @@
|
||||
|
||||
// It reads the contents for each item.
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
NSArray *viewModels = sections[section].viewModels;
|
||||
NSArray *nodeModels = sections[section].nodeModels;
|
||||
|
||||
// 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];
|
||||
[self expectViewModelMethodForItemAtIndexPath:indexPath viewModel:viewModels[i]];
|
||||
[self expectNodeModelMethodForItemAtIndexPath:indexPath nodeModel:nodeModels[i]];
|
||||
[self expectNodeBlockMethodForItemAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
@ -201,14 +201,14 @@
|
||||
// Note: Skip fast enumeration for readability.
|
||||
for (NSInteger section = 0; section < sections.count; 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])
|
||||
.andReturn(viewModel);
|
||||
OCMExpect([mockDataSource collectionNode:collectionNode nodeModelForItemAtIndexPath:indexPath])
|
||||
.andReturn(nodeModel);
|
||||
}
|
||||
|
||||
- (void)expectContextMethodForSection:(NSInteger)section
|
||||
@ -240,21 +240,21 @@
|
||||
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
ASTestSection *sectionObject = sections[section];
|
||||
NSArray *viewModels = sectionObject.viewModels;
|
||||
NSArray *nodeModels = sectionObject.nodeModels;
|
||||
|
||||
// Assert section object
|
||||
XCTAssertEqualObjects([collectionNode contextForSection:section], sectionObject);
|
||||
|
||||
// Assert item count
|
||||
XCTAssertEqual([collectionNode numberOfItemsInSection:section], viewModels.count);
|
||||
for (NSInteger item = 0; item < viewModels.count; item++) {
|
||||
// Assert view model
|
||||
XCTAssertEqual([collectionNode numberOfItemsInSection:section], nodeModels.count);
|
||||
for (NSInteger item = 0; item < nodeModels.count; item++) {
|
||||
// Assert node model
|
||||
// Could use pointer equality but the error message is less readable.
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
|
||||
id viewModel = viewModels[indexPath.item];
|
||||
XCTAssertEqualObjects(viewModel, [collectionNode viewModelForItemAtIndexPath:indexPath]);
|
||||
id nodeModel = nodeModels[indexPath.item];
|
||||
XCTAssertEqualObjects(nodeModel, [collectionNode nodeModelForItemAtIndexPath: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
|
||||
* 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
|
||||
reloadingItems:(NSDictionary<NSIndexPath *, id> *)reloadedItems
|
||||
@ -275,7 +275,7 @@
|
||||
[collectionNode performBatchUpdates:^{
|
||||
// First update our data source.
|
||||
[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) {
|
||||
sections[key.integerValue] = obj;
|
||||
@ -283,13 +283,13 @@
|
||||
|
||||
// Deletion paths, sorted descending
|
||||
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.
|
||||
NSArray *insertionsSortedAcending = [insertedItems.allKeys sortedArrayUsingSelector:@selector(compare:)];
|
||||
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.
|
||||
@ -314,10 +314,10 @@
|
||||
// Go through reloaded sections and add all their items into `insertsPlusReloads`
|
||||
[reloadedSectionIndexes enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) {
|
||||
[self expectContextMethodForSection:section];
|
||||
NSArray *viewModels = sections[section].viewModels;
|
||||
for (NSInteger i = 0; i < viewModels.count; i++) {
|
||||
NSArray *nodeModels = sections[section].nodeModels;
|
||||
for (NSInteger i = 0; i < nodeModels.count; i++) {
|
||||
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:)]) {
|
||||
[self expectViewModelMethodForItemAtIndexPath:indexPath viewModel:insertsPlusReloads[indexPath]];
|
||||
[self expectNodeModelMethodForItemAtIndexPath:indexPath nodeModel:insertsPlusReloads[indexPath]];
|
||||
NSIndexPath *oldIndexPath = [reloadMappings allKeysForObject:indexPath].firstObject;
|
||||
BOOL isSkippedReload = oldIndexPath && [skippedReloadIndexPaths containsObject:oldIndexPath];
|
||||
if (!isSkippedReload) {
|
||||
@ -335,7 +335,7 @@
|
||||
}
|
||||
} 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];
|
||||
}
|
||||
|
||||
@ -345,9 +345,9 @@
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@ -360,7 +360,7 @@
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_viewModels = [NSMutableArray array];
|
||||
_nodeModels = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -260,7 +260,7 @@
|
||||
[window makeKeyAndVisible];
|
||||
|
||||
[testController.collectionNode reloadData];
|
||||
[testController.collectionNode waitUntilAllUpdatesAreCommitted];
|
||||
[testController.collectionNode waitUntilAllUpdatesAreProcessed];
|
||||
[testController.collectionView layoutIfNeeded];
|
||||
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||
@ -397,7 +397,7 @@
|
||||
window.rootViewController = testController;\
|
||||
\
|
||||
[cn reloadData];\
|
||||
[cn waitUntilAllUpdatesAreCommitted]; \
|
||||
[cn waitUntilAllUpdatesAreProcessed]; \
|
||||
[testController.collectionView layoutIfNeeded];
|
||||
|
||||
- (void)testThatSubmittingAValidInsertDoesNotThrowAnException
|
||||
@ -620,7 +620,7 @@
|
||||
[window makeKeyAndVisible];
|
||||
|
||||
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]];
|
||||
[cn reloadDataWithCompletion:^{
|
||||
[done fulfill];
|
||||
@ -755,7 +755,7 @@
|
||||
|
||||
del.sectionGeneration++;
|
||||
[cn reloadData];
|
||||
[cn waitUntilAllUpdatesAreCommitted];
|
||||
[cn waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
NSInteger sectionCount = del->_itemCounts.size();
|
||||
for (NSInteger section = 0; section < sectionCount; section++) {
|
||||
@ -857,7 +857,7 @@
|
||||
[window layoutIfNeeded];
|
||||
|
||||
ASCollectionNode *cn = testController.collectionNode;
|
||||
[cn waitUntilAllUpdatesAreCommitted];
|
||||
[cn waitUntilAllUpdatesAreProcessed];
|
||||
[cn.view layoutIfNeeded];
|
||||
ASCellNode *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
XCTAssertTrue(node.visible);
|
||||
@ -880,7 +880,7 @@
|
||||
[window layoutIfNeeded];
|
||||
|
||||
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.");
|
||||
|
||||
__block NSUInteger batchFetchCount = 0;
|
||||
@ -926,7 +926,7 @@
|
||||
[window layoutIfNeeded];
|
||||
|
||||
ASCollectionNode *cn = testController.collectionNode;
|
||||
[cn waitUntilAllUpdatesAreCommitted];
|
||||
[cn waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
__block NSUInteger batchFetchCount = 0;
|
||||
XCTestExpectation *e = [self expectationWithDescription:@"Batch fetching completed"];
|
||||
@ -1020,7 +1020,7 @@
|
||||
[view layoutIfNeeded];
|
||||
|
||||
// Wait for ASDK reload to finish
|
||||
[cn waitUntilAllUpdatesAreCommitted];
|
||||
[cn waitUntilAllUpdatesAreProcessed];
|
||||
// Force UIKit to read updated data & range controller to update and account for it
|
||||
[cn.view layoutIfNeeded];
|
||||
[self waitForExpectationsWithTimeout:60 handler:nil];
|
||||
@ -1050,8 +1050,17 @@
|
||||
// Trigger the initial reload to start
|
||||
[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
|
||||
[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
|
||||
[cn.view layoutIfNeeded];
|
||||
|
||||
@ -1093,7 +1102,7 @@
|
||||
traitCollection.containerSize = screenBounds.size;
|
||||
cn.primitiveTraitCollection = traitCollection;
|
||||
|
||||
[cn waitUntilAllUpdatesAreCommitted];
|
||||
[cn waitUntilAllUpdatesAreProcessed];
|
||||
[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
|
||||
@ -1124,7 +1133,7 @@
|
||||
[window makeKeyAndVisible];
|
||||
[window layoutIfNeeded];
|
||||
|
||||
[cn waitUntilAllUpdatesAreCommitted];
|
||||
[cn waitUntilAllUpdatesAreProcessed];
|
||||
for (NSInteger i = 0; i < itemCount; i++) {
|
||||
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.");
|
||||
|
||||
@ -2,8 +2,17 @@
|
||||
// ASPagerNodeTests.m
|
||||
// Texture
|
||||
//
|
||||
// Created by Luke Parham on 11/6/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
@ -128,7 +137,7 @@
|
||||
#pragma clang diagnostic pop
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.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);
|
||||
|
||||
e = [self expectationWithDescription:@"Transition completed"];
|
||||
@ -158,7 +167,7 @@
|
||||
#pragma clang diagnostic pop
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.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);
|
||||
}
|
||||
|
||||
|
||||
@ -110,6 +110,7 @@ static NSArray<ASTextNode*> *defaultTextNodes()
|
||||
alignItems:style.alignItems
|
||||
flexWrap:style.flexWrap
|
||||
alignContent:style.alignContent
|
||||
lineSpacing:style.lineSpacing
|
||||
children:children];
|
||||
|
||||
[self testStackLayoutSpec:stackLayoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier];
|
||||
@ -163,6 +164,7 @@ static NSArray<ASTextNode*> *defaultTextNodes()
|
||||
}
|
||||
|
||||
- (void)testStackLayoutSpecWithAlignContent:(ASStackLayoutAlignContent)alignContent
|
||||
lineSpacing:(CGFloat)lineSpacing
|
||||
sizeRange:(ASSizeRange)sizeRange
|
||||
identifier:(NSString *)identifier
|
||||
{
|
||||
@ -170,8 +172,9 @@ static NSArray<ASTextNode*> *defaultTextNodes()
|
||||
.direction = ASStackLayoutDirectionHorizontal,
|
||||
.flexWrap = ASStackLayoutFlexWrapWrap,
|
||||
.alignContent = alignContent,
|
||||
.lineSpacing = lineSpacing,
|
||||
};
|
||||
|
||||
|
||||
CGSize subnodeSize = {50, 50};
|
||||
NSArray<ASDisplayNode *> *subnodes = @[
|
||||
ASDisplayNodeWithBackgroundColor([UIColor redColor], subnodeSize),
|
||||
@ -181,10 +184,17 @@ static NSArray<ASTextNode*> *defaultTextNodes()
|
||||
ASDisplayNodeWithBackgroundColor([UIColor greenColor], subnodeSize),
|
||||
ASDisplayNodeWithBackgroundColor([UIColor cyanColor], subnodeSize),
|
||||
];
|
||||
|
||||
|
||||
[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 -
|
||||
|
||||
- (void)testDefaultStackLayoutElementFlexProperties
|
||||
@ -1209,6 +1219,77 @@ static NSArray<ASTextNode*> *defaultTextNodes()
|
||||
[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
|
||||
|
||||
- (void)testAlignContentUnderflow
|
||||
@ -1282,4 +1363,33 @@ static NSArray<ASTextNode*> *defaultTextNodes()
|
||||
[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
|
||||
|
||||
@ -610,7 +610,7 @@
|
||||
|
||||
[UITableView as_recordEditingCallsIntoArray:selectors];
|
||||
XCTAssertGreaterThan(node.numberOfSections, 0);
|
||||
[node waitUntilAllUpdatesAreCommitted];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
XCTAssertGreaterThan(node.view.numberOfSections, 0);
|
||||
|
||||
// The first reloadData call helps prevent UITableView from calling it multiple times while ASDataController is working.
|
||||
@ -635,13 +635,13 @@
|
||||
|
||||
// Load initial data.
|
||||
XCTAssertGreaterThan(node.numberOfSections, 0);
|
||||
[node waitUntilAllUpdatesAreCommitted];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
XCTAssertGreaterThan(node.view.numberOfSections, 0);
|
||||
|
||||
// Reload data.
|
||||
[UITableView as_recordEditingCallsIntoArray:selectors];
|
||||
[node reloadData];
|
||||
[node waitUntilAllUpdatesAreCommitted];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
// Assert that the beginning of the call pattern is correct.
|
||||
// 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.
|
||||
XCTAssertGreaterThan(node.numberOfSections, 0);
|
||||
[node waitUntilAllUpdatesAreCommitted];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
ASSizeRange expectedSizeRange = ASSizeRangeMake(CGSizeMake(cellWidth, 0));
|
||||
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.
|
||||
[node setNeedsLayout];
|
||||
[node.view layoutIfNeeded];
|
||||
[node waitUntilAllUpdatesAreCommitted];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
UITableViewCell *cell = [node.view cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
XCTAssertNotNil(cell);
|
||||
@ -758,7 +758,7 @@
|
||||
[window makeKeyAndVisible];
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[node waitUntilAllUpdatesAreCommitted];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
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.");
|
||||
|
||||
@ -812,10 +812,10 @@
|
||||
node.dataSource = ds;
|
||||
|
||||
[node.view layoutIfNeeded];
|
||||
[node waitUntilAllUpdatesAreCommitted];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
CGFloat rowHeight = [node.view rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].size.height;
|
||||
// Scroll to row (0,1) + 10pt
|
||||
node.view.contentOffset = CGPointMake(0, rowHeight + 10);
|
||||
node.contentOffset = CGPointMake(0, rowHeight + 10);
|
||||
|
||||
[node performBatchAnimated:NO updates:^{
|
||||
// Delete row 0 from all sections.
|
||||
@ -825,11 +825,11 @@
|
||||
[node deleteRowsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:i]] withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
}
|
||||
} completion:nil];
|
||||
[node waitUntilAllUpdatesAreCommitted];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
// 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.
|
||||
XCTAssertEqual(node.view.contentOffset.y, 10);
|
||||
XCTAssertEqual(node.contentOffset.y, 10);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'Texture'
|
||||
spec.version = '2.3.4'
|
||||
spec.version = '2.4'
|
||||
spec.license = { :type => 'BSD and Apache 2', }
|
||||
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' }
|
||||
|
||||
@ -17,7 +17,7 @@ When it comes to corner rounding, many developers stick with CALayer's `.cornerR
|
||||
|
||||
## 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.
|
||||
|
||||
@ -53,11 +53,11 @@ The final consideration is to determine if all four corners cover the same node
|
||||
|
||||
### 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 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.
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ The most important thing to remember is that your init method must be capable of
|
||||
|
||||
### `-didLoad`
|
||||
|
||||
This method is conceptually similar to UIViewController's `-viewDidLoad` method; it’s 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; it’s 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:`
|
||||
|
||||
|
||||
@ -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>
|
||||
<br />
|
||||
<b>ClassDojo</b>
|
||||
<br>
|
||||
<a href="https://engineering.classdojo.com/blog/2017/07/12/asyncdisplaykit">Powering Class Story With Texture</a>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
@ -16,14 +16,14 @@
|
||||
//
|
||||
|
||||
#import "ViewController.h"
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import "SupplementaryNode.h"
|
||||
#import "ItemNode.h"
|
||||
|
||||
#define ASYNC_COLLECTION_LAYOUT 0
|
||||
|
||||
@interface ViewController () <ASCollectionDataSource, ASCollectionDelegateFlowLayout, ASCollectionGalleryLayoutSizeProviding>
|
||||
@interface ViewController () <ASCollectionDataSource, ASCollectionDelegateFlowLayout, ASCollectionGalleryLayoutPropertiesProviding>
|
||||
|
||||
@property (nonatomic, strong) ASCollectionNode *collectionNode;
|
||||
@property (nonatomic, strong) NSArray *data;
|
||||
@ -48,13 +48,15 @@
|
||||
|
||||
#if ASYNC_COLLECTION_LAYOUT
|
||||
ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionVerticalDirections];
|
||||
layoutDelegate.sizeProvider = self;
|
||||
layoutDelegate.propertiesProvider = self;
|
||||
self.collectionNode = [[ASCollectionNode alloc] initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil];
|
||||
#else
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.headerReferenceSize = CGSizeMake(50.0, 50.0);
|
||||
layout.footerReferenceSize = CGSizeMake(50.0, 50.0);
|
||||
self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
|
||||
[self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
|
||||
[self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter];
|
||||
#endif
|
||||
|
||||
self.collectionNode.dataSource = self;
|
||||
@ -82,18 +84,18 @@
|
||||
{
|
||||
NSLog(@"ViewController is not nil");
|
||||
strongSelf->_data = [[NSArray alloc] init];
|
||||
[strongSelf->_collectionView performBatchUpdates:^{
|
||||
[strongSelf->_collectionView insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]];
|
||||
[strongSelf->_collectionNode performBatchUpdates:^{
|
||||
[strongSelf->_collectionNode insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]];
|
||||
} completion:nil];
|
||||
NSLog(@"ViewController finished updating collectionView");
|
||||
NSLog(@"ViewController finished updating collectionNode");
|
||||
}
|
||||
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(), ^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
});
|
||||
#endif
|
||||
@ -108,7 +110,7 @@
|
||||
[self.collectionNode reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - ASCollectionGalleryLayoutSizeProviding
|
||||
#pragma mark - ASCollectionGalleryLayoutPropertiesProviding
|
||||
|
||||
- (CGSize)sizeForElements:(ASElementMap *)elements
|
||||
{
|
||||
|
||||