Merge commit 'ba08ae1318cac3a40c4c0fe6c920e1675117dc2f'

This commit is contained in:
Peter 2017-08-04 13:12:11 +03:00
commit 555fe0239d
42 changed files with 566 additions and 322 deletions

View File

@ -425,6 +425,7 @@
DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; };
DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; };
E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */; }; E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */; };
E54E00721F1D3828000B30D7 /* ASPagerNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; };
E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */; }; E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */; };
E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */; }; E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */; };
E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; }; E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; };
@ -912,6 +913,7 @@
E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutFlatteningTests.m; sourceTree = "<group>"; }; E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutFlatteningTests.m; sourceTree = "<group>"; };
E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTransition.mm; sourceTree = "<group>"; }; E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTransition.mm; sourceTree = "<group>"; };
E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTransition.h; sourceTree = "<group>"; }; E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTransition.h; sourceTree = "<group>"; };
E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASPagerNode+Beta.h"; sourceTree = "<group>"; };
E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPageTable.h; sourceTree = "<group>"; }; E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPageTable.h; sourceTree = "<group>"; };
E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPageTable.m; sourceTree = "<group>"; }; E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPageTable.m; sourceTree = "<group>"; };
E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutElement.mm; sourceTree = "<group>"; }; E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutElement.mm; sourceTree = "<group>"; };
@ -1101,6 +1103,7 @@
698371DA1E4379CD00437585 /* ASNodeController+Beta.m */, 698371DA1E4379CD00437585 /* ASNodeController+Beta.m */,
25E327541C16819500A2170C /* ASPagerNode.h */, 25E327541C16819500A2170C /* ASPagerNode.h */,
25E327551C16819500A2170C /* ASPagerNode.m */, 25E327551C16819500A2170C /* ASPagerNode.m */,
E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */,
A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */, A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */,
A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */, A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */,
CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */, CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */,
@ -1708,6 +1711,7 @@
isa = PBXHeadersBuildPhase; isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E54E00721F1D3828000B30D7 /* ASPagerNode+Beta.h in Headers */,
E5B225281F1790D6001E1431 /* ASHashing.h in Headers */, E5B225281F1790D6001E1431 /* ASHashing.h in Headers */,
CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */, CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */,
693A1DCA1ECC944E00D0C9D2 /* IGListAdapter+AsyncDisplayKit.h in Headers */, 693A1DCA1ECC944E00D0C9D2 /* IGListAdapter+AsyncDisplayKit.h in Headers */,

View File

@ -1,9 +1,16 @@
## master ## master
* Add your own contributions to the next release on the line below this with your name. * Add your own contributions to the next release on the line below this with your name.
- [ASStackLayoutSpec] Add lineSpacing property working with flex wrap. [Flo Vouin](https://github.com/flovouin)
- [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] 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) - [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) - Add ASCollectionGalleryLayoutDelegate - an async collection layout that makes same-size collections (e.g photo galleries, pagers, etc) fast and lightweight! [Huy Nguyen](https://github.com/nguyenhuy/) [#76](https://github.com/TextureGroup/Texture/pull/76) [#451](https://github.com/TextureGroup/Texture/pull/451)
- Fix an issue that causes infinite layout loop in ASDisplayNode after [#428](https://github.com/TextureGroup/Texture/pull/428) [Huy Nguyen](https://github.com/nguyenhuy) [#455](https://github.com/TextureGroup/Texture/pull/455)
- Fix an issue in layout transition that causes it to unexpectedly use the old layout [Huy Nguyen](https://github.com/nguyenhuy) [#464](https://github.com/TextureGroup/Texture/pull/464)
- Add -[ASDisplayNode detailedLayoutDescription] property to aid debugging. [Adlai Holler](https://github.com/Adlai-Holler) [#476](https://github.com/TextureGroup/Texture/pull/476)
##2.3.5 ##2.3.5
- Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler)
@ -11,6 +18,7 @@
- Fix a crash where scrolling a table view after entering editing mode could lead to bad internal states in the table. [Huy Nguyen](https://github.com/nguyenhuy) [#416](https://github.com/TextureGroup/Texture/pull/416/) - Fix a crash where scrolling a table view after entering editing mode could lead to bad internal states in the table. [Huy Nguyen](https://github.com/nguyenhuy) [#416](https://github.com/TextureGroup/Texture/pull/416/)
- Fix a crash in collection view that occurs if batch updates are performed while scrolling [Huy Nguyen](https://github.com/nguyenhuy) [#378](https://github.com/TextureGroup/Texture/issues/378) - Fix a crash in collection view that occurs if batch updates are performed while scrolling [Huy Nguyen](https://github.com/nguyenhuy) [#378](https://github.com/TextureGroup/Texture/issues/378)
- Some improvements in ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#407](https://github.com/TextureGroup/Texture/pull/407) - Some improvements in ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#407](https://github.com/TextureGroup/Texture/pull/407)
- Small refactors in ASDataController [Huy Nguyen](https://github.com/TextureGroup/Texture/pull/443) [#443](https://github.com/TextureGroup/Texture/pull/443)
##2.3.4 ##2.3.4
- [Yoga] Rewrite YOGA_TREE_CONTIGUOUS mode with improved behavior and cleaner integration [Scott Goodson](https://github.com/appleguy) - [Yoga] Rewrite YOGA_TREE_CONTIGUOUS mode with improved behavior and cleaner integration [Scott Goodson](https://github.com/appleguy)

View File

@ -89,7 +89,6 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
* *
* @return The supplementary element kind, or @c nil if this node does not represent a supplementary element. * @return The supplementary element kind, or @c nil if this node does not represent a supplementary element.
*/ */
//TODO change this to be a generic "kind" or "elementKind" that exposes `nil` for row kind
@property (atomic, copy, readonly, nullable) NSString *supplementaryElementKind; @property (atomic, copy, readonly, nullable) NSString *supplementaryElementKind;
/* /*

View File

@ -218,8 +218,10 @@
// Intentionally allocate the view here and trigger a layout pass on it, which in turn will trigger the intial data load. // Intentionally allocate the view here and trigger a layout pass on it, which in turn will trigger the intial data load.
// We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view.
// TODO (ASCL) If this node supports async layout, kick off the initial data load without allocating the view // TODO (ASCL) If this node supports async layout, kick off the initial data load without allocating the view
if (CGRectEqualToRect(self.bounds, CGRectZero) == NO) {
[[self view] layoutIfNeeded]; [[self view] layoutIfNeeded];
} }
}
#if ASRangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
- (void)didEnterVisibleState - (void)didEnterVisibleState

View File

@ -622,15 +622,17 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
return CGSizeZero; return CGSizeZero;
} }
NSString *supplementaryKind = element.supplementaryElementKind; ASCellNode *node = element.node;
NSIndexPath *indexPath = [_dataController.visibleMap indexPathForElement:element]; BOOL useUIKitCell = node.shouldUseUIKitCell;
ASSizeRange sizeRange; if (useUIKitCell) {
if (supplementaryKind == nil) { // In this case, we should use the exact value that was stashed earlier by calling sizeForItem:, referenceSizeFor*, etc.
sizeRange = [self dataController:_dataController constrainedSizeForNodeAtIndexPath:indexPath]; // Although the node would use the preferredSize in layoutThatFits, we can skip this because there's no constrainedSize.
ASDisplayNodeAssert([node.superclass isSubclassOfClass:[ASCellNode class]] == NO,
@"Placeholder cells for UIKit passthrough should be generic ASCellNodes: %@", node);
return node.style.preferredSize;
} else { } else {
sizeRange = [self dataController:_dataController constrainedSizeForSupplementaryNodeOfKind:supplementaryKind atIndexPath:indexPath]; return [node layoutThatFits:element.constrainedSize].size;
} }
return [element.node layoutThatFits:sizeRange].size;
} }
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath - (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
@ -949,63 +951,84 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
return [_dataController.visibleMap numberOfItemsInSection:section]; return [_dataController.visibleMap numberOfItemsInSection:section];
} }
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath #define ASIndexPathForSection(section) [NSIndexPath indexPathForItem:0 inSection:section]
#define ASFlowLayoutDefault(layout, property, default) \
({ \
UICollectionViewFlowLayout *flowLayout = ASDynamicCast(layout, UICollectionViewFlowLayout); \
flowLayout ? flowLayout.property : default; \
})
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath];
if (element == nil) { return e ? [self sizeForElement:e] : ASFlowLayoutDefault(layout, itemSize, CGSizeZero);
ASDisplayNodeAssert(NO, @"Unexpected nil element for collectionView:layout:sizeForItemAtIndexPath: %@, %@, %@", self, collectionViewLayout, indexPath);
return CGSizeZero;
} }
ASCellNode *cell = element.node; - (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
if (cell.shouldUseUIKitCell) { referenceSizeForHeaderInSection:(NSInteger)section
if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) {
CGSize size = [(id)_asyncDelegate collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:indexPath];
cell.style.preferredSize = size;
return size;
}
}
return [self sizeForElement:element];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForHeaderInSection:(NSInteger)section
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; ASElementMap *map = _dataController.visibleMap;
ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionHeader ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionHeader
atIndexPath:indexPath]; atIndexPath:ASIndexPathForSection(section)];
if (element == nil) { return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, headerReferenceSize, CGSizeZero);
return CGSizeZero;
} }
if (element.node.shouldUseUIKitCell && _asyncDelegateFlags.interop) { - (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) { referenceSizeForFooterInSection:(NSInteger)section
return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForHeaderInSection:section];
}
}
return [self sizeForElement:element];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForFooterInSection:(NSInteger)section
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; ASElementMap *map = _dataController.visibleMap;
ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionFooter ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionFooter
atIndexPath:indexPath]; atIndexPath:ASIndexPathForSection(section)];
if (element == nil) { return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, footerReferenceSize, CGSizeZero);
return CGSizeZero;
} }
if (element.node.shouldUseUIKitCell && _asyncDelegateFlags.interop) { // For the methods that call delegateIndexPathForSection:withSelector:, translate the section from
if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]) { // visibleMap to pendingMap. If the section no longer exists, or the delegate doesn't implement
return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForFooterInSection:section]; // the selector, we will return a nil indexPath (and then use the ASFlowLayoutDefault).
- (NSIndexPath *)delegateIndexPathForSection:(NSInteger)section withSelector:(SEL)selector
{
if ([_asyncDelegate respondsToSelector:selector]) {
return [_dataController.pendingMap convertIndexPath:ASIndexPathForSection(section)
fromMap:_dataController.visibleMap];
} else {
return nil;
} }
} }
return [self sizeForElement:element]; - (UIEdgeInsets)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
insetForSectionAtIndex:(NSInteger)section
{
NSIndexPath *indexPath = [self delegateIndexPathForSection:section withSelector:_cmd];
if (indexPath) {
return [(id)_asyncDelegate collectionView:cv layout:l insetForSectionAtIndex:indexPath.section];
}
return ASFlowLayoutDefault(l, sectionInset, UIEdgeInsetsZero);
}
- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
NSIndexPath *indexPath = [self delegateIndexPathForSection:section withSelector:_cmd];
if (indexPath) {
return [(id)_asyncDelegate collectionView:cv layout:l
minimumInteritemSpacingForSectionAtIndex:indexPath.section];
}
return ASFlowLayoutDefault(l, minimumInteritemSpacing, 10.0); // Default is documented as 10.0
}
- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
NSIndexPath *indexPath = [self delegateIndexPathForSection:section withSelector:_cmd];
if (indexPath) {
return [(id)_asyncDelegate collectionView:cv layout:l
minimumLineSpacingForSectionAtIndex:indexPath.section];
}
return ASFlowLayoutDefault(l, minimumLineSpacing, 10.0); // Default is documented as 10.0
} }
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
@ -1678,11 +1701,19 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
if (block == nil) { if (block == nil) {
if (_asyncDataSourceFlags.interop) { if (_asyncDataSourceFlags.interop) {
UICollectionViewLayout *layout = self.collectionViewLayout;
CGSize preferredSize = CGSizeZero;
SEL sizeForItem = @selector(collectionView:layout:sizeForItemAtIndexPath:);
if ([_asyncDelegate respondsToSelector:sizeForItem]) {
preferredSize = [(id)_asyncDelegate collectionView:self layout:layout sizeForItemAtIndexPath:indexPath];
} else {
preferredSize = ASFlowLayoutDefault(layout, itemSize, CGSizeZero);
}
block = ^{ block = ^{
ASCellNode *cell = [[ASCellNode alloc] init]; ASCellNode *node = [[ASCellNode alloc] init];
cell.shouldUseUIKitCell = YES; node.shouldUseUIKitCell = YES;
cell.style.preferredSize = CGSizeZero; node.style.preferredSize = preferredSize;
return cell; return node;
}; };
} else { } else {
ASDisplayNodeFailAssert(@"ASCollection could not get a node block for item at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the <ASCollectionDataSourceInterop> protocol!", indexPath, cell, block); ASDisplayNodeFailAssert(@"ASCollection could not get a node block for item at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the <ASCollectionDataSourceInterop> protocol!", indexPath, cell, block);
@ -1773,9 +1804,31 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
nodeBlock = ^{ return node; }; nodeBlock = ^{ return node; };
} else { } else {
BOOL useUIKitCell = _asyncDataSourceFlags.interop; BOOL useUIKitCell = _asyncDataSourceFlags.interop;
CGSize preferredSize = CGSizeZero;
if (useUIKitCell) {
UICollectionViewLayout *layout = self.collectionViewLayout;
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
SEL sizeForHeader = @selector(collectionView:layout:referenceSizeForHeaderInSection:);
if ([_asyncDelegate respondsToSelector:sizeForHeader]) {
preferredSize = [(id)_asyncDelegate collectionView:self layout:layout
referenceSizeForHeaderInSection:indexPath.section];
} else {
preferredSize = ASFlowLayoutDefault(layout, headerReferenceSize, CGSizeZero);
}
} else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) {
SEL sizeForFooter = @selector(collectionView:layout:referenceSizeForFooterInSection:);
if ([_asyncDelegate respondsToSelector:sizeForFooter]) {
preferredSize = [(id)_asyncDelegate collectionView:self layout:layout
referenceSizeForFooterInSection:indexPath.section];
} else {
preferredSize = ASFlowLayoutDefault(layout, footerReferenceSize, CGSizeZero);
}
}
}
nodeBlock = ^{ nodeBlock = ^{
ASCellNode *node = [[ASCellNode alloc] init]; ASCellNode *node = [[ASCellNode alloc] init];
node.shouldUseUIKitCell = useUIKitCell; node.shouldUseUIKitCell = useUIKitCell;
node.style.preferredSize = preferredSize;
return node; return node;
}; };
} }
@ -1901,19 +1954,16 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
#pragma mark - ASRangeControllerDelegate #pragma mark - ASRangeControllerDelegate
- (void)rangeController:(ASRangeController *)rangeController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) { if (!self.asyncDataSource || _superIsPendingDataLoad) {
updates();
[changeSet executeCompletionHandlerWithFinished:NO];
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
} }
if (changeSet.includesReloadData) { //TODO Do we need to notify _layoutFacilitator before reloadData?
//TODO Do we need to notify _layoutFacilitator?
return;
}
for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES];
} }
@ -1929,16 +1979,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES];
} }
}
- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
updates();
[changeSet executeCompletionHandlerWithFinished:NO];
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ ASPerformBlockWithoutAnimation(!changeSet.animated, ^{
as_activity_scope(as_activity_create("Commit collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); as_activity_scope(as_activity_create("Commit collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT));

View File

@ -185,6 +185,11 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
- (ASSizeRange)constrainedSizeForCalculatedLayout - (ASSizeRange)constrainedSizeForCalculatedLayout
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
return [self _locked_constrainedSizeForCalculatedLayout];
}
- (ASSizeRange)_locked_constrainedSizeForCalculatedLayout
{
if (_pendingDisplayNodeLayout != nullptr) { if (_pendingDisplayNodeLayout != nullptr) {
return _pendingDisplayNodeLayout->constrainedSize; return _pendingDisplayNodeLayout->constrainedSize;
} }
@ -305,7 +310,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
// Prefer _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout (if exists, it's the newest) // Prefer _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout (if exists, it's the newest)
// If there is no _pending, check if _calculated is valid to reuse (avoiding recalculation below). // If there is no _pending, check if _calculated is valid to reuse (avoiding recalculation below).
if (_pendingDisplayNodeLayout == nullptr) { if (_pendingDisplayNodeLayout == nullptr || _pendingDisplayNodeLayout->version < _layoutVersion) {
if (_calculatedDisplayNodeLayout->version >= _layoutVersion if (_calculatedDisplayNodeLayout->version >= _layoutVersion
&& (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES
|| CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) { || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
@ -314,7 +319,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
} }
as_activity_create_for_scope("Update node layout for current bounds"); as_activity_create_for_scope("Update node layout for current bounds");
as_log_verbose(ASLayoutLog(), "Node %@, bounds size %@, calculatedSize %@, calculatedIsDirty %d", self, NSStringFromCGSize(boundsSizeForLayout), NSStringFromCGSize(_calculatedDisplayNodeLayout->layout.size), _calculatedDisplayNodeLayout->isDirty()); as_log_verbose(ASLayoutLog(), "Node %@, bounds size %@, calculatedSize %@, calculatedIsDirty %d", self, NSStringFromCGSize(boundsSizeForLayout), NSStringFromCGSize(_calculatedDisplayNodeLayout->layout.size), _calculatedDisplayNodeLayout->version < _layoutVersion.load());
// _calculatedDisplayNodeLayout is not reusable we need to transition to a new one // _calculatedDisplayNodeLayout is not reusable we need to transition to a new one
[self cancelLayoutTransition]; [self cancelLayoutTransition];
@ -352,8 +357,10 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
ASLayout *layout = [self calculateLayoutThatFits:constrainedSize ASLayout *layout = [self calculateLayoutThatFits:constrainedSize
restrictedToSize:self.style.size restrictedToSize:self.style.size
relativeToParentSize:boundsSizeForLayout]; relativeToParentSize:boundsSizeForLayout];
nextLayout = std::make_shared<ASDisplayNodeLayout>(layout, constrainedSize, boundsSizeForLayout, version); nextLayout = std::make_shared<ASDisplayNodeLayout>(layout, constrainedSize, boundsSizeForLayout, version);
// Now that the constrained size of pending layout might have been reused, the layout is useless
// Release it and any orphaned subnodes it retains
_pendingDisplayNodeLayout = nullptr;
} }
if (didCreateNewContext) { if (didCreateNewContext) {
@ -373,6 +380,9 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
// particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout. // particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout.
nextLayout->requestedLayoutFromAbove = YES; nextLayout->requestedLayoutFromAbove = YES;
[self _setNeedsLayoutFromAbove]; [self _setNeedsLayoutFromAbove];
// Update the layout's version here because _setNeedsLayoutFromAbove calls __setNeedsLayout which in turn increases _layoutVersion
// Failing to do this will cause the layout to be invalid immediately
nextLayout->version = _layoutVersion;
} }
// Prepare to transition to nextLayout // Prepare to transition to nextLayout
@ -473,6 +483,11 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
- (BOOL)_isLayoutTransitionInvalid - (BOOL)_isLayoutTransitionInvalid
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
return [self _locked_isLayoutTransitionValid];
}
- (BOOL)_locked_isLayoutTransitionValid
{
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) {
ASLayoutElementContext *context = ASLayoutElementGetCurrentContext(); ASLayoutElementContext *context = ASLayoutElementGetCurrentContext();
if (context == nil || _pendingTransitionID != context.transitionID) { if (context == nil || _pendingTransitionID != context.transitionID) {
@ -505,9 +520,6 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
measurementCompletion:(void(^)())completion measurementCompletion:(void(^)())completion
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[self setNeedsLayout];
[self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass] [self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass]
animated:animated animated:animated
shouldMeasureAsync:shouldMeasureAsync shouldMeasureAsync:shouldMeasureAsync
@ -531,16 +543,27 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
return; return;
} }
{
ASDN::MutexLocker l(__instanceLock__);
// Check if we are a subnode in a layout transition. // Check if we are a subnode in a layout transition.
// In this case no measurement is needed as we're part of the layout transition. // In this case no measurement is needed as we're part of the layout transition.
if ([self _isLayoutTransitionInvalid]) { if ([self _locked_isLayoutTransitionValid]) {
return; return;
} }
{ if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) {
ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(NO, @"Can't start a transition when one of the supernodes is performing one.");
ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one."); return;
} }
}
// Invalidate calculated layout because this method acts as an animated "setNeedsLayout" for nodes.
// If the user has reconfigured the node and calls this, we should never return a stale layout
// for subsequent calls to layoutThatFits: regardless of size range. We choose this method rather than
// -setNeedsLayout because that method also triggers a CA layout invalidation, which isn't necessary at this time.
// See https://github.com/TextureGroup/Texture/issues/463
[self invalidateCalculatedLayout];
// Every new layout transition has a transition id associated to check in subsequent transitions for cancelling // Every new layout transition has a transition id associated to check in subsequent transitions for cancelling
int32_t transitionID = [self _startNewTransition]; int32_t transitionID = [self _startNewTransition];

View File

@ -96,6 +96,13 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)didExitPreloadState; - (void)didExitPreloadState;
/**
* @abstract Called when the node has completed applying the layout.
* @discussion Can be used for operations that are performed after layout has completed.
* @note This method is guaranteed to be called on main.
*/
- (void)nodeDidLayout;
@end @end
@interface ASDisplayNode (Subclassing) <ASInterfaceStateDelegate> @interface ASDisplayNode (Subclassing) <ASInterfaceStateDelegate>

View File

@ -589,6 +589,11 @@ extern NSInteger const ASDefaultDrawingPriority;
*/ */
- (NSString *)displayNodeRecursiveDescription AS_WARN_UNUSED_RESULT; - (NSString *)displayNodeRecursiveDescription AS_WARN_UNUSED_RESULT;
/**
* A detailed description of this node's layout state. This is useful when debugging.
*/
@property (atomic, copy, readonly) NSString *detailedLayoutDescription;
@end @end
/** /**

View File

@ -919,6 +919,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return; return;
} }
as_activity_create_for_scope("-[ASDisplayNode __layout]");
// This method will confirm that the layout is up to date (and update if needed). // This method will confirm that the layout is up to date (and update if needed).
// Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning).
[self _locked_measureNodeWithBoundsIfNecessary:bounds]; [self _locked_measureNodeWithBoundsIfNecessary:bounds];
@ -1124,6 +1126,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
ASDisplayNodeAssertTrue(self.isNodeLoaded); ASDisplayNodeAssertTrue(self.isNodeLoaded);
[_interfaceStateDelegate nodeDidLayout];
} }
#pragma mark Layout Transition #pragma mark Layout Transition
@ -3380,6 +3383,38 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
return subtree; return subtree;
} }
- (NSString *)detailedLayoutDescription
{
ASPushMainThreadAssertionsDisabled();
ASDN::MutexLocker l(__instanceLock__);
auto props = [NSMutableArray<NSDictionary *> array];
[props addObject:@{ @"layoutVersion": @(_layoutVersion.load()) }];
[props addObject:@{ @"bounds": [NSValue valueWithCGRect:self.bounds] }];
if (_calculatedDisplayNodeLayout != nullptr) {
ASDisplayNodeLayout c = *_calculatedDisplayNodeLayout;
[props addObject:@{ @"calculatedLayout": c.layout }];
[props addObject:@{ @"calculatedVersion": @(c.version) }];
[props addObject:@{ @"calculatedConstrainedSize" : NSStringFromASSizeRange(c.constrainedSize) }];
if (c.requestedLayoutFromAbove) {
[props addObject:@{ @"calculatedRequestedLayoutFromAbove": @"YES" }];
}
}
if (_pendingDisplayNodeLayout != nullptr) {
ASDisplayNodeLayout p = *_pendingDisplayNodeLayout;
[props addObject:@{ @"pendingLayout": p.layout }];
[props addObject:@{ @"pendingVersion": @(p.version) }];
[props addObject:@{ @"pendingConstrainedSize" : NSStringFromASSizeRange(p.constrainedSize) }];
if (p.requestedLayoutFromAbove) {
[props addObject:@{ @"pendingRequestedLayoutFromAbove": (id)kCFNull }];
}
}
ASPopMainThreadAssertionsDisabled();
return ASObjectDescriptionMake(self, props);
}
@end @end
#pragma mark - ASDisplayNode UIKit / CA Categories #pragma mark - ASDisplayNode UIKit / CA Categories

View File

@ -672,7 +672,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
return; return;
} }
as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], url); as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL);
// Grab the lock for the rest of the block // Grab the lock for the rest of the block
ASDN::MutexLocker l(strongSelf->__instanceLock__); ASDN::MutexLocker l(strongSelf->__instanceLock__);

View File

@ -18,18 +18,15 @@
#import <AsyncDisplayKit/ASDisplayNode.h> #import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h> // for ASInterfaceState protocol #import <AsyncDisplayKit/ASDisplayNode+Subclasses.h> // for ASInterfaceState protocol
// Until an ASNodeController can be provided in place of an ASCellNode, some apps may prefer to have
// nodes keep their controllers alive (and a weak reference from controller to node)
#define INVERT_NODE_CONTROLLER_OWNERSHIP 0
/* ASNodeController is currently beta and open to change in the future */ /* ASNodeController is currently beta and open to change in the future */
@interface ASNodeController<__covariant DisplayNodeType : ASDisplayNode *> : NSObject <ASInterfaceStateDelegate> @interface ASNodeController<__covariant DisplayNodeType : ASDisplayNode *> : NSObject <ASInterfaceStateDelegate>
#if INVERT_NODE_CONTROLLER_OWNERSHIP @property (nonatomic, strong /* may be weak! */) DisplayNodeType node;
@property (nonatomic, weak) DisplayNodeType node;
#else // Until an ASNodeController can be provided in place of an ASCellNode, some apps may prefer to have
@property (nonatomic, strong) DisplayNodeType node; // nodes keep their controllers alive (and a weak reference from controller to node)
#endif
@property (nonatomic, assign) BOOL shouldInvertStrongReference;
- (void)loadNode; - (void)loadNode;
@ -47,3 +44,9 @@
fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER; fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER;
@end @end
@interface ASDisplayNode (ASNodeController)
@property(nonatomic, readonly) ASNodeController *nodeController;
@end

View File

@ -15,34 +15,28 @@
// http://www.apache.org/licenses/LICENSE-2.0 // http://www.apache.org/licenses/LICENSE-2.0
// //
#import "ASNodeController+Beta.h" #import <AsyncDisplayKit/ASWeakProxy.h>
#import "ASDisplayNode+FrameworkPrivate.h" #import <AsyncDisplayKit/ASNodeController+Beta.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#if INVERT_NODE_CONTROLLER_OWNERSHIP #define _node (_shouldInvertStrongReference ? _weakNode : _strongNode)
@interface ASDisplayNode (ASNodeController) @interface ASDisplayNode (ASNodeControllerOwnership)
@property (nonatomic, strong) ASNodeController *asdkNodeController;
@end
@implementation ASDisplayNode (ASNodeController) // This property exists for debugging purposes. Don't use __nodeController in production code.
@property (nonatomic, readonly) ASNodeController *__nodeController;
- (ASNodeController *)asdkNodeController // These setters are mutually exclusive. Setting one will clear the relationship of the other.
{ - (void)__setNodeControllerStrong:(ASNodeController *)nodeController;
return objc_getAssociatedObject(self, @selector(asdkNodeController)); - (void)__setNodeControllerWeak:(ASNodeController *)nodeController;
}
- (void)setAsdkNodeController:(ASNodeController *)asdkNodeController
{
objc_setAssociatedObject(self, @selector(asdkNodeController), asdkNodeController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end @end
#endif
@implementation ASNodeController @implementation ASNodeController
{
@synthesize node = _node; ASDisplayNode *_strongNode;
__weak ASDisplayNode *_weakNode;
}
- (instancetype)init - (instancetype)init
{ {
@ -66,16 +60,41 @@
return _node; return _node;
} }
- (void)setupReferencesWithNode:(ASDisplayNode *)node
{
if (_shouldInvertStrongReference) {
// The node should own the controller; weak reference from controller to node.
_weakNode = node;
[node __setNodeControllerStrong:self];
_strongNode = nil;
} else {
// The controller should own the node; weak reference from node to controller.
_strongNode = node;
[node __setNodeControllerWeak:self];
_weakNode = nil;
}
node.interfaceStateDelegate = self;
}
- (void)setNode:(ASDisplayNode *)node - (void)setNode:(ASDisplayNode *)node
{ {
_node = node; [self setupReferencesWithNode:node];
_node.interfaceStateDelegate = self; }
#if INVERT_NODE_CONTROLLER_OWNERSHIP
_node.asdkNodeController = self; - (void)setShouldInvertStrongReference:(BOOL)shouldInvertStrongReference
#endif {
if (_shouldInvertStrongReference != shouldInvertStrongReference) {
// Because the BOOL controls which ivar we access, get the node before toggling.
ASDisplayNode *node = _node;
_shouldInvertStrongReference = shouldInvertStrongReference;
[self setupReferencesWithNode:node];
}
} }
// subclass overrides // subclass overrides
- (void)nodeDidLayout {}
- (void)didEnterVisibleState {} - (void)didEnterVisibleState {}
- (void)didExitVisibleState {} - (void)didExitVisibleState {}
@ -89,3 +108,41 @@
fromState:(ASInterfaceState)oldState {} fromState:(ASInterfaceState)oldState {}
@end @end
@implementation ASDisplayNode (ASNodeControllerOwnership)
- (ASNodeController *)__nodeController
{
ASNodeController *nodeController = nil;
id object = objc_getAssociatedObject(self, @selector(__nodeController));
if ([object isKindOfClass:[ASWeakProxy class]]) {
nodeController = (ASNodeController *)[(ASWeakProxy *)object target];
} else {
nodeController = (ASNodeController *)object;
}
return nodeController;
}
- (void)__setNodeControllerStrong:(ASNodeController *)nodeController
{
objc_setAssociatedObject(self, @selector(__nodeController), nodeController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)__setNodeControllerWeak:(ASNodeController *)nodeController
{
// Associated objects don't support weak references. Since assign can become a dangling pointer, use ASWeakProxy.
ASWeakProxy *nodeControllerProxy = [ASWeakProxy weakProxyWithTarget:nodeController];
objc_setAssociatedObject(self, @selector(__nodeController), nodeControllerProxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
@implementation ASDisplayNode (ASNodeController)
- (ASNodeController *)nodeController {
return self.__nodeController;
}
@end

19
Source/ASPagerNode+Beta.h Normal file
View File

@ -0,0 +1,19 @@
//
// ASPagerNode+Beta.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASPagerNode.h>
@interface ASPagerNode (Beta)
- (instancetype)initUsingAsyncCollectionLayout;
@end

View File

@ -17,6 +17,10 @@
#ifndef MINIMAL_ASDK #ifndef MINIMAL_ASDK
#import <AsyncDisplayKit/ASPagerNode.h> #import <AsyncDisplayKit/ASPagerNode.h>
#import <AsyncDisplayKit/ASPagerNode+Beta.h>
#import <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>
#import <AsyncDisplayKit/ASCollectionNode+Beta.h>
#import <AsyncDisplayKit/ASDelegateProxy.h> #import <AsyncDisplayKit/ASDelegateProxy.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h> #import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h> #import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@ -26,10 +30,8 @@
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h> #import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
#import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h> #import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h>
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionDelegateFlowLayout, ASDelegateProxyInterceptor> @interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionDelegateFlowLayout, ASDelegateProxyInterceptor, ASCollectionGalleryLayoutSizeProviding>
{ {
ASPagerFlowLayout *_flowLayout;
__weak id <ASPagerDataSource> _pagerDataSource; __weak id <ASPagerDataSource> _pagerDataSource;
ASPagerNodeProxy *_proxyDataSource; ASPagerNodeProxy *_proxyDataSource;
struct { struct {
@ -66,8 +68,15 @@
{ {
ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout."); ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout.");
self = [super initWithCollectionViewLayout:flowLayout]; self = [super initWithCollectionViewLayout:flowLayout];
if (self != nil) { return self;
_flowLayout = flowLayout; }
- (instancetype)initUsingAsyncCollectionLayout
{
ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionHorizontalDirections];
self = [super initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil];
if (self) {
layoutDelegate.sizeProvider = self;
} }
return self; return self;
} }
@ -129,6 +138,14 @@
return indexPath.row; return indexPath.row;
} }
#pragma mark - ASCollectionGalleryLayoutSizeProviding
- (CGSize)sizeForElements:(ASElementMap *)elements
{
ASDisplayNodeAssertMainThread();
return self.bounds.size;
}
#pragma mark - ASCollectionDataSource #pragma mark - ASCollectionDataSource
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath

View File

@ -1475,19 +1475,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
#pragma mark - ASRangeControllerDelegate #pragma mark - ASRangeControllerDelegate
- (void)rangeController:(ASRangeController *)rangeController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_automaticallyAdjustsContentOffset && !changeSet.includesReloadData) {
[self beginAdjustingContentOffset];
}
}
- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _updatingInResponseToInteractiveMove) { if (!self.asyncDataSource || _updatingInResponseToInteractiveMove) {
@ -1512,6 +1500,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
return; return;
} }
BOOL shouldAdjustContentOffset = (_automaticallyAdjustsContentOffset && !changeSet.includesReloadData);
if (shouldAdjustContentOffset) {
[self beginAdjustingContentOffset];
}
NSUInteger numberOfUpdates = 0; NSUInteger numberOfUpdates = 0;
LOG(@"--- UITableView beginUpdates"); LOG(@"--- UITableView beginUpdates");
@ -1621,7 +1614,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[_rangeController updateIfNeeded]; [_rangeController updateIfNeeded];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates];
}); });
if (_automaticallyAdjustsContentOffset) { if (shouldAdjustContentOffset) {
[self endAdjustingContentOffsetAnimated:changeSet.animated]; [self endAdjustingContentOffsetAnimated:changeSet.animated];
} }
[changeSet executeCompletionHandlerWithFinished:YES]; [changeSet executeCompletionHandlerWithFinished:YES];

View File

@ -42,6 +42,7 @@
#import <AsyncDisplayKit/ASTableNode.h> #import <AsyncDisplayKit/ASTableNode.h>
#import <AsyncDisplayKit/ASCollectionView.h> #import <AsyncDisplayKit/ASCollectionView.h>
#import <AsyncDisplayKit/ASCollectionNode.h> #import <AsyncDisplayKit/ASCollectionNode.h>
#import <AsyncDisplayKit/ASCollectionNode+Beta.h>
#import <AsyncDisplayKit/ASCollectionViewLayoutInspector.h> #import <AsyncDisplayKit/ASCollectionViewLayoutInspector.h>
#import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h> #import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h>
#import <AsyncDisplayKit/ASCellNode.h> #import <AsyncDisplayKit/ASCellNode.h>
@ -61,6 +62,7 @@
#import <AsyncDisplayKit/ASPagerFlowLayout.h> #import <AsyncDisplayKit/ASPagerFlowLayout.h>
#import <AsyncDisplayKit/ASPagerNode.h> #import <AsyncDisplayKit/ASPagerNode.h>
#import <AsyncDisplayKit/ASPagerNode+Beta.h>
#import <AsyncDisplayKit/ASNodeController+Beta.h> #import <AsyncDisplayKit/ASNodeController+Beta.h>
#import <AsyncDisplayKit/ASViewController.h> #import <AsyncDisplayKit/ASViewController.h>
@ -125,7 +127,5 @@
#import <AsyncDisplayKit/AsyncDisplayKit+Tips.h> #import <AsyncDisplayKit/AsyncDisplayKit+Tips.h>
#import <AsyncDisplayKit/ASDisplayNode+Deprecated.h> #import <AsyncDisplayKit/ASDisplayNode+Deprecated.h>
#import <AsyncDisplayKit/ASCollectionNode+Beta.h>
#import <AsyncDisplayKit/IGListAdapter+AsyncDisplayKit.h> #import <AsyncDisplayKit/IGListAdapter+AsyncDisplayKit.h>
#import <AsyncDisplayKit/AsyncDisplayKit+IGListKitMethods.h> #import <AsyncDisplayKit/AsyncDisplayKit+IGListKitMethods.h>

View File

@ -28,7 +28,6 @@ NS_ASSUME_NONNULL_BEGIN
AS_SUBCLASSING_RESTRICTED AS_SUBCLASSING_RESTRICTED
@interface ASCollectionElement : NSObject @interface ASCollectionElement : NSObject
//TODO change this to be a generic "kind" or "elementKind" that exposes `nil` for row kind
@property (nonatomic, readonly, copy, nullable) NSString *supplementaryElementKind; @property (nonatomic, readonly, copy, nullable) NSString *supplementaryElementKind;
@property (nonatomic, assign) ASSizeRange constrainedSize; @property (nonatomic, assign) ASSizeRange constrainedSize;
@property (nonatomic, readonly, weak) id<ASRangeManagingNode> owningNode; @property (nonatomic, readonly, weak) id<ASRangeManagingNode> owningNode;

View File

@ -48,11 +48,13 @@
- (ASScrollDirection)scrollableDirections - (ASScrollDirection)scrollableDirections
{ {
ASDisplayNodeAssertMainThread();
return _scrollableDirections; return _scrollableDirections;
} }
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements - (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{ {
ASDisplayNodeAssertMainThread();
return nil; return nil;
} }
@ -61,9 +63,7 @@
ASElementMap *elements = context.elements; ASElementMap *elements = context.elements;
NSMutableArray<ASCellNode *> *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); NSMutableArray<ASCellNode *> *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node);
if (children.count == 0) { if (children.count == 0) {
return [[ASCollectionLayoutState alloc] initWithContext:context return [[ASCollectionLayoutState alloc] initWithContext:context];
contentSize:CGSizeZero
elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]];
} }
ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal

View File

@ -15,8 +15,25 @@
#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h> #import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
#import <AsyncDisplayKit/ASScrollDirection.h> #import <AsyncDisplayKit/ASScrollDirection.h>
@class ASElementMap;
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@protocol ASCollectionGalleryLayoutSizeProviding <NSObject>
/**
* Returns the fixed size of each and every element.
*
* @discussion This method will only be called on main thread.
*
* @param elements All elements to be sized.
*
* @return The elements' size
*/
- (CGSize)sizeForElements:(ASElementMap *)elements;
@end
/** /**
* A thread-safe layout delegate that arranges items with the same size into a flow layout. * A thread-safe layout delegate that arranges items with the same size into a flow layout.
* *
@ -25,7 +42,9 @@ NS_ASSUME_NONNULL_BEGIN
AS_SUBCLASSING_RESTRICTED AS_SUBCLASSING_RESTRICTED
@interface ASCollectionGalleryLayoutDelegate : NSObject <ASCollectionLayoutDelegate> @interface ASCollectionGalleryLayoutDelegate : NSObject <ASCollectionLayoutDelegate>
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections itemSize:(CGSize)itemSize NS_DESIGNATED_INITIALIZER; @property (nonatomic, weak) id<ASCollectionGalleryLayoutSizeProviding> sizeProvider;
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER;
- (instancetype)init __unavailable; - (instancetype)init __unavailable;

View File

@ -33,40 +33,47 @@
CGSize _itemSize; CGSize _itemSize;
} }
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections itemSize:(CGSize)itemSize - (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections
{ {
self = [super init]; self = [super init];
if (self) { if (self) {
ASDisplayNodeAssertFalse(CGSizeEqualToSize(CGSizeZero, itemSize));
_scrollableDirections = scrollableDirections; _scrollableDirections = scrollableDirections;
_itemSize = itemSize;
} }
return self; return self;
} }
- (ASScrollDirection)scrollableDirections - (ASScrollDirection)scrollableDirections
{ {
ASDisplayNodeAssertMainThread();
return _scrollableDirections; return _scrollableDirections;
} }
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements - (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{ {
return [NSValue valueWithCGSize:_itemSize]; ASDisplayNodeAssertMainThread();
if (_sizeProvider == nil) {
return nil;
}
return [NSValue valueWithCGSize:[_sizeProvider sizeForElements:elements]];
} }
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context + (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{ {
ASElementMap *elements = context.elements; ASElementMap *elements = context.elements;
CGSize pageSize = context.viewportSize; CGSize pageSize = context.viewportSize;
CGSize itemSize = ((NSValue *)context.additionalInfo).CGSizeValue;
ASScrollDirection scrollableDirections = context.scrollableDirections; ASScrollDirection scrollableDirections = context.scrollableDirections;
CGSize itemSize = context.additionalInfo ? ((NSValue *)context.additionalInfo).CGSizeValue : CGSizeZero;
if (CGSizeEqualToSize(CGSizeZero, itemSize)) {
return [[ASCollectionLayoutState alloc] initWithContext:context];
}
NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements,
ASCollectionElement *element, ASCollectionElement *element,
[[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]);
if (children.count == 0) { if (children.count == 0) {
return [[ASCollectionLayoutState alloc] initWithContext:context return [[ASCollectionLayoutState alloc] initWithContext:context];
contentSize:CGSizeZero
elementToLayoutAttributesTable:[NSMapTable weakToStrongObjectsMapTable]];
} }
// Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element // Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element

View File

@ -39,7 +39,6 @@
{ {
self = [super init]; self = [super init];
if (self) { if (self) {
ASDisplayNodeAssertTrue([layoutDelegateClass conformsToProtocol:@protocol(ASCollectionLayoutDelegate)]);
_viewportSize = viewportSize; _viewportSize = viewportSize;
_scrollableDirections = scrollableDirections; _scrollableDirections = scrollableDirections;
_elements = elements; _elements = elements;

View File

@ -32,6 +32,8 @@ NS_ASSUME_NONNULL_BEGIN
* It will be available in the context parameter in +calculateLayoutWithContext: * It will be available in the context parameter in +calculateLayoutWithContext:
* *
* @return The scrollable directions. * @return The scrollable directions.
*
* @discusstion This method will be called on main thread.
*/ */
- (ASScrollDirection)scrollableDirections; - (ASScrollDirection)scrollableDirections;

View File

@ -58,6 +58,13 @@ AS_SUBCLASSING_RESTRICTED
contentSize:(CGSize)contentSize contentSize:(CGSize)contentSize
elementToLayoutAttributesTable:(NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)table NS_DESIGNATED_INITIALIZER; elementToLayoutAttributesTable:(NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)table NS_DESIGNATED_INITIALIZER;
/**
* Convenience initializer. Returns an object with zero content size and an empty table.
*
* @param context The context used to calculate this object
*/
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context;
/** /**
* Convenience initializer. * Convenience initializer.
* *

View File

@ -41,6 +41,13 @@
ASPageToLayoutAttributesTable *_unmeasuredPageToLayoutAttributesTable; ASPageToLayoutAttributesTable *_unmeasuredPageToLayoutAttributesTable;
} }
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context
{
return [self initWithContext:context
contentSize:CGSizeZero
elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]];
}
- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - (instancetype)initWithContext:(ASCollectionLayoutContext *)context
layout:(ASLayout *)layout layout:(ASLayout *)layout
getElementBlock:(ASCollectionElement *(^)(ASLayout *))getElementBlock getElementBlock:(ASCollectionElement *(^)(ASLayout *))getElementBlock

View File

@ -109,13 +109,6 @@ extern NSString * const ASCollectionInvalidUpdateException;
*/ */
@protocol ASDataControllerDelegate <NSObject> @protocol ASDataControllerDelegate <NSObject>
/**
* Called before updating with given change set.
*
* @param changeSet The change set that includes all updates
*/
- (void)dataController:(ASDataController *)dataController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;
/** /**
* Called for change set updates. * Called for change set updates.
* *
@ -127,7 +120,7 @@ extern NSString * const ASCollectionInvalidUpdateException;
* It should be called at the time the backing view is ready to process the updates, * It should be called at the time the backing view is ready to process the updates,
* i.e inside the updates block of `-[UICollectionView performBatchUpdates:completion:] or after calling `-[UITableView beginUpdates]`. * i.e inside the updates block of `-[UICollectionView performBatchUpdates:completion:] or after calling `-[UITableView beginUpdates]`.
*/ */
- (void)dataController:(ASDataController *)dataController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates; - (void)dataController:(ASDataController *)dataController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates;
@end @end

View File

@ -46,10 +46,8 @@
//#define LOG(...) NSLog(__VA_ARGS__) //#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...) #define LOG(...)
#define RETURN_IF_NO_DATASOURCE(val) if (_dataSource == nil) { return val; }
#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) #define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd))
const static NSUInteger kASDataControllerSizingCountPerProcessor = 5;
const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey"; const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey";
const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext"; const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext";
@ -125,18 +123,6 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
return self; return self;
} }
+ (NSUInteger)parallelProcessorCount
{
static NSUInteger parallelProcessorCount;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
parallelProcessorCount = [[NSProcessInfo processInfo] activeProcessorCount];
});
return parallelProcessorCount;
}
- (id<ASDataControllerLayoutDelegate>)layoutDelegate - (id<ASDataControllerLayoutDelegate>)layoutDelegate
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -153,34 +139,47 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
#pragma mark - Cell Layout #pragma mark - Cell Layout
- (void)batchAllocateNodesFromElements:(NSArray<ASCollectionElement *> *)elements batchSize:(NSInteger)batchSize batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler - (void)_allocateNodesFromElements:(NSArray<ASCollectionElement *> *)elements completion:(ASDataControllerCompletionBlock)completionHandler
{ {
ASSERT_ON_EDITING_QUEUE; ASSERT_ON_EDITING_QUEUE;
if (elements.count == 0 || _dataSource == nil) { NSUInteger nodeCount = elements.count;
batchCompletionHandler(); __weak id<ASDataControllerSource> weakDataSource = _dataSource;
if (nodeCount == 0 || weakDataSource == nil) {
completionHandler();
return; return;
} }
ASSignpostStart(ASSignpostDataControllerBatch); ASSignpostStart(ASSignpostDataControllerBatch);
if (batchSize == 0) {
batchSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
}
NSUInteger count = elements.count;
// Processing in batches
for (NSUInteger i = 0; i < count; i += batchSize) {
NSRange batchedRange = NSMakeRange(i, MIN(count - i, batchSize));
NSArray<ASCollectionElement *> *batchedElements = [elements subarrayWithRange:batchedRange];
{ {
as_activity_create_for_scope("Data controller batch"); as_activity_create_for_scope("Data controller batch");
[self _allocateNodesFromElements:batchedElements];
} dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
batchCompletionHandler(); ASDispatchApply(nodeCount, queue, 0, ^(size_t i) {
__strong id<ASDataControllerSource> strongDataSource = weakDataSource;
if (strongDataSource == nil) {
return;
} }
ASSignpostEndCustom(ASSignpostDataControllerBatch, self, 0, (_dataSource != nil ? ASSignpostColorDefault : ASSignpostColorRed)); // Allocate the node.
ASCollectionElement *context = elements[i];
ASCellNode *node = context.node;
if (node == nil) {
ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, strongDataSource);
node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps.
}
// Layout the node if the size range is valid.
ASSizeRange sizeRange = context.constrainedSize;
if (ASSizeRangeHasSignificantArea(sizeRange)) {
[self _layoutNode:node withConstrainedSize:sizeRange];
}
});
}
completionHandler();
ASSignpostEndCustom(ASSignpostDataControllerBatch, self, 0, (weakDataSource != nil ? ASSignpostColorDefault : ASSignpostColorRed));
} }
/** /**
@ -195,36 +194,6 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
node.frame = frame; node.frame = frame;
} }
// TODO Is returned array still needed? Can it be removed?
- (void)_allocateNodesFromElements:(NSArray<ASCollectionElement *> *)elements
{
ASSERT_ON_EDITING_QUEUE;
NSUInteger nodeCount = elements.count;
if (!nodeCount || _dataSource == nil) {
return;
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
ASDispatchApply(nodeCount, queue, 0, ^(size_t i) {
RETURN_IF_NO_DATASOURCE();
// Allocate the node.
ASCollectionElement *context = elements[i];
ASCellNode *node = context.node;
if (node == nil) {
ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource);
node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps.
}
// Layout the node if the size range is valid.
ASSizeRange sizeRange = context.constrainedSize;
if (ASSizeRangeHasSignificantArea(sizeRange)) {
[self _layoutNode:node withConstrainedSize:sizeRange];
}
});
}
#pragma mark - Data Source Access (Calling _dataSource) #pragma mark - Data Source Access (Calling _dataSource)
- (NSArray<NSIndexPath *> *)_allIndexPathsForItemsOfKind:(NSString *)kind inSections:(NSIndexSet *)sections - (NSArray<NSIndexPath *> *)_allIndexPathsForItemsOfKind:(NSString *)kind inSections:(NSIndexSet *)sections
@ -435,21 +404,16 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
return @[]; return @[];
} }
- (ASSizeRange)constrainedSizeForElement:(ASCollectionElement *)element inElementMap:(ASElementMap *)map /**
{ * Returns constrained size for the node of the given kind and at the given index path.
ASDisplayNodeAssertMainThread(); * NOTE: index path must be in the data-source index space.
NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind; */
NSIndexPath *indexPath = [map indexPathForElement:element];
return [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
}
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath - (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
id<ASDataControllerSource> dataSource = _dataSource; id<ASDataControllerSource> dataSource = _dataSource;
if (dataSource == nil) { if (dataSource == nil || indexPath == nil) {
return ASSizeRangeZero; return ASSizeRangeZero;
} }
@ -574,11 +538,8 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
dispatch_block_t completion = ^() { dispatch_block_t completion = ^() {
[_mainSerialQueue performBlockOnMainThread:^{ [_mainSerialQueue performBlockOnMainThread:^{
as_activity_scope_leave(&preparationScope); as_activity_scope_leave(&preparationScope);
// TODO Merge the two delegate methods below
[_delegate dataController:self willUpdateWithChangeSet:changeSet];
// Step 4: Inform the delegate // Step 4: Inform the delegate
[_delegate dataController:self didUpdateWithChangeSet:changeSet updates:^{ [_delegate dataController:self updateWithChangeSet:changeSet updates:^{
// Step 5: Deploy the new data as "completed" // Step 5: Deploy the new data as "completed"
// //
// Note that since the backing collection view might be busy responding to user events (e.g scrolling), // Note that since the backing collection view might be busy responding to user events (e.g scrolling),
@ -599,7 +560,7 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
NSArray<ASCollectionElement *> *elementsToProcess = ASArrayByFlatMapping(newMap, NSArray<ASCollectionElement *> *elementsToProcess = ASArrayByFlatMapping(newMap,
ASCollectionElement *element, ASCollectionElement *element,
(element.nodeIfAllocated.calculatedLayout == nil ? element : nil)); (element.nodeIfAllocated.calculatedLayout == nil ? element : nil));
[self batchAllocateNodesFromElements:elementsToProcess batchSize:elementsToProcess.count batchCompletion:completion]; [self _allocateNodesFromElements:elementsToProcess completion:completion];
} }
}); });
@ -752,15 +713,18 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
auto pendingMap = self.pendingMap; auto pendingMap = self.pendingMap;
for (ASCellNode *node in nodes) { for (ASCellNode *node in nodes) {
auto element = node.collectionElement; auto element = node.collectionElement;
NSIndexPath *indexPathInPendingMap = [pendingMap indexPathForElement:element];
// Ensure the element is present in both maps or skip it. If it's not in the visible map, // Ensure the element is present in both maps or skip it. If it's not in the visible map,
// then we can't check the presented size. If it's not in the pending map, we can't get the constrained size. // then we can't check the presented size. If it's not in the pending map, we can't get the constrained size.
// This will only happen if the element has been deleted, so the specifics of this behavior aren't important. // This will only happen if the element has been deleted, so the specifics of this behavior aren't important.
if ([visibleMap indexPathForElement:element] == nil || [pendingMap indexPathForElement:element] == nil) { if (indexPathInPendingMap == nil || [visibleMap indexPathForElement:element] == nil) {
continue; continue;
} }
ASSizeRange constrainedSize = [self constrainedSizeForElement:element inElementMap:pendingMap]; NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind;
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPathInPendingMap];
[self _layoutNode:node withConstrainedSize:constrainedSize]; [self _layoutNode:node withConstrainedSize:constrainedSize];
BOOL matchesSize = [dataSource dataController:self presentedSizeForElement:element matchesSize:node.frame.size]; BOOL matchesSize = [dataSource dataController:self presentedSizeForElement:element matchesSize:node.frame.size];
if (! matchesSize) { if (! matchesSize) {
[nodesSizesChanged addObject:node]; [nodesSizesChanged addObject:node];
@ -787,15 +751,23 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
for (ASCollectionElement *element in _visibleMap) { for (ASCollectionElement *element in _visibleMap) {
ASSizeRange constrainedSize = [self constrainedSizeForElement:element inElementMap:_visibleMap]; // Ignore this element if it is no longer in the latest data. It is still recognized in the UIKit world but will be deleted soon.
if (ASSizeRangeHasSignificantArea(constrainedSize)) { NSIndexPath *indexPathInPendingMap = [_pendingMap indexPathForElement:element];
element.constrainedSize = constrainedSize; if (indexPathInPendingMap == nil) {
continue;
}
NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind;
ASSizeRange newConstrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPathInPendingMap];
if (ASSizeRangeHasSignificantArea(newConstrainedSize)) {
element.constrainedSize = newConstrainedSize;
// Node may not be allocated yet (e.g node virtualization or same size optimization) // Node may not be allocated yet (e.g node virtualization or same size optimization)
// Call context.nodeIfAllocated here to avoid immature node allocation and layout // Call context.nodeIfAllocated here to avoid immature node allocation and layout
ASCellNode *node = element.nodeIfAllocated; ASCellNode *node = element.nodeIfAllocated;
if (node) { if (node) {
[self _layoutNode:node withConstrainedSize:constrainedSize]; [self _layoutNode:node withConstrainedSize:newConstrainedSize];
} }
} }
} }

View File

@ -76,6 +76,9 @@
// handled by ASCollectionView node<->cell machinery // handled by ASCollectionView node<->cell machinery
selector == @selector(collectionView:cellForItemAtIndexPath:) || selector == @selector(collectionView:cellForItemAtIndexPath:) ||
selector == @selector(collectionView:layout:sizeForItemAtIndexPath:) || selector == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
selector == @selector(collectionView:layout:insetForSectionAtIndex:) ||
selector == @selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:) ||
selector == @selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:) ||
selector == @selector(collectionView:layout:referenceSizeForHeaderInSection:) || selector == @selector(collectionView:layout:referenceSizeForHeaderInSection:) ||
selector == @selector(collectionView:layout:referenceSizeForFooterInSection:) || selector == @selector(collectionView:layout:referenceSizeForFooterInSection:) ||
selector == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) || selector == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) ||

View File

@ -115,7 +115,7 @@
- (nullable NSIndexPath *)indexPathForElement:(ASCollectionElement *)element - (nullable NSIndexPath *)indexPathForElement:(ASCollectionElement *)element
{ {
return [_elementToIndexPathMap objectForKey:element]; return element ? [_elementToIndexPathMap objectForKey:element] : nil;
} }
- (nullable NSIndexPath *)indexPathForElementIfCell:(ASCollectionElement *)element - (nullable NSIndexPath *)indexPathForElementIfCell:(ASCollectionElement *)element

View File

@ -148,14 +148,7 @@ AS_SUBCLASSING_RESTRICTED
@protocol ASRangeControllerDelegate <NSObject> @protocol ASRangeControllerDelegate <NSObject>
/** /**
* Called before updating with given change set. * Called to update with given change set.
*
* @param changeSet The change set that includes all updates
*/
- (void)rangeController:(ASRangeController *)rangeController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;
/**
* Called after updating with given change set.
* *
* @param changeSet The change set that includes all updates * @param changeSet The change set that includes all updates
* *
@ -165,7 +158,7 @@ AS_SUBCLASSING_RESTRICTED
* It should be called at the time the backing view is ready to process the updates, * It should be called at the time the backing view is ready to process the updates,
* i.e inside the updates block of `-[UICollectionView performBatchUpdates:completion:] or after calling `-[UITableView beginUpdates]`. * i.e inside the updates block of `-[UICollectionView performBatchUpdates:completion:] or after calling `-[UITableView beginUpdates]`.
*/ */
- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates; - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates;
@end @end

View File

@ -494,20 +494,14 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
#pragma mark - ASDataControllerDelegete #pragma mark - ASDataControllerDelegete
- (void)dataController:(ASDataController *)dataController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet - (void)dataController:(ASDataController *)dataController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
if (changeSet.includesReloadData) { if (changeSet.includesReloadData) {
[self _setVisibleNodes:nil]; [self _setVisibleNodes:nil];
} }
[_delegate rangeController:self willUpdateWithChangeSet:changeSet];
}
- (void)dataController:(ASDataController *)dataController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
{
ASDisplayNodeAssertMainThread();
_rangeIsValid = NO; _rangeIsValid = NO;
[_delegate rangeController:self didUpdateWithChangeSet:changeSet updates:updates]; [_delegate rangeController:self updateWithChangeSet:changeSet updates:updates];
} }
#pragma mark - Memory Management #pragma mark - Memory Management

View File

@ -70,6 +70,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) ASStackLayoutFlexWrap flexWrap; @property (nonatomic, assign) ASStackLayoutFlexWrap flexWrap;
/** Orientation of lines along cross axis if there are multiple lines. Defaults to ASStackLayoutAlignContentStart */ /** Orientation of lines along cross axis if there are multiple lines. Defaults to ASStackLayoutAlignContentStart */
@property (nonatomic, assign) ASStackLayoutAlignContent alignContent; @property (nonatomic, assign) ASStackLayoutAlignContent alignContent;
/** If the stack spreads on multiple lines using flexWrap, the amount of space between lines. */
@property (nonatomic, assign) CGFloat lineSpacing;
/** Whether this stack can dispatch to other threads, regardless of which thread it's running on */ /** Whether this stack can dispatch to other threads, regardless of which thread it's running on */
@property (nonatomic, assign, getter=isConcurrent) BOOL concurrent; @property (nonatomic, assign, getter=isConcurrent) BOOL concurrent;
@ -105,6 +107,25 @@ NS_ASSUME_NONNULL_BEGIN
alignContent:(ASStackLayoutAlignContent)alignContent alignContent:(ASStackLayoutAlignContent)alignContent
children:(NSArray<id<ASLayoutElement>> *)children AS_WARN_UNUSED_RESULT; children:(NSArray<id<ASLayoutElement>> *)children AS_WARN_UNUSED_RESULT;
/**
@param direction The direction of the stack view (horizontal or vertical)
@param spacing The spacing between the children
@param justifyContent If no children are flexible, this describes how to fill any extra space
@param alignItems Orientation of the children along the cross axis
@param flexWrap Whether children are stacked into a single or multiple lines
@param alignContent Orientation of lines along cross axis if there are multiple lines
@param lineSpacing The spacing between lines
@param children ASLayoutElement children to be positioned.
*/
+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction
spacing:(CGFloat)spacing
justifyContent:(ASStackLayoutJustifyContent)justifyContent
alignItems:(ASStackLayoutAlignItems)alignItems
flexWrap:(ASStackLayoutFlexWrap)flexWrap
alignContent:(ASStackLayoutAlignContent)alignContent
lineSpacing:(CGFloat)lineSpacing
children:(NSArray<id<ASLayoutElement>> *)children AS_WARN_UNUSED_RESULT;
/** /**
* @return A stack layout spec with direction of ASStackLayoutDirectionVertical * @return A stack layout spec with direction of ASStackLayoutDirectionVertical
**/ **/

View File

@ -33,17 +33,22 @@
- (instancetype)init - (instancetype)init
{ {
return [self initWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart children:nil]; return [self initWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing:0.0 children:nil];
} }
+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children + (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children
{ {
return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart children:children]; return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing: 0.0 children:children];
} }
+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray<id<ASLayoutElement>> *)children + (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray<id<ASLayoutElement>> *)children
{ {
return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent children:children]; return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:0.0 children:children];
}
+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray<id<ASLayoutElement>> *)children
{
return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:lineSpacing children:children];
} }
+ (instancetype)verticalStackLayoutSpec + (instancetype)verticalStackLayoutSpec
@ -60,7 +65,7 @@
return stackLayoutSpec; return stackLayoutSpec;
} }
- (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray *)children - (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray *)children
{ {
if (!(self = [super init])) { if (!(self = [super init])) {
return nil; return nil;
@ -73,6 +78,7 @@
_justifyContent = justifyContent; _justifyContent = justifyContent;
_flexWrap = flexWrap; _flexWrap = flexWrap;
_alignContent = alignContent; _alignContent = alignContent;
_lineSpacing = lineSpacing;
[self setChildren:children]; [self setChildren:children];
return self; return self;
@ -144,7 +150,7 @@
return {child, style, style.size}; return {child, style, style.size};
}); });
const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .flexWrap = _flexWrap, .alignContent = _alignContent}; const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .flexWrap = _flexWrap, .alignContent = _alignContent, .lineSpacing = _lineSpacing};
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize, _concurrent); const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize, _concurrent);
const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize); const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize);

View File

@ -87,12 +87,9 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context + (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{ {
if (context.elements == nil) { if (context.elements == nil) {
return [[ASCollectionLayoutState alloc] initWithContext:context return [[ASCollectionLayoutState alloc] initWithContext:context];
contentSize:CGSizeZero
elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]];
} }
ASDisplayNodeAssertTrue([context.layoutDelegateClass conformsToProtocol:@protocol(ASCollectionLayoutDelegate)]);
ASCollectionLayoutState *layout = [context.layoutDelegateClass calculateLayoutWithContext:context]; ASCollectionLayoutState *layout = [context.layoutDelegateClass calculateLayoutWithContext:context];
[context.layoutCache setLayout:layout forContext:context]; [context.layoutCache setLayout:layout forContext:context];

View File

@ -24,6 +24,7 @@ typedef struct {
ASStackLayoutAlignItems alignItems; ASStackLayoutAlignItems alignItems;
ASStackLayoutFlexWrap flexWrap; ASStackLayoutFlexWrap flexWrap;
ASStackLayoutAlignContent alignContent; ASStackLayoutAlignContent alignContent;
CGFloat lineSpacing;
} ASStackLayoutSpecStyle; } ASStackLayoutSpecStyle;
inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size) inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size)

View File

@ -160,6 +160,7 @@ ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnposition
const auto numOfLines = lines.size(); const auto numOfLines = lines.size();
const auto direction = style.direction; const auto direction = style.direction;
const auto alignContent = style.alignContent; const auto alignContent = style.alignContent;
const auto lineSpacing = style.lineSpacing;
const auto justifyContent = style.justifyContent; const auto justifyContent = style.justifyContent;
const auto crossViolation = ASStackUnpositionedLayout::computeCrossViolation(layout.crossDimensionSum, style, sizeRange); const auto crossViolation = ASStackUnpositionedLayout::computeCrossViolation(layout.crossDimensionSum, style, sizeRange);
CGFloat crossOffset; CGFloat crossOffset;
@ -171,7 +172,7 @@ ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnposition
BOOL first = YES; BOOL first = YES;
for (const auto &line : lines) { for (const auto &line : lines) {
if (!first) { if (!first) {
p = p + directionPoint(direction, 0, crossSpacing); p = p + directionPoint(direction, 0, crossSpacing + lineSpacing);
} }
first = NO; first = NO;

View File

@ -113,9 +113,12 @@ static void dispatchApplyIfNeeded(size_t iterationCount, BOOL forced, void(^work
@param lines unpositioned lines @param lines unpositioned lines
*/ */
static CGFloat computeLinesCrossDimensionSum(const std::vector<ASStackUnpositionedLine> &lines) static CGFloat computeLinesCrossDimensionSum(const std::vector<ASStackUnpositionedLine> &lines,
const ASStackLayoutSpecStyle &style)
{ {
return std::accumulate(lines.begin(), lines.end(), 0.0, return std::accumulate(lines.begin(), lines.end(),
// Start from default spacing between each line:
lines.empty() ? 0 : style.lineSpacing * (lines.size() - 1),
[&](CGFloat x, const ASStackUnpositionedLine &l) { [&](CGFloat x, const ASStackUnpositionedLine &l) {
return x + l.crossSize; return x + l.crossSize;
}); });
@ -236,7 +239,7 @@ static void stretchLinesAlongCrossDimension(std::vector<ASStackUnpositionedLine>
{ {
ASDisplayNodeCAssertFalse(lines.empty()); ASDisplayNodeCAssertFalse(lines.empty());
const std::size_t numOfLines = lines.size(); const std::size_t numOfLines = lines.size();
const CGFloat violation = ASStackUnpositionedLayout::computeCrossViolation(computeLinesCrossDimensionSum(lines), style, sizeRange); const CGFloat violation = ASStackUnpositionedLayout::computeCrossViolation(computeLinesCrossDimensionSum(lines, style), style, sizeRange);
// Don't stretch if the stack is single line, because the line's cross size was clamped against the stack's constrained size. // Don't stretch if the stack is single line, because the line's cross size was clamped against the stack's constrained size.
const BOOL shouldStretchLines = (numOfLines > 1 const BOOL shouldStretchLines = (numOfLines > 1
&& style.alignContent == ASStackLayoutAlignContentStretch && style.alignContent == ASStackLayoutAlignContentStretch
@ -648,7 +651,8 @@ static std::vector<ASStackUnpositionedLine> collectChildrenIntoLines(const std::
for(auto it = items.begin(); it != items.end(); ++it) { for(auto it = items.begin(); it != items.end(); ++it) {
const auto &item = *it; const auto &item = *it;
const CGFloat itemStackDimension = stackDimension(style.direction, item.layout.size); const CGFloat itemStackDimension = stackDimension(style.direction, item.layout.size);
const BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + itemStackDimension, style, sizeRange) < 0); 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 BOOL breakCurrentLine = negativeViolationIfAddItem && !lineItems.empty(); const BOOL breakCurrentLine = negativeViolationIfAddItem && !lineItems.empty();
if (breakCurrentLine) { if (breakCurrentLine) {
@ -658,7 +662,7 @@ static std::vector<ASStackUnpositionedLine> collectChildrenIntoLines(const std::
} }
lineItems.push_back(std::move(item)); lineItems.push_back(std::move(item));
lineStackDimensionSum += itemStackDimension; lineStackDimensionSum += itemAndSpacingStackDimension;
} }
// Handle last line // Handle last line
@ -752,7 +756,7 @@ ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<A
} }
// Compute cross dimension sum of the stack. // Compute cross dimension sum of the stack.
// This should be done before `lines` are moved to a new ASStackUnpositionedLayout struct (i.e `std::move(lines)`) // This should be done before `lines` are moved to a new ASStackUnpositionedLayout struct (i.e `std::move(lines)`)
CGFloat layoutCrossDimensionSum = computeLinesCrossDimensionSum(lines); CGFloat layoutCrossDimensionSum = computeLinesCrossDimensionSum(lines, style);
return {.lines = std::move(lines), .stackDimensionSum = layoutStackDimensionSum, .crossDimensionSum = layoutCrossDimensionSum}; return {.lines = std::move(lines), .stackDimensionSum = layoutStackDimensionSum, .crossDimensionSum = layoutCrossDimensionSum};
} }

View File

@ -96,7 +96,7 @@ Texture's collection and table APIs have been moved from the view space (`collec
- Search your project for `tableView` and `collectionView`. Most, if not all, of the data source / delegate methods have new node versions. - Search your project for `tableView` and `collectionView`. Most, if not all, of the data source / delegate methods have new node versions.
It is important that developers using Texture understand that an ASCollectionNode is backed by an ASCollectionView (a subclass of UICollectionView). ASCollectionNode runs asynchronously, so calling number -numberOfRowsInSection on the collectionNode is different than calling it on the collectionView. It is important that developers using Texture understand that an ASCollectionNode is backed by an ASCollectionView (a subclass of UICollectionView). ASCollectionNode runs asynchronously, so calling -numberOfRowsInSection on the collectionNode is different than calling it on the collectionView.
For example, let's say you have an empty table. You insert `100` rows and then immediately call -tableView:numberOfRowsInSection. This will return `0` rows. If you call -waitUntilAllUpdatesAreCommitted after insertion (waits until the collectionNode synchronizes with the collectionView), you will get 100, _but_ you might block the main thread. A good developer should rarely (or never) need to use -waitUntilAllUpdatesAreCommitted. If you update the collectionNode and then need to read back immediately, you should use the collectionNode API. You shouldn't need to talk to the collectionView. For example, let's say you have an empty table. You insert `100` rows and then immediately call -tableView:numberOfRowsInSection. This will return `0` rows. If you call -waitUntilAllUpdatesAreCommitted after insertion (waits until the collectionNode synchronizes with the collectionView), you will get 100, _but_ you might block the main thread. A good developer should rarely (or never) need to use -waitUntilAllUpdatesAreCommitted. If you update the collectionNode and then need to read back immediately, you should use the collectionNode API. You shouldn't need to talk to the collectionView.
@ -122,7 +122,7 @@ Other updates include:
- Renamed `pagerNode:constrainedSizeForNodeAtIndexPath:` to `pagerNode:constrainedSizeForNodeAtIndex:` - Renamed `pagerNode:constrainedSizeForNodeAtIndexPath:` to `pagerNode:constrainedSizeForNodeAtIndex:`
- collection view update validation assertions are now enabled. If you see something like `"Invalid number of items in section 2. The number of items after the update (7) must be equal to the number of items before the update (4) plus or minus the number of items inserted or removed from the section (4 inserted, 0 removed)`, please check the data source logic. If you have any questions, reach out to us on GitHub. - collection view update validation assertions are now enabled. If you see something like `"Invalid number of items in section 2. The number of items after the update (7) must be equal to the number of items before the update (4) plus or minus the number of items inserted or removed from the section (4 inserted, 0 removed)"`, please check the data source logic. If you have any questions, reach out to us on GitHub.
Best Practices: Best Practices:

View File

@ -20,7 +20,7 @@ _backgroundImageNode.imageModificationBlock = ^(UIImage *image) {
tintColor:[UIColor colorWithWhite:0.5 alpha:0.3] tintColor:[UIColor colorWithWhite:0.5 alpha:0.3]
saturationDeltaFactor:1.8 saturationDeltaFactor:1.8
maskImage:nil]; maskImage:nil];
return newImage ? newImage : image; return newImage ?: image;
}; };
//some time later... //some time later...

View File

@ -76,6 +76,3 @@ scrollNode.layoutSpecBlock = { node, constrainedSize in
</pre> </pre>
</div> </div>
</div> </div>
As you can see, the `scrollNode`'s underlying view is a `ASScrollNode`.

View File

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

View File

@ -23,7 +23,7 @@
#define ASYNC_COLLECTION_LAYOUT 0 #define ASYNC_COLLECTION_LAYOUT 0
@interface ViewController () <ASCollectionDataSource, ASCollectionDelegateFlowLayout> @interface ViewController () <ASCollectionDataSource, ASCollectionDelegateFlowLayout, ASCollectionGalleryLayoutSizeProviding>
@property (nonatomic, strong) ASCollectionNode *collectionNode; @property (nonatomic, strong) ASCollectionNode *collectionNode;
@property (nonatomic, strong) NSArray *data; @property (nonatomic, strong) NSArray *data;
@ -47,8 +47,8 @@
[super viewDidLoad]; [super viewDidLoad];
#if ASYNC_COLLECTION_LAYOUT #if ASYNC_COLLECTION_LAYOUT
id<ASCollectionLayoutDelegate> layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionVerticalDirections ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionVerticalDirections];
itemSize:CGSizeMake(180, 90)]; layoutDelegate.sizeProvider = self;
self.collectionNode = [[ASCollectionNode alloc] initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil]; self.collectionNode = [[ASCollectionNode alloc] initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil];
#else #else
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
@ -108,6 +108,14 @@
[self.collectionNode reloadData]; [self.collectionNode reloadData];
} }
#pragma mark - ASCollectionGalleryLayoutSizeProviding
- (CGSize)sizeForElements:(ASElementMap *)elements
{
ASDisplayNodeAssertMainThread();
return CGSizeMake(180, 90);
}
#pragma mark - ASCollectionView Data Source #pragma mark - ASCollectionView Data Source
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath; - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath;

View File

@ -36,11 +36,13 @@
- (ASScrollDirection)scrollableDirections - (ASScrollDirection)scrollableDirections
{ {
ASDisplayNodeAssertMainThread();
return ASScrollDirectionVerticalDirections; return ASScrollDirectionVerticalDirections;
} }
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements - (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{ {
ASDisplayNodeAssertMainThread();
return _info; return _info;
} }