Merge commit '19a9d29aa873edcf5624bed7fa9ee86662da0bfd'

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

View File

@ -429,9 +429,11 @@
E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */; };
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 */,

View File

@ -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)

View File

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

View File

@ -125,14 +125,14 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
*
* This property may be set off the main thread, but this method will never be invoked concurrently on the
*/
@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

View File

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

View File

@ -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 nodes origin to the collection nodes origin.
*
* @param contentOffset The offset
*
* @param animated YES to animate to this new offset at a constant velocity, NO to not aniamte and immediately make the transition.
*/
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
/**
* Tuning parameters for a range type in full mode.
*
@ -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:

View File

@ -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

View File

@ -143,6 +143,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead.");
/**
* 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.")

View File

@ -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];
}

View File

@ -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.

View File

@ -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];
}
/**

View File

@ -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;

View File

@ -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)

View File

@ -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.

View File

@ -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:)])

View File

@ -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];
};
}
}

View File

@ -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.

View File

@ -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;

View File

@ -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

View File

@ -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 nodes origin to the table nodes origin.
*
* @param contentOffset The offset
*
* @param animated YES to animate to this new offset at a constant velocity, NO to not aniamte and immediately make the transition.
*/
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
/**
* YES to automatically adjust the contentOffset when cells are inserted or deleted above
* 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

View File

@ -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

View File

@ -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.")

View File

@ -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;

View File

@ -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.

View File

@ -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();

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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;
}];
}

View File

@ -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;

View File

@ -0,0 +1,144 @@
//
// ASCollectionGalleryLayoutDelegate.mm
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef MINIMAL_ASDK
#import <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>
#import <AsyncDisplayKit/_ASCollectionGalleryLayoutInfo.h>
#import <AsyncDisplayKit/_ASCollectionGalleryLayoutItem.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutDefines.h>
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutRangeType.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
#pragma mark - ASCollectionGalleryLayoutDelegate
@implementation ASCollectionGalleryLayoutDelegate {
ASScrollDirection _scrollableDirections;
struct {
unsigned int minimumLineSpacingForElements:1;
unsigned int minimumInteritemSpacingForElements:1;
unsigned int sectionInsetForElements:1;
} _propertiesProviderFlags;
}
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections
{
self = [super init];
if (self) {
// Scrollable directions must be either vertical or horizontal, but not both
ASDisplayNodeAssertTrue(ASScrollDirectionContainsVerticalDirection(scrollableDirections)
|| ASScrollDirectionContainsHorizontalDirection(scrollableDirections));
ASDisplayNodeAssertFalse(ASScrollDirectionContainsVerticalDirection(scrollableDirections)
&& ASScrollDirectionContainsHorizontalDirection(scrollableDirections));
_scrollableDirections = scrollableDirections;
}
return self;
}
- (ASScrollDirection)scrollableDirections
{
ASDisplayNodeAssertMainThread();
return _scrollableDirections;
}
- (void)setPropertiesProvider:(id<ASCollectionGalleryLayoutPropertiesProviding>)propertiesProvider
{
ASDisplayNodeAssertMainThread();
if (propertiesProvider == nil) {
_propertiesProvider = nil;
_propertiesProviderFlags = {};
} else {
_propertiesProvider = propertiesProvider;
_propertiesProviderFlags.minimumLineSpacingForElements = [_propertiesProvider respondsToSelector:@selector(minimumLineSpacingForElements:)];
_propertiesProviderFlags.minimumInteritemSpacingForElements = [_propertiesProvider respondsToSelector:@selector(minimumInteritemSpacingForElements:)];
_propertiesProviderFlags.sectionInsetForElements = [_propertiesProvider respondsToSelector:@selector(sectionInsetForElements:)];
}
}
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{
ASDisplayNodeAssertMainThread();
id<ASCollectionGalleryLayoutPropertiesProviding> propertiesProvider = _propertiesProvider;
if (propertiesProvider == nil) {
return nil;
}
CGSize itemSize = [propertiesProvider sizeForElements:elements];
UIEdgeInsets sectionInset = _propertiesProviderFlags.sectionInsetForElements ? [propertiesProvider sectionInsetForElements:elements] : UIEdgeInsetsZero;
CGFloat lineSpacing = _propertiesProviderFlags.minimumLineSpacingForElements ? [propertiesProvider minimumLineSpacingForElements:elements] : 0.0;
CGFloat interitemSpacing = _propertiesProviderFlags.minimumInteritemSpacingForElements ? [propertiesProvider minimumInteritemSpacingForElements:elements] : 0.0;
return [[_ASCollectionGalleryLayoutInfo alloc] initWithItemSize:itemSize
minimumLineSpacing:lineSpacing
minimumInteritemSpacing:interitemSpacing
sectionInset:sectionInset];
}
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
CGSize pageSize = context.viewportSize;
ASScrollDirection scrollableDirections = context.scrollableDirections;
_ASCollectionGalleryLayoutInfo *info = ASDynamicCast(context.additionalInfo, _ASCollectionGalleryLayoutInfo);
CGSize itemSize = info.itemSize;
if (info == nil || CGSizeEqualToSize(CGSizeZero, itemSize)) {
return [[ASCollectionLayoutState alloc] initWithContext:context];
}
NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements,
ASCollectionElement *element,
[[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]);
if (children.count == 0) {
return [[ASCollectionLayoutState alloc] initWithContext:context];
}
// Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element
ASStackLayoutDirection stackDirection = ASScrollDirectionContainsVerticalDirection(scrollableDirections)
? ASStackLayoutDirectionHorizontal
: ASStackLayoutDirectionVertical;
ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:stackDirection
spacing:info.minimumInteritemSpacing
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStart
flexWrap:ASStackLayoutFlexWrapWrap
alignContent:ASStackLayoutAlignContentStart
lineSpacing:info.minimumLineSpacing
children:children];
stackSpec.concurrent = YES;
ASLayoutSpec *finalSpec = stackSpec;
UIEdgeInsets sectionInset = info.sectionInset;
if (UIEdgeInsetsEqualToEdgeInsets(sectionInset, UIEdgeInsetsZero) == NO) {
finalSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:sectionInset child:stackSpec];
}
ASLayout *layout = [finalSpec layoutThatFits:ASSizeRangeForCollectionLayoutThatFitsViewportSize(pageSize, scrollableDirections)];
return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) {
_ASGalleryLayoutItem *item = ASDynamicCast(sublayout.layoutElement, _ASGalleryLayoutItem);
return item ? item.collectionElement : nil;
}];
}
@end
#endif

View File

@ -29,6 +29,7 @@ AS_SUBCLASSING_RESTRICTED
@interface ASCollectionLayoutContext : NSObject
@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;

View File

@ -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) {

View File

@ -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.

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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];
}
}

View File

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

View File

@ -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);

View File

@ -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) {

View File

@ -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());
}

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

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

View File

@ -0,0 +1,72 @@
//
// _ASCollectionGalleryLayoutInfo.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/_ASCollectionGalleryLayoutInfo.h>
#import <AsyncDisplayKit/ASHashing.h>
@implementation _ASCollectionGalleryLayoutInfo
- (instancetype)initWithItemSize:(CGSize)itemSize
minimumLineSpacing:(CGFloat)minimumLineSpacing
minimumInteritemSpacing:(CGFloat)minimumInteritemSpacing
sectionInset:(UIEdgeInsets)sectionInset
{
self = [super init];
if (self) {
_itemSize = itemSize;
_minimumLineSpacing = minimumLineSpacing;
_minimumInteritemSpacing = minimumInteritemSpacing;
_sectionInset = sectionInset;
}
return self;
}
- (BOOL)isEqualToInfo:(_ASCollectionGalleryLayoutInfo *)info
{
if (info == nil) {
return NO;
}
return CGSizeEqualToSize(_itemSize, info.itemSize)
&& _minimumLineSpacing == info.minimumLineSpacing
&& _minimumInteritemSpacing == info.minimumInteritemSpacing
&& UIEdgeInsetsEqualToEdgeInsets(_sectionInset, info.sectionInset);
}
- (BOOL)isEqual:(id)other
{
if (self == other) {
return YES;
}
if (! [other isKindOfClass:[_ASCollectionGalleryLayoutInfo class]]) {
return NO;
}
return [self isEqualToInfo:other];
}
- (NSUInteger)hash
{
struct {
CGSize itemSize;
CGFloat minimumLineSpacing;
CGFloat minimumInteritemSpacing;
UIEdgeInsets sectionInset;
} data = {
_itemSize,
_minimumLineSpacing,
_minimumInteritemSpacing,
_sectionInset,
};
return ASHashBytes(&data, sizeof(data));
}
@end

View File

@ -38,7 +38,12 @@ ASDISPLAYNODE_EXTERN_C_BEGIN
@interface ASDisplayNode (ASResizableContents) <ASResizableContents>
@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);
/**

View File

@ -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.

View File

@ -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;
}

View File

@ -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.");

View File

@ -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);
}

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
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' }

View File

@ -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.

View File

@ -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; its called once and is the point where the backing view has been loaded. It is guaranteed to be called on the **main thread** and is the appropriate place to do any UIKit things (such as adding gesture recognizers, touching the view / layer, initializing UIKIt objects).
This method is conceptually similar to UIViewController's `-viewDidLoad` method; its called once and is the point where the backing view has been loaded. It is guaranteed to be called on the **main thread** and is the appropriate place to do any UIKit things (such as adding gesture recognizers, touching the view / layer, initializing UIKit objects).
### `-layoutSpecThatFits:`

View File

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

View File

@ -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
{