Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

Commit

Permalink
Introduce ASCollectionViewLayout
Browse files Browse the repository at this point in the history
- `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
  • Loading branch information
nguyenhuy committed Mar 24, 2017
1 parent 36de258 commit 28eb7e4
Show file tree
Hide file tree
Showing 30 changed files with 959 additions and 120 deletions.
104 changes: 82 additions & 22 deletions AsyncDisplayKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions Source/ASCollectionNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, assign) BOOL allowsMultipleSelection;

/**
* The layout used to organize the node's items.
*
* @discussion Assigning a new layout object to this property causes the new layout to be applied (without animations) to the node’s items.
*/
@property (nonatomic, strong) UICollectionViewLayout *collectionViewLayout;

/**
* Tuning parameters for a range type in full mode.
Expand Down
52 changes: 50 additions & 2 deletions Source/ASCollectionNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
//

#import <AsyncDisplayKit/ASCollectionNode.h>
#import <AsyncDisplayKit/ASCollectionNode+FrameworkPrivate.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>
Expand All @@ -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
Expand Down Expand Up @@ -97,7 +100,7 @@ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMo

#pragma mark - ASCollectionNode

@interface ASCollectionNode ()
@interface ASCollectionNode () <ASCollectionLayoutDataSource>
{
ASDN::RecursiveMutex _environmentStateLock;
Class _collectionViewClass;
Expand Down Expand Up @@ -151,6 +154,7 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionVi
};

if (self = [super initWithViewBlock:collectionViewBlock didLoadBlock:nil]) {
[self configureNewCollectionViewLayout:layout];
return self;
}
return nil;
Expand All @@ -173,10 +177,14 @@ - (void)didLoad
view.inverted = pendingState.inverted;
view.allowsSelection = pendingState.allowsSelection;
view.allowsMultipleSelection = pendingState.allowsMultipleSelection;

if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) {
[view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
}

if (pendingState.collectionViewLayout != nil) {
view.collectionViewLayout = pendingState.collectionViewLayout;
}
}
}

Expand Down Expand Up @@ -351,6 +359,26 @@ - (BOOL)allowsMultipleSelection
}
}

- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout
{
if ([self pendingState]) {
_pendingState.collectionViewLayout = layout;
[self configureNewCollectionViewLayout:layout];
} else {
self.view.collectionViewLayout = layout;
// Don't call -configureNewCollectionViewLayout: as the view will call on us.
}
}

- (UICollectionViewLayout *)collectionViewLayout
{
if ([self pendingState]) {
return _pendingState.collectionViewLayout;
} else {
return self.view.collectionViewLayout;
}
}

#pragma mark - Range Tuning

- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
Expand Down Expand Up @@ -673,4 +701,24 @@ - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode;
return result;
}

#pragma mark - ASCollectionLayoutDataSource

- (ASElementMap *)elementMapForCollectionLayout:(ASCollectionLayout *)collectionLayout
{
ASDisplayNodeAssertMainThread();
// TODO Own the data controller when view is not yet loaded
return self.dataController.visibleMap;
}

#pragma mark - Framework private methods

- (void)configureNewCollectionViewLayout:(UICollectionViewLayout *)layout
{
if ([layout isKindOfClass:[ASCollectionLayout class]]) {
ASCollectionLayout *collectionLayout = (ASCollectionLayout *)layout;
collectionLayout.dataSource = self;
collectionLayout.collectionNode = self;
}
}

@end
42 changes: 33 additions & 9 deletions Source/ASCollectionView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,28 @@
#import <AsyncDisplayKit/ASCellNode+Internal.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASCollectionInternal.h>
#import <AsyncDisplayKit/ASCollectionLayout.h>
#import <AsyncDisplayKit/ASCollectionViewLayoutController.h>
#import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h>
#import <AsyncDisplayKit/ASCollectionViewFlowLayoutInspector.h>
#import <AsyncDisplayKit/ASDataController.h>
#import <AsyncDisplayKit/ASDataController+Beta.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
#import <AsyncDisplayKit/ASRangeController.h>
#import <AsyncDisplayKit/ASCollectionNode.h>
#import <AsyncDisplayKit/ASCollectionNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/_ASCollectionViewCell.h>
#import <AsyncDisplayKit/_ASDisplayLayer.h>
#import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h>
#import <AsyncDisplayKit/ASPagerNode.h>
#import <AsyncDisplayKit/ASSectionContext.h>
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
#import <AsyncDisplayKit/_ASHierarchyChangeSet.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASThread.h>

/**
* A macro to get self.collectionNode and assign it to a local variable, or return
Expand Down Expand Up @@ -213,6 +216,8 @@ @interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDe
unsigned int didChangeCollectionViewDataSource:1;
unsigned int didChangeCollectionViewDelegate:1;
} _layoutInspectorFlags;

BOOL _hasCollectionLayoutDelegate;
}

@end
Expand Down Expand Up @@ -292,6 +297,8 @@ - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionV
_retainedLayer = self.layer;
}

[self _configureNewCollectionViewLayout:layout];

return self;
}

Expand Down Expand Up @@ -532,9 +539,12 @@ - (void)setAsyncDelegate:(id<ASCollectionDelegate>)asyncDelegate
}
}

- (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout
- (void)setCollectionViewLayout:(nonnull UICollectionViewLayout *)collectionViewLayout
{
ASDisplayNodeAssertMainThread();

[super setCollectionViewLayout:collectionViewLayout];
[self _configureNewCollectionViewLayout:collectionViewLayout];

// Trigger recreation of layout inspector with new collection view layout
if (_layoutInspector != nil) {
Expand Down Expand Up @@ -746,6 +756,16 @@ - (NSArray *)visibleNodes

#pragma mark Internal

- (void)_configureNewCollectionViewLayout:(nonnull UICollectionViewLayout *)layout
{
_hasCollectionLayoutDelegate = [layout conformsToProtocol:@protocol(ASDataControllerLayoutDelegate)];
if (_hasCollectionLayoutDelegate) {
_dataController.layoutDelegate = (id<ASDataControllerLayoutDelegate>)layout;
}
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
[collectionNode configureNewCollectionViewLayout:layout];
}

/**
Performing nested batch updates with super (e.g. resizing a cell node & updating collection view during same frame)
can cause super to throw data integrity exceptions because it checks the data source counts before
Expand Down Expand Up @@ -1503,7 +1523,6 @@ - (void)_beginBatchFetching
}
}


#pragma mark - ASDataControllerSource

- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath
Expand Down Expand Up @@ -1566,11 +1585,6 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt
return block;
}

- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
}

- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
{
if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) {
Expand Down Expand Up @@ -1665,6 +1679,11 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementa
}
}

- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
}

- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
if (_layoutInspectorFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath) {
Expand Down Expand Up @@ -1880,6 +1899,7 @@ - (void)rangeController:(ASRangeController *)rangeController didUpdateWithChange
}

#pragma mark - ASCellNodeDelegate

- (void)nodeSelectedStateDidChange:(ASCellNode *)node
{
NSIndexPath *indexPath = [self indexPathForNode:node];
Expand Down Expand Up @@ -2018,6 +2038,10 @@ - (void)didMoveToWindow
*/
- (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds newValue:(CGRect)newBounds
{
if (_hasCollectionLayoutDelegate) {
// Let the collection layout delegate handle bounds changes if it's available.
return;
}
if (self.collectionViewLayout == nil) {
return;
}
Expand Down
13 changes: 0 additions & 13 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,6 @@
#import <AsyncDisplayKit/ASWeakProxy.h>
#import <AsyncDisplayKit/ASResponderChainEnumerator.h>

/**
* Assert if the current thread owns a mutex.
* This assertion is useful when you want to indicate and enforce the locking policy/expectation of methods.
* To determine when and which methods acquired a (recursive) mutex (to debug deadlocks, for example),
* put breakpoints at some of these assertions. When the breakpoints hit, walk through stack trace frames
* and check ownership count of the mutex.
*/
#if CHECK_LOCKING_SAFETY
#define ASDisplayNodeAssertLockUnownedByCurrentThread(lock) ASDisplayNodeAssertFalse(lock.ownedByCurrentThread())
#else
#define ASDisplayNodeAssertLockUnownedByCurrentThread(lock)
#endif

#if ASDisplayNodeLoggingEnabled
#define LOG(...) NSLog(__VA_ARGS__)
#else
Expand Down
1 change: 1 addition & 0 deletions Source/ASPagerFlowLayout.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ @interface ASPagerFlowLayout () {

@end

//TODO make this an ASCollectionViewLayout
@implementation ASPagerFlowLayout

- (ASCollectionView *)asCollectionView
Expand Down
4 changes: 2 additions & 2 deletions Source/ASTableView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ - (void)configureWithDataControllerClass:(Class)dataControllerClass eventLog:(AS

_proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
super.dataSource = (id<UITableViewDataSource>)_proxyDataSource;

[self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier];
}

Expand Down Expand Up @@ -1547,7 +1547,7 @@ - (void)rangeController:(ASRangeController *)rangeController didUpdateWithChange
[changeSet executeCompletionHandlerWithFinished:YES];
}

#pragma mark - ASDataControllerDelegate
#pragma mark - ASDataControllerSource

- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath {
ASCellNodeBlock block = nil;
Expand Down
3 changes: 3 additions & 0 deletions Source/AsyncDisplayKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASSectionContext.h>

#import <AsyncDisplayKit/ASCollectionLayout.h>
#import <AsyncDisplayKit/ASCollectionFlowLayout.h>

#import <AsyncDisplayKit/ASSectionController.h>
#import <AsyncDisplayKit/ASSupplementaryNodeSource.h>
#if IG_LIST_KIT
Expand Down
13 changes: 13 additions & 0 deletions Source/Base/ASAssert.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@
#define ASDisplayNodeCAssertPositiveReal(description, num) ASDisplayNodeCAssert(num >= 0 && num <= CGFLOAT_MAX, @"%@ must be a real positive integer.", description)
#define ASDisplayNodeCAssertInfOrPositiveReal(description, num) ASDisplayNodeCAssert(isinf(num) || (num >= 0 && num <= CGFLOAT_MAX), @"%@ must be infinite or a real positive integer.", description)

/**
* Assert if the current thread owns a mutex.
* This assertion is useful when you want to indicate and enforce the locking policy/expectation of methods.
* To determine when and which methods acquired a (recursive) mutex (to debug deadlocks, for example),
* put breakpoints at some of these assertions. When the breakpoints hit, walk through stack trace frames
* and check ownership count of the mutex.
*/
#if CHECK_LOCKING_SAFETY
#define ASDisplayNodeAssertLockUnownedByCurrentThread(lock) ASDisplayNodeAssertFalse(lock.ownedByCurrentThread())
#else
#define ASDisplayNodeAssertLockUnownedByCurrentThread(lock)
#endif

#define ASDisplayNodeErrorDomain @"ASDisplayNodeErrorDomain"
#define ASDisplayNodeNonFatalErrorCode 1

Expand Down
42 changes: 42 additions & 0 deletions Source/Details/ASCollectionContentAttributes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// ASCollectionContentAttributes.h
// AsyncDisplayKit
//
// Created by Huy Nguyen on 9/3/17.
// Copyright © 2017 Facebook. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>

@class ASElementMap, ASCollectionElement;

NS_ASSUME_NONNULL_BEGIN

AS_SUBCLASSING_RESTRICTED
@interface ASCollectionContentAttributes : NSObject

/// The element map used to calculate this object
@property (nonatomic, weak, readonly) ASElementMap *elementMap;

@property (nonatomic, assign, readonly) CGSize contentSize;
/// Element to layout attributes map. Should use weak pointers for elements.
@property (nonatomic, strong, readonly) NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *elementToLayoutArrtibutesMap;

- (instancetype)init __unavailable;

/**
* Designated initializer.
*
* @param elementMap The element map used to calculate this object
*
* @param contentSize The content size of the collection's layout
*
* @param elementToLayoutArrtibutesMap Map between elements to their layout attributes. The map may contain all elements, or a subset of them and to be updated later. Should use weak pointers for elements.
*/
- (instancetype)initWithElementMap:(ASElementMap *)elementMap contentSize:(CGSize)contentSize elementToLayoutArrtibutesMap:(NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)attrsMap NS_DESIGNATED_INITIALIZER;

@end

NS_ASSUME_NONNULL_END
26 changes: 26 additions & 0 deletions Source/Details/ASCollectionContentAttributes.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// ASCollectionContentAttributes.m
// AsyncDisplayKit
//
// Created by Huy Nguyen on 9/3/17.
// Copyright © 2017 Facebook. All rights reserved.
//

#import <AsyncDisplayKit/ASCollectionContentAttributes.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASElementMap.h>

@implementation ASCollectionContentAttributes

- (instancetype)initWithElementMap:(ASElementMap *)elementMap contentSize:(CGSize)contentSize elementToLayoutArrtibutesMap:(NSMapTable<ASCollectionElement *,UICollectionViewLayoutAttributes *> *)attrsMap
{
self = [super init];
if (self) {
_elementMap = elementMap;
_contentSize = contentSize;
_elementToLayoutArrtibutesMap = attrsMap;
}
return self;
}

@end
Loading

0 comments on commit 28eb7e4

Please sign in to comment.