Introduce ASCollectionLayout and friends (#3130)

* Introduce ASCollectionViewLayout
- `ASCollectionViewLayout` is an async `UICollectionViewLayout` that encapsulates its layout calculation logic into a separate thread-safe object which can be used ahead of time and/or on multiple threads.
- `ASDataController` now can prepare for a new layout resulted from a change set before `ASCollectionView` even knows about it. By the time the change set it ready to be consumed by `ASCollectionView`, its new layout is also ready.
- New `ASCollectionViewLayoutCalculating` protocol that is simple and generic enough that many types of calculators can be built on top. `ASCollectionViewLayoutSpecCalculator` conforms to `ASCollectionViewLayoutCalculating` protocol and can be backed by any layout spec (e.g `ASStackLayoutSpec`, `PIMasonryLayoutSpec`, etc). We can even build a `ASCollectionViewLayoutYogaCalculator` that uses Yoga internally.
- A built-in `ASCollectionViewFlowLayoutCalculator` that is a subclass of `ASCollectionViewLayoutSpecCalculator` and uses a multi-threaded multi-line `ASStackLayoutSpec` internally. The result is a performant and thread-safe flow layout calculator.
- Finally, `ASCollectionViewLayout` can be subclassed to handle a specific type of calculator with optimizations implemented based on the knowledge of such calculator. For example, `ASCollectionViewFlowLayout` can have a highly optimized implementation of `-layoutAttributesForElementsInRect:`.

Protocolize layout calculator providing and consuming

Add flex wrap documentation

Add a `multithreaded` flag to ASStackLayoutSpec that forces it to dispatch even if it's off main
- Update ASCollectionViewFlowLayoutSpecCalculator to use that flag.

Minor change in ASCollectionViewLayout

Implement Mosaic layout calculator

Minor change

Fix project file

Rename and fix project file

Skip fetching constrained size only if a layout calculator is available

Update examples/ASCollectionView

Remove unnecessary change in ASTableView

Address comments

Rename collection view calculator protocols

Minor changes after rebasing with master

Add ASLegacyCollectionLayoutCalculator for backward compatibility

Remove ASCollectionLayoutSpecCalculator

Remove ASLegacyCollectionLayoutCalculator

Introduce ASCollectionLayout
- A wrapper object that contains content size and an element to rect table.
- Collection layout calculators to return this new object instead of an ASLayout.

Before adding a content cache

Finishing hooking up ASCollectionLayoutDataSource to ASCollectionNode

Stash

Finish ASCollectionLayout

Rough impl of ASCollectionFlowLayout

Revert changes in CustomCollectionView example

Move ASRectTable back to Private

* Rename ASCollectionContentAttributes to ASCollectionLayoutState

* Address other comments

* Introduce ASCollectionLayoutDelegate and make ASCollectionLayout private

* Address comments

* API tweaks:
- Replace `-layoutContextWithElementMap:` in ASCollectionLayoutDelegate with `-additionalInfoForLayoutWithElements:`. The returned object is then stored in ASCollectionLayoutContext for later lookups.
- ASCollectionLayoutContext has no public initializer.
- ASDataControllerLayoutDelegate no longer requires a context of type ASCollectionLayoutContext but simply an `id`. This helps decouple ASDataController and ASCollectionLayout.
- Rename `elementMap` to `elements`.
- Rename `visibleMap` to `visibleElements`.
- Other minor changes.

* Rename ASCGSizeHash to ASHashFromCGSize

* Make sure to call super in -[ASCollectionLayout prepareLayout]

* Update example/ASCollectionView to use ASCollectionFlowLayoutDelegate

* Remove unnecessary change
This commit is contained in:
Huy Nguyen
2017-04-12 11:13:44 +01:00
committed by GitHub
parent ae1e299f21
commit 7d365c7d07
32 changed files with 980 additions and 129 deletions

View File

@@ -11,10 +11,12 @@
//
#import <AsyncDisplayKit/ASCollectionNode.h>
#import <AsyncDisplayKit/ASCollectionNode+Beta.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASCollectionInternal.h>
#import <AsyncDisplayKit/ASCollectionLayout.h>
#import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@@ -33,6 +35,7 @@
@interface _ASCollectionPendingState : NSObject
@property (weak, nonatomic) id <ASCollectionDelegate> delegate;
@property (weak, nonatomic) id <ASCollectionDataSource> dataSource;
@property (strong, nonatomic) UICollectionViewLayout *collectionViewLayout;
@property (nonatomic, assign) ASLayoutRangeMode rangeMode;
@property (nonatomic, assign) BOOL allowsSelection; // default is YES
@property (nonatomic, assign) BOOL allowsMultipleSelection; // default is NO
@@ -133,13 +136,21 @@
return [self initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil];
}
- (instancetype)initWithLayoutDelegate:(id<ASCollectionLayoutDelegate>)layoutDelegate layoutFacilitator:(id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator
{
return [self initWithFrame:CGRectZero collectionViewLayout:[[ASCollectionLayout alloc] initWithLayoutDelegate:layoutDelegate] layoutFacilitator:layoutFacilitator];
}
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator
{
if (self = [super init]) {
// Must call the setter here to make sure pendingState is created and the layout is configured.
[self setCollectionViewLayout:layout];
__weak __typeof__(self) weakSelf = self;
[self setViewBlock:^{
__typeof__(self) strongSelf = weakSelf;
return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(strongSelf)];
return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(strongSelf)];
}];
}
return self;
@@ -162,10 +173,12 @@
view.inverted = pendingState.inverted;
view.allowsSelection = pendingState.allowsSelection;
view.allowsMultipleSelection = pendingState.allowsMultipleSelection;
if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) {
[view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
}
// Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block.
}
}
@@ -340,6 +353,42 @@
}
}
- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout
{
if ([self pendingState]) {
[self _configureCollectionViewLayout:layout];
_pendingState.collectionViewLayout = layout;
} else {
[self _configureCollectionViewLayout:layout];
self.view.collectionViewLayout = layout;
}
}
- (UICollectionViewLayout *)collectionViewLayout
{
if ([self pendingState]) {
return _pendingState.collectionViewLayout;
} else {
return self.view.collectionViewLayout;
}
}
- (ASElementMap *)visibleElements
{
ASDisplayNodeAssertMainThread();
// TODO Own the data controller when view is not yet loaded
return self.dataController.visibleMap;
}
- (id<ASCollectionLayoutDelegate>)layoutDelegate
{
UICollectionViewLayout *layout = self.collectionViewLayout;
if ([layout isKindOfClass:[ASCollectionLayout class]]) {
return ((ASCollectionLayout *)layout).layoutDelegate;
}
return nil;
}
#pragma mark - Range Tuning
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
@@ -662,4 +711,14 @@ ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock)
return result;
}
#pragma mark - Private methods
- (void)_configureCollectionViewLayout:(UICollectionViewLayout *)layout
{
if ([layout isKindOfClass:[ASCollectionLayout class]]) {
ASCollectionLayout *collectionLayout = (ASCollectionLayout *)layout;
collectionLayout.collectionNode = self;
}
}
@end