From 7db0eeb63395a5b4447f0325a3d86f92f506c96a Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 6 Mar 2017 18:33:00 +0000 Subject: [PATCH] Add ASLegacyCollectionLayoutCalculator for backward compatibility --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 ++ Source/ASCollectionView.mm | 44 +++++-- Source/ASTableView.mm | 19 ++- Source/AsyncDisplayKit.h | 1 + .../ASCollectionFlowLayoutCalculator.m | 8 +- .../Details/ASCollectionLayoutCalculating.h | 11 +- .../ASCollectionLayoutSpecCalculator.m | 5 + Source/Details/ASCollectionViewLayout.m | 2 +- Source/Details/ASDataController.h | 4 +- Source/Details/ASDataController.mm | 124 ++++++------------ .../ASLegacyCollectionLayoutCalculator.h | 22 ++++ .../ASLegacyCollectionLayoutCalculator.m | 50 +++++++ .../Sample/MosaicCollectionLayoutCalculator.m | 5 + 13 files changed, 193 insertions(+), 110 deletions(-) create mode 100644 Source/Details/ASLegacyCollectionLayoutCalculator.h create mode 100644 Source/Details/ASLegacyCollectionLayoutCalculator.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 4b7034de9a..88c21492f7 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -368,6 +368,8 @@ DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; + E52465BC1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.h in Headers */ = {isa = PBXBuildFile; fileRef = E52465BA1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.h */; }; + E52465BD1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = E52465BB1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.m */; }; E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; }; E5711A2C1C840C81009619D4 /* ASCollectionElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASCollectionElement.h */; settings = {ATTRIBUTES = (Private, ); }; }; E5711A301C840C96009619D4 /* ASCollectionElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */; }; @@ -775,6 +777,8 @@ DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTransition.mm; sourceTree = ""; }; E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTransition.h; sourceTree = ""; }; + E52465BA1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLegacyCollectionLayoutCalculator.h; sourceTree = ""; }; + E52465BB1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLegacyCollectionLayoutCalculator.m; sourceTree = ""; }; E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutElement.mm; sourceTree = ""; }; E5711A2A1C840C81009619D4 /* ASCollectionElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionElement.h; sourceTree = ""; }; E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionElement.mm; sourceTree = ""; }; @@ -1399,6 +1403,8 @@ E5B077F01E6855B100C24B5B /* ASCollectionLayoutSpecCalculator.m */, E5B0780A1E6A01E200C24B5B /* ASCollectionFlowLayoutCalculator.h */, E5B0780B1E6A01E200C24B5B /* ASCollectionFlowLayoutCalculator.m */, + E52465BA1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.h */, + E52465BB1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.m */, E5B077E11E6843A600C24B5B /* ASCollectionViewLayout.h */, E5B077F41E68576B00C24B5B /* ASCollectionViewLayout.m */, ); @@ -1467,6 +1473,7 @@ 68FC85E31CE29B7E00EDD713 /* ASTabBarController.h in Headers */, B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */, B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */, + E52465BC1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.h in Headers */, B35062171B010EFD0018CF92 /* ASDataController.h in Headers */, 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */, 68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */, @@ -1891,6 +1898,7 @@ 68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */, CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.m in Sources */, 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */, + E52465BD1E6DE58E003632D0 /* ASLegacyCollectionLayoutCalculator.m in Sources */, 9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */, 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 607b5d70a7..5d1fc56a0a 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -17,14 +17,13 @@ #import #import #import -#import -#import #import #import #import #import #import #import +#import #import #import #import @@ -144,6 +143,8 @@ @interface ASCollectionView () _layoutCalculator; + struct { unsigned int scrollViewDidScroll:1; unsigned int scrollViewWillBeginDragging:1; @@ -218,8 +219,9 @@ @interface ASCollectionView () )collectionViewLayoutCalculator +- (id)collectionLayoutCalculator { - return _collectionViewLayoutFlags.providesLayoutCalculator ? [(id)self.collectionViewLayout collectionViewLayoutCalculator] : nil; + ASDisplayNodeAssertMainThread(); + + // If layout object provides layout calculator, use it. But clear any existing default calculator first. + if (_collectionViewLayoutFlags.providesLayoutCalculator) { + _layoutCalculator = nil; + return [(id)self.collectionViewLayout collectionLayoutCalculator]; + } + + // Layout object doesn't provide a layout calculator. Lazily create a default one. + if (_layoutCalculator == nil) { + _layoutCalculator = [[ASLegacyCollectionLayoutCalculator alloc] init]; + } + return _layoutCalculator; } - (CGSize)viewportSizeForCollectionLayout { - return _collectionViewLayoutFlags.providesLayoutCalculator ? [(id)self.collectionViewLayout viewportSizeForCollectionLayout] : CGSizeZero; + ASDisplayNodeAssertMainThread(); + return _collectionViewLayoutFlags.providesLayoutCalculator ? [(id)self.collectionViewLayout viewportSizeForCollectionLayout] : self.bounds.size;; } #pragma mark - ASCollectionLayoutCalculatorResultConsuming @@ -1705,9 +1721,7 @@ - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSize return [self.layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; } - if (_collectionViewLayoutFlags.providesLayoutCalculator == NO) { - ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - } + ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); return ASSizeRangeMake(CGSizeZero, CGSizeZero); } @@ -2050,7 +2064,9 @@ - (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds new if (self.collectionViewLayout == nil) { return; } - if (_collectionViewLayoutFlags.providesLayoutCalculator) { + if (_collectionViewLayoutFlags.isASCollectionViewLayout) { + // ASCollectionView should be able to handle bounds changes and automatically re-run its calculator. + // TODO This is more or less a workaround for the current relayout system which is destined for an overhaul. return; } CGSize lastUsedSize = _lastBoundsSizeUsedForMeasuringNodes; diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index ee4513971d..685cf8db61 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -24,6 +24,7 @@ #import #import #import +#import #import #import #import @@ -181,6 +182,8 @@ @interface ASTableView () _layoutCalculator; + struct { unsigned int scrollViewDidScroll:1; unsigned int scrollViewWillBeginDragging:1; @@ -288,6 +291,8 @@ - (void)configureWithDataControllerClass:(Class)dataControllerClass eventLog:(AS _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; super.dataSource = (id)_proxyDataSource; + + _layoutCalculator = [[ASLegacyCollectionLayoutCalculator alloc] init]; [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; } @@ -1542,7 +1547,19 @@ - (void)rangeController:(ASRangeController *)rangeController didUpdateWithChange [changeSet executeCompletionHandlerWithFinished:YES]; } -#pragma mark - ASDataControllerDelegate +#pragma mark - ASDataControllerSource + +- (id)collectionLayoutCalculator +{ + ASDisplayNodeAssertMainThread(); + return _layoutCalculator; +} + +- (CGSize)viewportSizeForCollectionLayout +{ + ASDisplayNodeAssertMainThread(); + return CGSizeMake(_nodesConstrainedWidth, self.bounds.size.height); +} - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { ASCellNodeBlock block = nil; diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index 47d12973af..8bad9d925a 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -41,6 +41,7 @@ #import #import #import +#import #import #import diff --git a/Source/Details/ASCollectionFlowLayoutCalculator.m b/Source/Details/ASCollectionFlowLayoutCalculator.m index e2a324ac04..0b8ffc873b 100644 --- a/Source/Details/ASCollectionFlowLayoutCalculator.m +++ b/Source/Details/ASCollectionFlowLayoutCalculator.m @@ -11,17 +11,13 @@ #import #import #import +#import @implementation ASCollectionFlowLayoutCalculator - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)sizeRange forElementMap:(ASElementMap *)map { - // TODO Use ASArrayByFlatMapping - NSMutableArray *children = [NSMutableArray array]; - for (ASCollectionElement *element in map) { - [children addObject:element.node]; - } - + NSArray *children = ASArrayByFlatMapping(map, ASCollectionElement *element, element.node); ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentStart diff --git a/Source/Details/ASCollectionLayoutCalculating.h b/Source/Details/ASCollectionLayoutCalculating.h index 358bf70b89..890ae08b3b 100644 --- a/Source/Details/ASCollectionLayoutCalculating.h +++ b/Source/Details/ASCollectionLayoutCalculating.h @@ -15,10 +15,17 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASCollectionLayoutCalculating +/** + * Whether or not size ranges must be provided via collection element in the element map. + * Default to NO, which means the calculator can determine size range for each collection element. + * ASLegacyCollectionLayoutCalculator might return YES because size ranges used to be provided via async delegate of table/collection view or layout inspector of collection view. + */ +@property (nonatomic, assign, readonly) BOOL sizeRangesShouldBeProvided; + /** * @abstract Return a layout of elements. * - * @param viewportSize The viewport size that the resulting layout should fit, usually equals to the bounds of the containing collection view. + * @param viewportSize The viewport size that the resulting layout should fit, usually equals to the bounds of the containing table/collection view. * * @param map An element map containings all elements to be laid out. * @@ -31,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASCollectionLayoutCalculatorProviding -- (nullable id)collectionViewLayoutCalculator; +- (id)collectionLayoutCalculator; - (CGSize)viewportSizeForCollectionLayout; diff --git a/Source/Details/ASCollectionLayoutSpecCalculator.m b/Source/Details/ASCollectionLayoutSpecCalculator.m index 670ac4d6d8..b3164d3368 100644 --- a/Source/Details/ASCollectionLayoutSpecCalculator.m +++ b/Source/Details/ASCollectionLayoutSpecCalculator.m @@ -12,6 +12,11 @@ @implementation ASCollectionLayoutSpecCalculator +- (BOOL)sizeRangesShouldBeProvided +{ + return NO; +} + - (instancetype)init { return [self initWithScrollableDirections:ASScrollDirectionVerticalDirections]; diff --git a/Source/Details/ASCollectionViewLayout.m b/Source/Details/ASCollectionViewLayout.m index 38dd6ae39e..ea1e236c20 100644 --- a/Source/Details/ASCollectionViewLayout.m +++ b/Source/Details/ASCollectionViewLayout.m @@ -50,7 +50,7 @@ - (instancetype)initWithLayoutCalculator:(id)calc #pragma mark - ASCollectionLayoutCalculatorProviding -- (id)collectionViewLayoutCalculator +- (id)collectionLayoutCalculator { return _calculator; } diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h index 402721b720..673bc67782 100644 --- a/Source/Details/ASDataController.h +++ b/Source/Details/ASDataController.h @@ -12,6 +12,7 @@ #import #import +#import #import #import #ifdef __cplusplus @@ -46,7 +47,7 @@ extern NSString * const ASCollectionInvalidUpdateException; It will be invoked in the same thread as the api call of ASDataController. */ -@protocol ASDataControllerSource +@protocol ASDataControllerSource /** Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path. @@ -56,7 +57,6 @@ extern NSString * const ASCollectionInvalidUpdateException; /** The constrained size range for layout. */ -// TODO now that there is a layout calculator, this might be optional? - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; /** diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index f3e056e319..cf35e2a2ac 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -67,7 +67,6 @@ @interface ASDataController () { BOOL _initialReloadDataHasBeenCalled; struct { - unsigned int providesCollectionViewLayoutCalculator:1; unsigned int consumesResultOfCollectionViewLayoutCalculator:1; unsigned int supplementaryNodeKindsInSections:1; unsigned int supplementaryNodesOfKindInSection:1; @@ -91,7 +90,6 @@ - (instancetype)initWithDataSource:(id)dataSource eventL _dataSource = dataSource; - _dataSourceFlags.providesCollectionViewLayoutCalculator = [_dataSource conformsToProtocol:@protocol(ASCollectionLayoutCalculatorProviding)]; _dataSourceFlags.consumesResultOfCollectionViewLayoutCalculator = [_dataSource conformsToProtocol:@protocol(ASCollectionLayoutCalculatorResultConsuming)]; _dataSourceFlags.supplementaryNodeKindsInSections = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeKindsInSections:)]; _dataSourceFlags.supplementaryNodesOfKindInSection = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodesOfKind:inSection:)]; @@ -139,7 +137,7 @@ + (NSUInteger)parallelProcessorCount #pragma mark - Cell Layout -- (void)batchAllocateNodesFromContexts:(NSArray *)elements andLayout:(BOOL)layout batchSize:(NSInteger)batchSize batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler +- (void)batchAllocateNodesFromContexts:(NSArray *)elements batchSize:(NSInteger)batchSize batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler { ASSERT_ON_EDITING_QUEUE; #if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK @@ -162,7 +160,7 @@ - (void)batchAllocateNodesFromContexts:(NSArray *)element for (NSUInteger i = 0; i < count; i += batchSize) { NSRange batchedRange = NSMakeRange(i, MIN(count - i, batchSize)); NSArray *batchedContexts = [elements subarrayWithRange:batchedRange]; - NSArray *nodes = [self _allocateNodesFromContexts:batchedContexts andLayout:layout]; + NSArray *nodes = [self _allocateNodesFromContexts:batchedContexts]; batchCompletionHandler(batchedContexts, nodes); } @@ -181,7 +179,8 @@ - (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrai node.frame = frame; } -- (NSArray *)_allocateNodesFromContexts:(NSArray *)elements andLayout:(BOOL)layout +// TODO Is returned array still needed? Can it be removed? +- (NSArray *)_allocateNodesFromContexts:(NSArray *)elements { ASSERT_ON_EDITING_QUEUE; @@ -204,18 +203,6 @@ - (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrai node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. } - // Layout the node if the size range is valid. - if (layout) { - ASSizeRange sizeRange = context.constrainedSize; - if (ASSizeRangeHasSignificantArea(sizeRange)) { - [self _layoutNode:node withConstrainedSize:sizeRange]; - } - -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - [ASDataController _didLayoutNode]; -#endif - } - allocatedNodeBuffer[i] = node; }); @@ -276,14 +263,14 @@ - (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrai * @param changeSet The changeset that triggered this repopulation. * @param environment The trait environment needed to initialize elements * @param indexPathsAreNew YES if index paths are "after the update," NO otherwise. - * @param shouldFetchConstrainedSizes Whether constrained sizes should be fetched from data source + * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source */ - (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map forSectionsContainingIndexPaths:(NSArray *)indexPaths changeSet:(_ASHierarchyChangeSet *)changeSet environment:(id)environment indexPathsAreNew:(BOOL)indexPathsAreNew - shouldFetchConstrainedSizes:(BOOL)shouldFetchConstrainedSizes + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); @@ -306,7 +293,7 @@ - (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map } for (NSString *kind in [self supplementaryKindsInSections:newSections]) { - [self _insertElementsIntoMap:map kind:kind forSections:newSections environment:environment shouldFetchConstrainedSizes:shouldFetchConstrainedSizes]; + [self _insertElementsIntoMap:map kind:kind forSections:newSections environment:environment shouldFetchSizeRanges:shouldFetchSizeRanges]; } } @@ -316,13 +303,13 @@ - (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map * @param kind The kind of the elements, e.g ASDataControllerRowNodeKind * @param sections The sections that should be populated by new elements * @param environment The trait environment needed to initialize elements - * @param shouldFetchConstrainedSizes Whether constrained sizes should be fetched from data source + * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source */ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map kind:(NSString *)kind forSections:(NSIndexSet *)sections environment:(id)environment - shouldFetchConstrainedSizes:(BOOL)shouldFetchConstrainedSizes + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); @@ -331,7 +318,7 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map } NSArray *indexPaths = [self _allIndexPathsForItemsOfKind:kind inSections:sections]; - [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPaths environment:environment shouldFetchConstrainedSizes:shouldFetchConstrainedSizes]; + [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPaths environment:environment shouldFetchSizeRanges:shouldFetchSizeRanges]; } /** @@ -341,13 +328,13 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map * @param kind The kind of the elements, e.g ASDataControllerRowNodeKind * @param indexPaths The index paths at which new elements should be populated * @param environment The trait environment needed to initialize elements - * @param shouldFetchConstrainedSizes Whether constrained sizes should be fetched from data source + * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source */ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map kind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths environment:(id)environment - shouldFetchConstrainedSizes:(BOOL)shouldFetchConstrainedSizes + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); @@ -371,14 +358,14 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map } ASSizeRange constrainedSize; - if (shouldFetchConstrainedSizes) { + if (shouldFetchSizeRanges) { constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; } ASCollectionElement *element = [[ASCollectionElement alloc] initWithNodeBlock:nodeBlock - supplementaryElementKind:isRowKind ? nil : kind - constrainedSize:constrainedSize - environment:environment]; + supplementaryElementKind:isRowKind ? nil : kind + constrainedSize:constrainedSize + environment:environment]; [map insertElement:element atIndexPath:indexPath]; } } @@ -488,13 +475,9 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet } } - id layoutCalculator; - CGSize viewportSize; - if (_dataSourceFlags.providesCollectionViewLayoutCalculator) { - layoutCalculator = [(id)_dataSource collectionViewLayoutCalculator]; - viewportSize = [(id)_dataSource viewportSizeForCollectionLayout]; - } - BOOL canUseLayoutCalculator = (layoutCalculator != nil && CGSizeEqualToSize(CGSizeZero, viewportSize) == NO); + id layoutCalculator = [_dataSource collectionLayoutCalculator]; + BOOL shouldFetchSizeRanges = layoutCalculator.sizeRangesShouldBeProvided; + CGSize viewportSize = [(id)_dataSource viewportSizeForCollectionLayout]; // Mutable copy of current data. ASMutableElementMap *mutableMap = [_pendingMap mutableCopy]; @@ -502,7 +485,7 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet // Step 1: update the mutable copies to match the data source's state [self _updateSectionContextsInMap:mutableMap changeSet:changeSet]; //TODO If _elements is the same, use a fast path - [self _updateElementsInMap:mutableMap changeSet:changeSet shouldFetchConstrainedSizes:(canUseLayoutCalculator == NO)]; + [self _updateElementsInMap:mutableMap changeSet:changeSet shouldFetchSizeRanges:shouldFetchSizeRanges]; // Step 2: Clone the new data ASElementMap *newMap = [mutableMap copy]; @@ -511,22 +494,19 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ // Step 3: Allocate and layout elements. - NSArray *unprocessedElements; - if (canUseLayoutCalculator) { - // If the calculator is available, allocate all elements and let it figure which elements need to be measured - unprocessedElements = [ASDataController unallocatedElementsFromMap:newMap]; - } else { - // If the calculator is unavailable, allocate and measure all elements that need to be measure - unprocessedElements = [ASDataController unmeasuredElementsFromMap:newMap]; - } + NSArray *unallocatedElements = ASArrayByFlatMapping(newMap, + ASCollectionElement *element, + (element.nodeIfAllocated == nil ? element : nil)); - [self batchAllocateNodesFromContexts:unprocessedElements andLayout:(canUseLayoutCalculator == NO) batchSize:unprocessedElements.count batchCompletion:^(NSArray *elements, NSArray *nodes) { + // TODO Maybe let the calculator to drive node allocation? + [self batchAllocateNodesFromContexts:unallocatedElements batchSize:unallocatedElements.count batchCompletion:^(NSArray *elements, NSArray *nodes) { ASSERT_ON_EDITING_QUEUE; - ASLayout *layout; - if (canUseLayoutCalculator) { - layout = [layoutCalculator layoutThatFits:viewportSize forElementMap:newMap]; - } + ASLayout *layout = [layoutCalculator layoutThatFits:viewportSize forElementMap:newMap]; + +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + [ASDataController _didLayoutNode]; +#endif [_mainSerialQueue performBlockOnMainThread:^{ [_delegate dataController:self willUpdateWithChangeSet:changeSet]; @@ -534,7 +514,7 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet // Step 4: Deploy the new data as "completed" and inform delegate _visibleMap = newMap; - if (_dataSourceFlags.consumesResultOfCollectionViewLayoutCalculator && layout != nil) { + if (_dataSourceFlags.consumesResultOfCollectionViewLayoutCalculator) { [(id)_dataSource collectionLayoutCalculator:layoutCalculator didFinishLayout:layout forElementMap:newMap]; } @@ -594,7 +574,7 @@ - (void)_insertSectionContextsIntoMap:(ASMutableElementMap *)map indexes:(NSInde /** * Update elements based on the given change set. */ -- (void)_updateElementsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet shouldFetchConstrainedSizes:(BOOL)shouldFetchConstrainedSizes +- (void)_updateElementsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); @@ -606,7 +586,7 @@ - (void)_updateElementsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyC NSUInteger sectionCount = [self itemCountsFromDataSource].size(); if (sectionCount > 0) { NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - [self _insertElementsIntoMap:map sections:sectionIndexes environment:environment shouldFetchConstrainedSizes:shouldFetchConstrainedSizes]; + [self _insertElementsIntoMap:map sections:sectionIndexes environment:environment shouldFetchSizeRanges:shouldFetchSizeRanges]; } // Return immediately because reloadData can't be used in conjuntion with other updates. return; @@ -619,7 +599,7 @@ - (void)_updateElementsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyC changeSet:changeSet environment:environment indexPathsAreNew:NO - shouldFetchConstrainedSizes:shouldFetchConstrainedSizes]; + shouldFetchSizeRanges:shouldFetchSizeRanges]; } for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { @@ -629,24 +609,24 @@ - (void)_updateElementsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyC } for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map sections:change.indexSet environment:environment shouldFetchConstrainedSizes:shouldFetchConstrainedSizes]; + [self _insertElementsIntoMap:map sections:change.indexSet environment:environment shouldFetchSizeRanges:shouldFetchSizeRanges]; } for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind atIndexPaths:change.indexPaths environment:environment shouldFetchConstrainedSizes:shouldFetchConstrainedSizes]; + [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind atIndexPaths:change.indexPaths environment:environment shouldFetchSizeRanges:shouldFetchSizeRanges]; // Aggressively reload supplementary nodes (#1773 & #1629) [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths changeSet:changeSet environment:environment indexPathsAreNew:YES - shouldFetchConstrainedSizes:shouldFetchConstrainedSizes]; + shouldFetchSizeRanges:shouldFetchSizeRanges]; } } - (void)_insertElementsIntoMap:(ASMutableElementMap *)map sections:(NSIndexSet *)sectionIndexes environment:(id)environment - shouldFetchConstrainedSizes:(BOOL)shouldFetchConstrainedSizes + shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges { ASDisplayNodeAssertMainThread(); @@ -656,12 +636,12 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map // Items [map insertEmptySectionsOfItemsAtIndexes:sectionIndexes]; - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind forSections:sectionIndexes environment:environment shouldFetchConstrainedSizes:shouldFetchConstrainedSizes]; + [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind forSections:sectionIndexes environment:environment shouldFetchSizeRanges:shouldFetchSizeRanges]; // Supplementaries for (NSString *kind in [self supplementaryKindsInSections:sectionIndexes]) { // Step 2: Populate new elements for all sections - [self _insertElementsIntoMap:map kind:kind forSections:sectionIndexes environment:environment shouldFetchConstrainedSizes:shouldFetchConstrainedSizes]; + [self _insertElementsIntoMap:map kind:kind forSections:sectionIndexes environment:environment shouldFetchSizeRanges:shouldFetchSizeRanges]; } } @@ -727,30 +707,6 @@ - (void)_relayoutAllNodes } } -+ (NSArray *)unallocatedElementsFromMap:(ASElementMap *)map -{ - // TODO Use ASArrayByFlatMapping - NSMutableArray *unallocatedElements = [NSMutableArray array]; - for (ASCollectionElement *element in map) { - if (element.nodeIfAllocated == nil) { - [unallocatedElements addObject:element]; - } - } - return unallocatedElements; -} - -+ (NSArray *)unmeasuredElementsFromMap:(ASElementMap *)map -{ - // TODO Use ASArrayByFlatMapping - NSMutableArray *unmeasuredElements = [NSMutableArray array]; - for (ASCollectionElement *element in map) { - if (element.nodeIfAllocated.calculatedLayout == nil) { - [unmeasuredElements addObject:element]; - } - } - return unmeasuredElements; -} - @end #if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK diff --git a/Source/Details/ASLegacyCollectionLayoutCalculator.h b/Source/Details/ASLegacyCollectionLayoutCalculator.h new file mode 100644 index 0000000000..8addd093ea --- /dev/null +++ b/Source/Details/ASLegacyCollectionLayoutCalculator.h @@ -0,0 +1,22 @@ +// +// ASLegacyCollectionLayoutCalculator.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 6/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +/** + * Normally layout calculators can determine size range of all collection elements because it is the layout logic. + * However, prior to ASCollectionViewLayout, size ranges are provided by async delegate of ASTableView, or async delegate and layout inspector of ASCollectionView. + * This calculator is the bridge between the old size range API and the new layout calculating API, hence the name "legacy". + * Instead of deciding size ranges on its own, it uses the ones provided in collection elements. + * + * @note Since this calculator doesn't have a layout logic, it doesn't calculate positions of collection elements. + * Thus, the layout returned by the calculator is not meant to be consumed as is. + */ +@interface ASLegacyCollectionLayoutCalculator : NSObject + +@end diff --git a/Source/Details/ASLegacyCollectionLayoutCalculator.m b/Source/Details/ASLegacyCollectionLayoutCalculator.m new file mode 100644 index 0000000000..c2a9fb5a02 --- /dev/null +++ b/Source/Details/ASLegacyCollectionLayoutCalculator.m @@ -0,0 +1,50 @@ +// +// ASLegacyCollectionLayoutCalculator.m +// AsyncDisplayKit +// +// Created by Huy Nguyen on 6/3/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import + +@implementation ASLegacyCollectionLayoutCalculator + +- (BOOL)sizeRangesShouldBeProvided +{ + return YES; +} + +- (ASLayout *)layoutThatFits:(CGSize)viewportSize forElementMap:(ASElementMap *)map +{ + // TODO Use ASArrayByFlatMapping + NSMutableArray *elements = [NSMutableArray array]; + for (ASCollectionElement *element in map) { + if (element.node.calculatedLayout == nil) { + [elements addObject:element]; + } + } + + if (elements.count > 0) { + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + ASDispatchApply(elements.count, queue, 0, ^(size_t i) { + ASCollectionElement *element = elements[i]; + ASSizeRange sizeRange = element.constrainedSize; + if (ASSizeRangeHasSignificantArea(sizeRange)) { + [element.node layoutThatFits:sizeRange]; + } + }); + } + + return [ASLayout layoutWithLayoutElement:[[ASLayoutSpec alloc] init] size:viewportSize]; +} + +@end diff --git a/examples/CustomCollectionView/Sample/MosaicCollectionLayoutCalculator.m b/examples/CustomCollectionView/Sample/MosaicCollectionLayoutCalculator.m index 6ae1ebe02f..a39144ddf2 100644 --- a/examples/CustomCollectionView/Sample/MosaicCollectionLayoutCalculator.m +++ b/examples/CustomCollectionView/Sample/MosaicCollectionLayoutCalculator.m @@ -45,6 +45,11 @@ - (instancetype)initWithNumberOfColumns:(NSInteger)numberOfColumns headerHeight: return self; } +- (BOOL)sizeRangesShouldBeProvided +{ + return NO; +} + - (ASLayout *)layoutThatFits:(CGSize)size forElementMap:(ASElementMap *)map { CGFloat layoutWidth = size.width;