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 */; };
DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; };
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 */; };
E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */; };
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>"; };
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>"; };
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>"; };
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>"; };
@ -1101,6 +1103,7 @@
698371DA1E4379CD00437585 /* ASNodeController+Beta.m */,
25E327541C16819500A2170C /* ASPagerNode.h */,
25E327551C16819500A2170C /* ASPagerNode.m */,
E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */,
A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */,
A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */,
CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */,
@ -1708,6 +1711,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
E54E00721F1D3828000B30D7 /* ASPagerNode+Beta.h in Headers */,
E5B225281F1790D6001E1431 /* ASHashing.h in Headers */,
CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */,
693A1DCA1ECC944E00D0C9D2 /* IGListAdapter+AsyncDisplayKit.h in Headers */,

View File

@ -1,9 +1,16 @@
## master
* Add your own contributions to the next release on the line below this with your name.
- [ASStackLayoutSpec] Add lineSpacing property working with flex wrap. [Flo Vouin](https://github.com/flovouin)
- [ASStackLayoutSpec] Fix flex wrap overflow in some cases using item spacing. [Flo Vouin](https://github.com/flovouin)
- [ASNodeController] Add -nodeDidLayout callback. Allow switching retain behavior at runtime. [Scott Goodson](https://github.com/appleguy)
- [ASCollectionView] Add delegate bridging and index space translation for missing UICollectionViewLayout properties. [Scott Goodson](https://github.com/appleguy)
- [ASTextNode2] Add initial implementation for link handling. [Scott Goodson](https://github.com/appleguy) [#396](https://github.com/TextureGroup/Texture/pull/396)
- [ASTextNode2] Provide compile flag to globally enable new implementation of ASTextNode: ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE. [Scott Goodson](https://github.com/appleguy) [#396](https://github.com/TextureGroup/Texture/pull/410)
- Add ASCollectionGalleryLayoutDelegate - an async collection layout that makes same-size collections (e.g photo galleries, pagers, etc) fast and lightweight! [Huy Nguyen](https://github.com/nguyenhuy/) [#76](https://github.com/TextureGroup/Texture/pull/76)
- 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
- 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 in collection view that occurs if batch updates are performed while scrolling [Huy Nguyen](https://github.com/nguyenhuy) [#378](https://github.com/TextureGroup/Texture/issues/378)
- Some improvements in ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#407](https://github.com/TextureGroup/Texture/pull/407)
- Small refactors in ASDataController [Huy Nguyen](https://github.com/TextureGroup/Texture/pull/443) [#443](https://github.com/TextureGroup/Texture/pull/443)
##2.3.4
- [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.
*/
//TODO change this to be a generic "kind" or "elementKind" that exposes `nil` for row kind
@property (atomic, copy, readonly, nullable) NSString *supplementaryElementKind;
/*

View File

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

View File

@ -622,15 +622,17 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
return CGSizeZero;
}
NSString *supplementaryKind = element.supplementaryElementKind;
NSIndexPath *indexPath = [_dataController.visibleMap indexPathForElement:element];
ASSizeRange sizeRange;
if (supplementaryKind == nil) {
sizeRange = [self dataController:_dataController constrainedSizeForNodeAtIndexPath:indexPath];
ASCellNode *node = element.node;
BOOL useUIKitCell = node.shouldUseUIKitCell;
if (useUIKitCell) {
// In this case, we should use the exact value that was stashed earlier by calling sizeForItem:, referenceSizeFor*, etc.
// 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 {
sizeRange = [self dataController:_dataController constrainedSizeForSupplementaryNodeOfKind:supplementaryKind atIndexPath:indexPath];
return [node layoutThatFits:element.constrainedSize].size;
}
return [element.node layoutThatFits:sizeRange].size;
}
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
@ -949,63 +951,84 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
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();
ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath];
if (element == nil) {
ASDisplayNodeAssert(NO, @"Unexpected nil element for collectionView:layout:sizeForItemAtIndexPath: %@, %@, %@", self, collectionViewLayout, indexPath);
return CGSizeZero;
}
ASCellNode *cell = element.node;
if (cell.shouldUseUIKitCell) {
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];
ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath];
return e ? [self sizeForElement:e] : ASFlowLayoutDefault(layout, itemSize, CGSizeZero);
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForHeaderInSection:(NSInteger)section
- (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
referenceSizeForHeaderInSection:(NSInteger)section
{
ASDisplayNodeAssertMainThread();
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionHeader
atIndexPath:indexPath];
if (element == nil) {
return CGSizeZero;
}
if (element.node.shouldUseUIKitCell && _asyncDelegateFlags.interop) {
if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) {
return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForHeaderInSection:section];
}
}
return [self sizeForElement:element];
ASElementMap *map = _dataController.visibleMap;
ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionHeader
atIndexPath:ASIndexPathForSection(section)];
return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, headerReferenceSize, CGSizeZero);
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForFooterInSection:(NSInteger)section
- (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
referenceSizeForFooterInSection:(NSInteger)section
{
ASDisplayNodeAssertMainThread();
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionFooter
atIndexPath:indexPath];
if (element == nil) {
return CGSizeZero;
}
ASElementMap *map = _dataController.visibleMap;
ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionFooter
atIndexPath:ASIndexPathForSection(section)];
return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, footerReferenceSize, CGSizeZero);
}
if (element.node.shouldUseUIKitCell && _asyncDelegateFlags.interop) {
if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]) {
return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForFooterInSection:section];
}
// For the methods that call delegateIndexPathForSection:withSelector:, translate the section from
// visibleMap to pendingMap. If the section no longer exists, or the delegate doesn't implement
// 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
@ -1013,7 +1036,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
if ([_registeredSupplementaryKinds containsObject:kind] == NO) {
[self registerSupplementaryNodeOfKind:kind];
}
UICollectionReusableView *view = nil;
ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:kind atIndexPath:indexPath];
ASCellNode *node = element.node;
@ -1678,11 +1701,19 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
if (block == nil) {
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 = ^{
ASCellNode *cell = [[ASCellNode alloc] init];
cell.shouldUseUIKitCell = YES;
cell.style.preferredSize = CGSizeZero;
return cell;
ASCellNode *node = [[ASCellNode alloc] init];
node.shouldUseUIKitCell = YES;
node.style.preferredSize = preferredSize;
return node;
};
} 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);
@ -1773,9 +1804,31 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
nodeBlock = ^{ return node; };
} else {
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 = ^{
ASCellNode *node = [[ASCellNode alloc] init];
node.shouldUseUIKitCell = useUIKitCell;
node.style.preferredSize = preferredSize;
return node;
};
}
@ -1901,37 +1954,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
#pragma mark - ASRangeControllerDelegate
- (void)rangeController:(ASRangeController *)rangeController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
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?
return;
}
for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES];
}
for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES];
}
for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES];
}
for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES];
}
}
- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
- (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
@ -1939,7 +1962,24 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
[changeSet executeCompletionHandlerWithFinished:NO];
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
//TODO Do we need to notify _layoutFacilitator before reloadData?
for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES];
}
for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES];
}
for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES];
}
for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES];
}
ASPerformBlockWithoutAnimation(!changeSet.animated, ^{
as_activity_scope(as_activity_create("Commit collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT));
if (changeSet.includesReloadData) {

View File

@ -185,6 +185,11 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
- (ASSizeRange)constrainedSizeForCalculatedLayout
{
ASDN::MutexLocker l(__instanceLock__);
return [self _locked_constrainedSizeForCalculatedLayout];
}
- (ASSizeRange)_locked_constrainedSizeForCalculatedLayout
{
if (_pendingDisplayNodeLayout != nullptr) {
return _pendingDisplayNodeLayout->constrainedSize;
}
@ -302,10 +307,10 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
}
CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size);
// 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 (_pendingDisplayNodeLayout == nullptr) {
if (_pendingDisplayNodeLayout == nullptr || _pendingDisplayNodeLayout->version < _layoutVersion) {
if (_calculatedDisplayNodeLayout->version >= _layoutVersion
&& (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES
|| CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
@ -314,7 +319,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
}
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
[self cancelLayoutTransition];
@ -352,8 +357,10 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
ASLayout *layout = [self calculateLayoutThatFits:constrainedSize
restrictedToSize:self.style.size
relativeToParentSize:boundsSizeForLayout];
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) {
@ -373,6 +380,9 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
// particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout.
nextLayout->requestedLayoutFromAbove = YES;
[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
@ -473,6 +483,11 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
- (BOOL)_isLayoutTransitionInvalid
{
ASDN::MutexLocker l(__instanceLock__);
return [self _locked_isLayoutTransitionValid];
}
- (BOOL)_locked_isLayoutTransitionValid
{
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) {
ASLayoutElementContext *context = ASLayoutElementGetCurrentContext();
if (context == nil || _pendingTransitionID != context.transitionID) {
@ -505,9 +520,6 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
measurementCompletion:(void(^)())completion
{
ASDisplayNodeAssertMainThread();
[self setNeedsLayout];
[self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass]
animated:animated
shouldMeasureAsync:shouldMeasureAsync
@ -531,17 +543,28 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
return;
}
// 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.
if ([self _isLayoutTransitionInvalid]) {
return;
}
{
ASDN::MutexLocker l(__instanceLock__);
ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one.");
// 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.
if ([self _locked_isLayoutTransitionValid]) {
return;
}
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) {
ASDisplayNodeAssert(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
int32_t transitionID = [self _startNewTransition];
as_log_verbose(ASLayoutLog(), "Transition ID is %d", transitionID);
@ -956,7 +979,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
_unflattenedLayout = displayNodeLayout->layout;
displayNodeLayout->layout = [_unflattenedLayout filteredNodeLayoutTree];
}
_calculatedDisplayNodeLayout = displayNodeLayout;
}

View File

@ -96,6 +96,13 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (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
@interface ASDisplayNode (Subclassing) <ASInterfaceStateDelegate>

View File

@ -589,6 +589,11 @@ extern NSInteger const ASDefaultDrawingPriority;
*/
- (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
/**

View File

@ -918,11 +918,13 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
if (_transitionID != ASLayoutElementContextInvalidTransitionID) {
return;
}
as_activity_create_for_scope("-[ASDisplayNode __layout]");
// 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).
[self _locked_measureNodeWithBoundsIfNecessary:bounds];
[self _locked_layoutPlaceholderIfNecessary];
}
@ -1124,6 +1126,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
ASDisplayNodeAssertTrue(self.isNodeLoaded);
[_interfaceStateDelegate nodeDidLayout];
}
#pragma mark Layout Transition
@ -3380,6 +3383,38 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
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
#pragma mark - ASDisplayNode UIKit / CA Categories

View File

@ -672,7 +672,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
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
ASDN::MutexLocker l(strongSelf->__instanceLock__);

View File

@ -18,18 +18,15 @@
#import <AsyncDisplayKit/ASDisplayNode.h>
#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 */
@interface ASNodeController<__covariant DisplayNodeType : ASDisplayNode *> : NSObject <ASInterfaceStateDelegate>
#if INVERT_NODE_CONTROLLER_OWNERSHIP
@property (nonatomic, weak) DisplayNodeType node;
#else
@property (nonatomic, strong) DisplayNodeType node;
#endif
@property (nonatomic, strong /* may be weak! */) DisplayNodeType node;
// 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)
@property (nonatomic, assign) BOOL shouldInvertStrongReference;
- (void)loadNode;
@ -47,3 +44,9 @@
fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER;
@end
@interface ASDisplayNode (ASNodeController)
@property(nonatomic, readonly) ASNodeController *nodeController;
@end

View File

@ -15,34 +15,28 @@
// http://www.apache.org/licenses/LICENSE-2.0
//
#import "ASNodeController+Beta.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import <AsyncDisplayKit/ASWeakProxy.h>
#import <AsyncDisplayKit/ASNodeController+Beta.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#if INVERT_NODE_CONTROLLER_OWNERSHIP
#define _node (_shouldInvertStrongReference ? _weakNode : _strongNode)
@interface ASDisplayNode (ASNodeController)
@property (nonatomic, strong) ASNodeController *asdkNodeController;
@end
@interface ASDisplayNode (ASNodeControllerOwnership)
@implementation ASDisplayNode (ASNodeController)
// This property exists for debugging purposes. Don't use __nodeController in production code.
@property (nonatomic, readonly) ASNodeController *__nodeController;
- (ASNodeController *)asdkNodeController
{
return objc_getAssociatedObject(self, @selector(asdkNodeController));
}
- (void)setAsdkNodeController:(ASNodeController *)asdkNodeController
{
objc_setAssociatedObject(self, @selector(asdkNodeController), asdkNodeController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// These setters are mutually exclusive. Setting one will clear the relationship of the other.
- (void)__setNodeControllerStrong:(ASNodeController *)nodeController;
- (void)__setNodeControllerWeak:(ASNodeController *)nodeController;
@end
#endif
@implementation ASNodeController
@synthesize node = _node;
{
ASDisplayNode *_strongNode;
__weak ASDisplayNode *_weakNode;
}
- (instancetype)init
{
@ -66,16 +60,41 @@
return _node;
}
-(void)setNode:(ASDisplayNode *)node
- (void)setupReferencesWithNode:(ASDisplayNode *)node
{
_node = node;
_node.interfaceStateDelegate = self;
#if INVERT_NODE_CONTROLLER_OWNERSHIP
_node.asdkNodeController = self;
#endif
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
{
[self setupReferencesWithNode:node];
}
- (void)setShouldInvertStrongReference:(BOOL)shouldInvertStrongReference
{
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
- (void)nodeDidLayout {}
- (void)didEnterVisibleState {}
- (void)didExitVisibleState {}
@ -89,3 +108,41 @@
fromState:(ASInterfaceState)oldState {}
@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
#import <AsyncDisplayKit/ASPagerNode.h>
#import <AsyncDisplayKit/ASPagerNode+Beta.h>
#import <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>
#import <AsyncDisplayKit/ASCollectionNode+Beta.h>
#import <AsyncDisplayKit/ASDelegateProxy.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@ -26,10 +30,8 @@
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.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;
ASPagerNodeProxy *_proxyDataSource;
struct {
@ -66,8 +68,15 @@
{
ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout.");
self = [super initWithCollectionViewLayout:flowLayout];
if (self != nil) {
_flowLayout = flowLayout;
return self;
}
- (instancetype)initUsingAsyncCollectionLayout
{
ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionHorizontalDirections];
self = [super initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil];
if (self) {
layoutDelegate.sizeProvider = self;
}
return self;
}
@ -129,6 +138,14 @@
return indexPath.row;
}
#pragma mark - ASCollectionGalleryLayoutSizeProviding
- (CGSize)sizeForElements:(ASElementMap *)elements
{
ASDisplayNodeAssertMainThread();
return self.bounds.size;
}
#pragma mark - ASCollectionDataSource
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath

View File

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

View File

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

View File

@ -28,7 +28,6 @@ NS_ASSUME_NONNULL_BEGIN
AS_SUBCLASSING_RESTRICTED
@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, assign) ASSizeRange constrainedSize;
@property (nonatomic, readonly, weak) id<ASRangeManagingNode> owningNode;

View File

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

View File

@ -15,8 +15,25 @@
#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
#import <AsyncDisplayKit/ASScrollDirection.h>
@class ASElementMap;
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.
*
@ -25,7 +42,9 @@ NS_ASSUME_NONNULL_BEGIN
AS_SUBCLASSING_RESTRICTED
@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;

View File

@ -33,40 +33,47 @@
CGSize _itemSize;
}
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections itemSize:(CGSize)itemSize
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections
{
self = [super init];
if (self) {
ASDisplayNodeAssertFalse(CGSizeEqualToSize(CGSizeZero, itemSize));
_scrollableDirections = scrollableDirections;
_itemSize = itemSize;
}
return self;
}
- (ASScrollDirection)scrollableDirections
{
ASDisplayNodeAssertMainThread();
return _scrollableDirections;
}
- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{
return [NSValue valueWithCGSize:_itemSize];
ASDisplayNodeAssertMainThread();
if (_sizeProvider == nil) {
return nil;
}
return [NSValue valueWithCGSize:[_sizeProvider sizeForElements:elements]];
}
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
CGSize pageSize = context.viewportSize;
CGSize itemSize = ((NSValue *)context.additionalInfo).CGSizeValue;
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,
ASCollectionElement *element,
[[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]);
ASCollectionElement *element,
[[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]);
if (children.count == 0) {
return [[ASCollectionLayoutState alloc] initWithContext:context
contentSize:CGSizeZero
elementToLayoutAttributesTable:[NSMapTable weakToStrongObjectsMapTable]];
return [[ASCollectionLayoutState alloc] initWithContext:context];
}
// 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];
if (self) {
ASDisplayNodeAssertTrue([layoutDelegateClass conformsToProtocol:@protocol(ASCollectionLayoutDelegate)]);
_viewportSize = viewportSize;
_scrollableDirections = scrollableDirections;
_elements = elements;

View File

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

View File

@ -58,6 +58,13 @@ AS_SUBCLASSING_RESTRICTED
contentSize:(CGSize)contentSize
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.
*

View File

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

View File

@ -109,13 +109,6 @@ extern NSString * const ASCollectionInvalidUpdateException;
*/
@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.
*
@ -127,7 +120,7 @@ extern NSString * const ASCollectionInvalidUpdateException;
* 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]`.
*/
- (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

View File

@ -46,10 +46,8 @@
//#define LOG(...) NSLog(__VA_ARGS__)
#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))
const static NSUInteger kASDataControllerSizingCountPerProcessor = 5;
const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey";
const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext";
@ -125,18 +123,6 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
return self;
}
+ (NSUInteger)parallelProcessorCount
{
static NSUInteger parallelProcessorCount;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
parallelProcessorCount = [[NSProcessInfo processInfo] activeProcessorCount];
});
return parallelProcessorCount;
}
- (id<ASDataControllerLayoutDelegate>)layoutDelegate
{
ASDisplayNodeAssertMainThread();
@ -153,34 +139,47 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
#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;
if (elements.count == 0 || _dataSource == nil) {
batchCompletionHandler();
NSUInteger nodeCount = elements.count;
__weak id<ASDataControllerSource> weakDataSource = _dataSource;
if (nodeCount == 0 || weakDataSource == nil) {
completionHandler();
return;
}
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");
[self _allocateNodesFromElements:batchedElements];
}
batchCompletionHandler();
{
as_activity_create_for_scope("Data controller batch");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
ASDispatchApply(nodeCount, queue, 0, ^(size_t i) {
__strong id<ASDataControllerSource> strongDataSource = weakDataSource;
if (strongDataSource == nil) {
return;
}
// 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];
}
});
}
ASSignpostEndCustom(ASSignpostDataControllerBatch, self, 0, (_dataSource != nil ? ASSignpostColorDefault : ASSignpostColorRed));
completionHandler();
ASSignpostEndCustom(ASSignpostDataControllerBatch, self, 0, (weakDataSource != nil ? ASSignpostColorDefault : ASSignpostColorRed));
}
/**
@ -195,36 +194,6 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
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)
- (NSArray<NSIndexPath *> *)_allIndexPathsForItemsOfKind:(NSString *)kind inSections:(NSIndexSet *)sections
@ -435,21 +404,16 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
return @[];
}
- (ASSizeRange)constrainedSizeForElement:(ASCollectionElement *)element inElementMap:(ASElementMap *)map
{
ASDisplayNodeAssertMainThread();
NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind;
NSIndexPath *indexPath = [map indexPathForElement:element];
return [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
}
/**
* Returns constrained size for the node of the given kind and at the given index path.
* NOTE: index path must be in the data-source index space.
*/
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssertMainThread();
id<ASDataControllerSource> dataSource = _dataSource;
if (dataSource == nil) {
if (dataSource == nil || indexPath == nil) {
return ASSizeRangeZero;
}
@ -574,11 +538,8 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
dispatch_block_t completion = ^() {
[_mainSerialQueue performBlockOnMainThread:^{
as_activity_scope_leave(&preparationScope);
// TODO Merge the two delegate methods below
[_delegate dataController:self willUpdateWithChangeSet:changeSet];
// 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"
//
// 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,
ASCollectionElement *element,
(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;
for (ASCellNode *node in nodes) {
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,
// 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.
if ([visibleMap indexPathForElement:element] == nil || [pendingMap indexPathForElement:element] == nil) {
if (indexPathInPendingMap == nil || [visibleMap indexPathForElement:element] == nil) {
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];
BOOL matchesSize = [dataSource dataController:self presentedSizeForElement:element matchesSize:node.frame.size];
if (! matchesSize) {
[nodesSizesChanged addObject:node];
@ -787,15 +751,23 @@ typedef dispatch_block_t ASDataControllerCompletionBlock;
{
ASDisplayNodeAssertMainThread();
for (ASCollectionElement *element in _visibleMap) {
ASSizeRange constrainedSize = [self constrainedSizeForElement:element inElementMap:_visibleMap];
if (ASSizeRangeHasSignificantArea(constrainedSize)) {
element.constrainedSize = constrainedSize;
// 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.
NSIndexPath *indexPathInPendingMap = [_pendingMap indexPathForElement:element];
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)
// Call context.nodeIfAllocated here to avoid immature node allocation and layout
ASCellNode *node = element.nodeIfAllocated;
if (node) {
[self _layoutNode:node withConstrainedSize:constrainedSize];
[self _layoutNode:node withConstrainedSize:newConstrainedSize];
}
}
}

View File

@ -76,6 +76,9 @@
// handled by ASCollectionView node<->cell machinery
selector == @selector(collectionView:cellForItemAtIndexPath:) ||
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:referenceSizeForFooterInSection:) ||
selector == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) ||

View File

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

View File

@ -148,14 +148,7 @@ AS_SUBCLASSING_RESTRICTED
@protocol ASRangeControllerDelegate <NSObject>
/**
* Called before updating 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.
* Called to update with given change set.
*
* @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,
* 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

View File

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

View File

@ -70,6 +70,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) ASStackLayoutFlexWrap flexWrap;
/** Orientation of lines along cross axis if there are multiple lines. Defaults to ASStackLayoutAlignContentStart */
@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 */
@property (nonatomic, assign, getter=isConcurrent) BOOL concurrent;
@ -105,6 +107,25 @@ NS_ASSUME_NONNULL_BEGIN
alignContent:(ASStackLayoutAlignContent)alignContent
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
**/

View File

@ -33,17 +33,22 @@
- (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
{
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
{
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
@ -60,7 +65,7 @@
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])) {
return nil;
@ -73,6 +78,7 @@
_justifyContent = justifyContent;
_flexWrap = flexWrap;
_alignContent = alignContent;
_lineSpacing = lineSpacing;
[self setChildren:children];
return self;
@ -144,7 +150,7 @@
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 positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize);

View File

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

View File

@ -24,6 +24,7 @@ typedef struct {
ASStackLayoutAlignItems alignItems;
ASStackLayoutFlexWrap flexWrap;
ASStackLayoutAlignContent alignContent;
CGFloat lineSpacing;
} ASStackLayoutSpecStyle;
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 direction = style.direction;
const auto alignContent = style.alignContent;
const auto lineSpacing = style.lineSpacing;
const auto justifyContent = style.justifyContent;
const auto crossViolation = ASStackUnpositionedLayout::computeCrossViolation(layout.crossDimensionSum, style, sizeRange);
CGFloat crossOffset;
@ -171,7 +172,7 @@ ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnposition
BOOL first = YES;
for (const auto &line : lines) {
if (!first) {
p = p + directionPoint(direction, 0, crossSpacing);
p = p + directionPoint(direction, 0, crossSpacing + lineSpacing);
}
first = NO;

View File

@ -113,9 +113,12 @@ static void dispatchApplyIfNeeded(size_t iterationCount, BOOL forced, void(^work
@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) {
return x + l.crossSize;
});
@ -236,7 +239,7 @@ static void stretchLinesAlongCrossDimension(std::vector<ASStackUnpositionedLine>
{
ASDisplayNodeCAssertFalse(lines.empty());
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.
const BOOL shouldStretchLines = (numOfLines > 1
&& style.alignContent == ASStackLayoutAlignContentStretch
@ -648,7 +651,8 @@ static std::vector<ASStackUnpositionedLine> collectChildrenIntoLines(const std::
for(auto it = items.begin(); it != items.end(); ++it) {
const auto &item = *it;
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();
if (breakCurrentLine) {
@ -658,7 +662,7 @@ static std::vector<ASStackUnpositionedLine> collectChildrenIntoLines(const std::
}
lineItems.push_back(std::move(item));
lineStackDimensionSum += itemStackDimension;
lineStackDimensionSum += itemAndSpacingStackDimension;
}
// Handle last line
@ -752,7 +756,7 @@ ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<A
}
// 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)`)
CGFloat layoutCrossDimensionSum = computeLinesCrossDimensionSum(lines);
CGFloat layoutCrossDimensionSum = computeLinesCrossDimensionSum(lines, style);
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.
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.
@ -122,7 +122,7 @@ Other updates include:
- 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:

View File

@ -17,10 +17,10 @@ By assigning an `imageModificationBlock` to your imageNode, you can define a set
<pre lang="objc" class="objcCode">
_backgroundImageNode.imageModificationBlock = ^(UIImage *image) {
UIImage *newImage = [image applyBlurWithRadius:30
tintColor:[UIColor colorWithWhite:0.5 alpha:0.3]
saturationDeltaFactor:1.8
maskImage:nil];
return newImage ? newImage : image;
tintColor:[UIColor colorWithWhite:0.5 alpha:0.3]
saturationDeltaFactor:1.8
maskImage:nil];
return newImage ?: image;
};
//some time later...

View File

@ -76,6 +76,3 @@ scrollNode.layoutSpecBlock = { node, constrainedSize in
</pre>
</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`
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:`

View File

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

View File

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