Merge pull request #694 from levi/supplementary-views

Add support for supplementary views in ASCollectionView.  Introduce collection-specific data controller.
This commit is contained in:
appleguy
2015-10-18 20:23:48 -07:00
26 changed files with 1620 additions and 152 deletions

2
.gitignore vendored
View File

@@ -26,3 +26,5 @@ docs/.sass-cache
*.lock
*.gcov
*.gcno
*.gcda

BIN
AsyncDisplayKit-Prefix.gcda Normal file

Binary file not shown.

View File

@@ -140,6 +140,12 @@
205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; };
242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; };
251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; settings = {ASSET_TAGS = (); }; };
251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; settings = {ASSET_TAGS = (); }; };
251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ASSET_TAGS = (); }; };
251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; settings = {ASSET_TAGS = (); }; };
251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; settings = {ASSET_TAGS = (); }; };
2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; settings = {ASSET_TAGS = (); }; };
2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; settings = {ASSET_TAGS = (); }; };
2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; };
@@ -206,6 +212,8 @@
509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; };
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; };
9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; };
9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C5FA3511B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -544,6 +552,12 @@
205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = "<group>"; };
205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = "<group>"; };
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = "<group>"; };
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionDataController.h; sourceTree = "<group>"; };
251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionDataController.mm; sourceTree = "<group>"; };
251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = "<group>"; };
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = "<group>"; };
251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDataController+Subclasses.h"; sourceTree = "<group>"; };
2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = "<group>"; };
2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = "<group>"; };
292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = "<group>"; };
292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerPreload.h; sourceTree = "<group>"; };
@@ -829,6 +843,7 @@
058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */,
058D09C6195D04C000B7D73C /* Supporting Files */,
052EE06A1A15A0D8002C6279 /* TestResources */,
2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */,
);
path = AsyncDisplayKitTests;
sourceTree = "<group>";
@@ -847,6 +862,11 @@
children = (
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */,
CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */,
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */,
251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */,
251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */,
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */,
251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */,
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */,
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
058D09E4195D050800B7D73C /* _ASDisplayView.h */,
@@ -1062,6 +1082,7 @@
054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */,
2967F9E21AB0A5190072E4AB /* ASBasicImageDownloaderInternal.h in Headers */,
299DA1A91A828D2900162D41 /* ASBatchContext.h in Headers */,
251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */,
044285071BAA63FE00D16268 /* ASBatchFetching.h in Headers */,
055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */,
ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */,
@@ -1091,6 +1112,7 @@
ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */,
ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */,
ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */,
251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */,
ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */,
9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */,
464052241A3F83C40061C0BA /* ASLayoutController.h in Headers */,
@@ -1125,6 +1147,7 @@
9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */,
ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */,
055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */,
251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */,
0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */,
058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */,
058D0A5B195D05DC00B7D73C /* ASTextNodeCoreTextAdditions.h in Headers */,
@@ -1474,6 +1497,7 @@
ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */,
9C5FA3531B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */,
9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */,
ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */,
0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */,
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
@@ -1490,6 +1514,7 @@
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */,
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */,
ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */,
ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */,
ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */,
@@ -1522,6 +1547,7 @@
2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */,
ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */,
058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */,
2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */,
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */,
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */,
058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */,
@@ -1548,6 +1574,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */,
9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */,
B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.m in Sources */,
B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */,
B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */,

View File

@@ -17,7 +17,7 @@
@class ASCellNode;
@protocol ASCollectionViewDataSource;
@protocol ASCollectionViewDelegate;
@protocol ASCollectionViewLayoutInspecting;
/**
* Node-based collection view.
@@ -63,7 +63,7 @@
*
* @param asyncDataFetchingEnabled Enable the data fetching in async mode.
*
* @discussion If asyncDataFetching is enabled, the `AScollectionView` will fetch data through `collectionView:numberOfRowsInSection:` and
* @discussion If asyncDataFetching is enabled, the `ASCollectionView` will fetch data through `collectionView:numberOfRowsInSection:` and
* `collectionView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically
* from calling thread.
* Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for
@@ -80,6 +80,18 @@
*/
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
/**
* Optional introspection object for the collection view's layout.
*
* @discussion Since supplementary and decoration views are controlled by the collection view's layout, this object
* is used as a bridge to provide information to the internal data controller about the existence of these views and
* their associated index paths. For collection views using `UICollectionViewFlowLayout`, a default inspector
* implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom
* collection view layout subclasses will need to provide their own implementation of an inspector object for their
* supplementary views to be compatible with `ASCollectionView`'s supplementary node support.
*/
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutDelegate;
/**
* Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread.
* The asyncDataSource must be updated to reflect the changes before the update block completes.
@@ -119,6 +131,18 @@
*/
- (void)reloadData;
/**
* Registers the given kind of supplementary node for use in creating node-backed supplementary views.
*
* @param kind The kind of supplementary node that will be requested through the data source.
*
* @discussion Use this method to register support for the use of supplementary nodes in place of the default
* `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:`
* methods. This method will register an internal backing view that will host the contents of the supplementary nodes
* returned from the data source.
*/
- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind;
/**
* Inserts one or more sections.
*
@@ -272,6 +296,15 @@
@optional
/**
* Asks the collection view to provide a supplementary node to display in the collection view.
*
* @param collectionView An object representing the collection view requesting this information.
* @param kind The kind of supplementary node to provide.
* @param indexPath The index path that specifies the location of the new supplementary node.
*/
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
* Provides the constrained size range for measuring the node at the index path.
*
@@ -342,18 +375,37 @@
*/
- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView;
@end
/**
* Defines methods that let you coordinate with a `UICollectionViewFlowLayout` in combination with an `ASCollectionView`.
*/
@protocol ASCollectionViewDelegateFlowLayout <ASCollectionViewDelegate>
@optional
/**
* Passthrough support to UICollectionViewDelegateFlowLayout sectionInset behavior.
*
* @param collectionView The sender.
* @param collectionViewLayout The layout object requesting the information.
* #param section The index number of the section whose insets are needed.
* @param section The index number of the section whose insets are needed.
*
* @discussion The same rules apply as the UICollectionView implementation, but this can also be used without a UICollectionViewFlowLayout.
* https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewDelegateFlowLayout_protocol/index.html#//apple_ref/occ/intfm/UICollectionViewDelegateFlowLayout/collectionView:layout:insetForSectionAtIndex:
*
*/
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
/**
* Asks the delegate for the size of the header in the specified section.
*/
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
/**
* Asks the delegate for the size of the footer in the specified section.
*/
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
@end

View File

@@ -11,11 +11,12 @@
#import "ASAssert.h"
#import "ASCollectionViewLayoutController.h"
#import "ASRangeController.h"
#import "ASDataController.h"
#import "ASCollectionDataController.h"
#import "ASDisplayNodeInternal.h"
#import "ASBatchFetching.h"
#import "UICollectionViewLayout+ASConvenience.h"
#import "ASInternalHelpers.h"
#import "ASCollectionViewFlowLayoutInspector.h"
// FIXME: Temporary nonsense import until method names are finalized and exposed
#import "ASDisplayNode+Subclasses.h"
@@ -37,9 +38,7 @@ static BOOL _isInterceptedSelector(SEL sel)
// handled by ASCollectionView node<->cell machinery
sel == @selector(collectionView:cellForItemAtIndexPath:) ||
sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
// TODO: Supplementary views are currently not supported. An assertion is triggered if the _asyncDataSource implements this method.
// sel == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) ||
sel == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) ||
// handled by ASRangeController
sel == @selector(numberOfSectionsInCollectionView:) ||
@@ -136,9 +135,10 @@ static BOOL _isInterceptedSelector(SEL sel)
_ASCollectionViewProxy *_proxyDataSource;
_ASCollectionViewProxy *_proxyDelegate;
ASDataController *_dataController;
ASCollectionDataController *_dataController;
ASRangeController *_rangeController;
ASCollectionViewLayoutController *_layoutController;
ASCollectionViewFlowLayoutInspector *_flowLayoutInspector;
BOOL _performingBatchUpdates;
NSMutableArray *_batchUpdateBlocks;
@@ -153,6 +153,8 @@ static BOOL _isInterceptedSelector(SEL sel)
CGSize _maxSizeForNodesConstrainedSize;
BOOL _ignoreMaxSizeChange;
NSMutableArray *_registeredSupplementaryKinds;
/**
* If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it.
@@ -201,10 +203,10 @@ static BOOL _isInterceptedSelector(SEL sel)
_rangeController.delegate = self;
_rangeController.layoutController = _layoutController;
_dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
_dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
_dataController.delegate = _rangeController;
_dataController.dataSource = self;
_batchContext = [[ASBatchContext alloc] init];
_leadingScreensForBatching = 1.0;
@@ -224,6 +226,14 @@ static BOOL _isInterceptedSelector(SEL sel)
// and should not trigger a relayout.
_ignoreMaxSizeChange = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero);
// Register the default layout inspector delegate for flow layouts only, custom layouts
// will need to roll their own ASCollectionViewLayoutInspecting implementation and set a layout delegate
if ([layout asdk_isFlowLayout]) {
_layoutDelegate = [self flowLayoutInspector];
}
_registeredSupplementaryKinds = [NSMutableArray array];
self.backgroundColor = [UIColor whiteColor];
[self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:@"_ASCollectionViewCell"];
@@ -239,6 +249,19 @@ static BOOL _isInterceptedSelector(SEL sel)
super.dataSource = nil;
}
/**
* A layout inspector implementation specific for the sizing behavior of UICollectionViewFlowLayouts
*/
- (ASCollectionViewFlowLayoutInspector *)flowLayoutInspector
{
if (_flowLayoutInspector == nil) {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout;
_flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self
flowLayout:layout];
}
return _flowLayoutInspector;
}
#pragma mark -
#pragma mark Overrides.
@@ -283,10 +306,6 @@ static BOOL _isInterceptedSelector(SEL sel)
_asyncDataSourceImplementsConstrainedSizeForNode = NO;
} else {
_asyncDataSource = asyncDataSource;
// TODO: Support supplementary views with ASCollectionView.
if ([_asyncDataSource respondsToSelector:@selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:)]) {
ASDisplayNodeAssert(NO, @"ASCollectionView is planned to support supplementary views by September 2015. You can work around this issue by using standard items.");
}
_proxyDataSource = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
_asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] ? 1 : 0);
@@ -313,6 +332,17 @@ static BOOL _isInterceptedSelector(SEL sel)
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
_asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0);
}
[_flowLayoutInspector cacheSelectorsForCollectionView:self];
}
- (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout
{
[super setCollectionViewLayout:collectionViewLayout];
if ([collectionViewLayout asdk_isFlowLayout]) {
_flowLayoutInspector = nil;
_layoutDelegate = [self flowLayoutInspector];
}
}
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
@@ -379,6 +409,14 @@ static BOOL _isInterceptedSelector(SEL sel)
[self performBatchAnimated:YES updates:updates completion:completion];
}
- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind
{
ASDisplayNodeAssert(elementKind != nil, @"A kind is needed for supplementary node registration");
[_registeredSupplementaryKinds addObject:elementKind];
[self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind
withReuseIdentifier:[self __reuseIdentifierForKind:elementKind]];
}
- (void)insertSections:(NSIndexSet *)sections
{
ASDisplayNodeAssertMainThread();
@@ -427,6 +465,11 @@ static BOOL _isInterceptedSelector(SEL sel)
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone];
}
- (NSString *)__reuseIdentifierForKind:(NSString *)kind
{
return [@"_ASCollectionSupplementaryView_" stringByAppendingString:kind];
}
#pragma mark -
#pragma mark Intercepted selectors.
@@ -437,6 +480,7 @@ static BOOL _isInterceptedSelector(SEL sel)
_ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
[_rangeController configureContentView:cell.contentView forCellNode:node];
cell.node = node;
@@ -449,6 +493,15 @@ static BOOL _isInterceptedSelector(SEL sel)
return [[_dataController nodeAtIndexPath:indexPath] calculatedSize];
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [self __reuseIdentifierForKind:kind];
UICollectionReusableView *view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:identifier forIndexPath:indexPath];
ASCellNode *node = [_dataController supplementaryNodeOfKind:kind atIndexPath:indexPath];
[_rangeController configureContentView:view forCellNode:node];
return view;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
_superIsPendingDataLoad = NO;
@@ -548,7 +601,7 @@ static BOOL _isInterceptedSelector(SEL sel)
_ignoreMaxSizeChange = NO;
} else {
[self performBatchAnimated:NO updates:^{
[_dataController relayoutAllRows];
[_dataController relayoutAllNodes];
} completion:nil];
}
}
@@ -628,7 +681,7 @@ static BOOL _isInterceptedSelector(SEL sel)
}
if (_asyncDelegateImplementsInsetSection) {
sectionInset = [_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section];
sectionInset = [(id<ASCollectionViewDelegateFlowLayout>)_asyncDelegate collectionView:self layout:self.collectionViewLayout insetForSectionAtIndex:indexPath.section];
}
if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) {
@@ -653,7 +706,7 @@ static BOOL _isInterceptedSelector(SEL sel)
return [_asyncDataSource collectionView:self numberOfItemsInSection:section];
}
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController {
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController {
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
return [_asyncDataSource numberOfSectionsInCollectionView:self];
} else {
@@ -681,8 +734,39 @@ static BOOL _isInterceptedSelector(SEL sel)
}
}
#pragma mark -
#pragma mark ASRangeControllerDelegate.
#pragma mark - ASCollectionViewDataControllerSource Supplementary view support
- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
ASCellNode *node = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath];
ASDisplayNodeAssert(node != nil, @"A node must be returned for a supplementary node");
return node;
}
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController
{
return _registeredSupplementaryKinds;
}
- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssert(_layoutDelegate != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return [_layoutDelegate collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath];
}
- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
{
ASDisplayNodeAssert(_layoutDelegate != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return [_layoutDelegate collectionView:self supplementaryNodesOfKind:kind inSection:section];
}
- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
{
ASDisplayNodeAssert(_layoutDelegate != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return [_layoutDelegate collectionView:self numberOfSectionsForSupplementaryNodeOfKind:kind];
}
#pragma mark - ASRangeControllerDelegate.
- (void)rangeControllerBeginUpdates:(ASRangeController *)rangeController {
ASDisplayNodeAssertMainThread();

View File

@@ -405,7 +405,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
_ignoreMaxWidthChange = NO;
} else {
[self beginUpdates];
[_dataController relayoutAllRows];
[_dataController relayoutAllNodes];
[self endUpdates];
}
}
@@ -859,7 +859,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
return [_asyncDataSource tableView:self numberOfRowsInSection:section];
}
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController
{
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
return [_asyncDataSource numberOfSectionsInTableView:self];

View File

@@ -0,0 +1,39 @@
/* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASDataController.h>
#import <AsyncDisplayKit/ASDimension.h>
@class ASDisplayNode;
@class ASCollectionDataController;
@protocol ASDataControllerSource;
@protocol ASCollectionDataControllerSource <ASDataControllerSource>
- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
The constrained size range for layout.
*/
- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController;
- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
@end
@interface ASCollectionDataController : ASDataController
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
@end

View File

@@ -0,0 +1,217 @@
/* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASCollectionDataController.h"
#import "ASAssert.h"
#import "ASMultidimensionalArrayUtils.h"
#import "ASDisplayNode.h"
#import "ASDisplayNodeInternal.h"
#import "ASDataController+Subclasses.h"
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
@interface ASCollectionDataController ()
- (id<ASCollectionDataControllerSource>)collectionDataSource;
@end
@implementation ASCollectionDataController {
NSMutableDictionary *_pendingNodes;
NSMutableDictionary *_pendingIndexPaths;
}
- (void)prepareForReloadData
{
_pendingNodes = [NSMutableDictionary dictionary];
_pendingIndexPaths = [NSMutableDictionary dictionary];
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
LOG(@"Populating elements of kind: %@", kind);
NSMutableArray *indexPaths = [NSMutableArray array];
NSMutableArray *nodes = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths];
}];
}
- (void)willReloadData
{
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind];
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
NSArray *editingNodes = [self editingNodesOfKind:kind];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
[self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil];
// Insert each section
NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind];
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]];
}
[self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil];
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
[_pendingNodes removeObjectForKey:kind];
[_pendingIndexPaths removeObjectForKey:kind];
}];
}
- (void)prepareForInsertSections:(NSIndexSet *)sections
{
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections);
NSMutableArray *nodes = [NSMutableArray array];
NSMutableArray *indexPaths = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths];
}];
}
- (void)willInsertSections:(NSIndexSet *)sections
{
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil];
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil];
_pendingNodes[kind] = nil;
_pendingIndexPaths[kind] = nil;
}];
}
- (void)willDeleteSections:(NSIndexSet *)sections
{
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
[self deleteSectionsOfKind:kind atIndexSet:sections completion:nil];
}];
}
- (void)prepareForReloadSections:(NSIndexSet *)sections
{
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
NSMutableArray *nodes = [NSMutableArray array];
NSMutableArray *indexPaths = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths];
}];
}
- (void)willReloadSections:(NSIndexSet *)sections
{
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// reinsert the elements
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil];
_pendingNodes[kind] = nil;
_pendingIndexPaths[kind] = nil;
}];
}
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
{
[[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// update the section of indexpaths
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection];
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]];
}];
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
}
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths
{
NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind];
for (NSUInteger i = 0; i < sectionCount; i++) {
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i];
NSUInteger rowCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:i];
for (NSUInteger j = 0; j < rowCount; j++) {
NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j];
[indexPaths addObject:indexPath];
[nodes addObject:[self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]];
}
}
}
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths
{
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSUInteger rowNum = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:idx];
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
for (NSUInteger i = 0; i < rowNum; i++) {
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
[indexPaths addObject:indexPath];
[nodes addObject:[self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]];
}
}];
}
#pragma mark - Sizing query
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
if ([kind isEqualToString:ASDataControllerRowNodeKind]) {
return [super constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
} else {
return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath];
}
}
#pragma mark - External supplementary store querying
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssertMainThread();
return [self completedNodesOfKind:kind][indexPath.section][indexPath.item];
}
#pragma mark - Private Helpers
- (NSArray *)supplementaryKinds
{
return [self.collectionDataSource supplementaryNodeKindsInDataController:self];
}
- (id<ASCollectionDataControllerSource>)collectionDataSource
{
return (id<ASCollectionDataControllerSource>)self.dataSource;
}
@end

View File

@@ -0,0 +1,42 @@
/* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASDimension.h>
@class ASCollectionView;
@protocol ASCollectionViewLayoutInspecting <NSObject>
/**
* Asks the inspector to provide a constrained size range for the given supplementary node.
*/
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
* Asks the inspector for the number of supplementary sections in the collection view for the given kind.
*/
- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
/**
* Asks the inspector for the number of supplementary views for the given kind in the specified section.
*/
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
@end
@interface ASCollectionViewFlowLayoutInspector : NSObject <ASCollectionViewLayoutInspecting>
@property (nonatomic, weak) UICollectionViewFlowLayout *layout;
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout;
- (void)cacheSelectorsForCollectionView:(ASCollectionView *)collectionView;
@end

View File

@@ -0,0 +1,121 @@
/* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "ASCollectionViewFlowLayoutInspector.h"
#import "ASCollectionView.h"
@implementation ASCollectionViewFlowLayoutInspector {
BOOL _delegateImplementsReferenceSizeForHeader;
BOOL _delegateImplementsReferenceSizeForFooter;
}
#pragma mark - Accessors
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout;
{
self = [super init];
if (flowLayout == nil) {
return nil;
}
if (self != nil) {
[self cacheSelectorsForCollectionView:collectionView];
_layout = flowLayout;
}
return self;
}
- (void)cacheSelectorsForCollectionView:(ASCollectionView *)collectionView
{
if (collectionView == nil) {
_delegateImplementsReferenceSizeForHeader = nil;
_delegateImplementsReferenceSizeForFooter = nil;
} else {
_delegateImplementsReferenceSizeForHeader = [[self delegateForCollectionView:collectionView] respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)];
_delegateImplementsReferenceSizeForFooter = [[self delegateForCollectionView:collectionView] respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)];
}
}
#pragma mark - ASCollectionViewLayoutInspecting
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
CGSize constrainedSize;
CGSize supplementarySize = [self sizeForSupplementaryViewOfKind:kind inSection:indexPath.section collectionView:collectionView];
if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
constrainedSize = CGSizeMake(collectionView.bounds.size.width, supplementarySize.height);
} else {
constrainedSize = CGSizeMake(supplementarySize.height, collectionView.bounds.size.height);
}
return ASSizeRangeMake(CGSizeZero, constrainedSize);
}
- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind
{
if ([collectionView.asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
return [collectionView.asyncDataSource numberOfSectionsInCollectionView:collectionView];
} else {
return 1;
}
}
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
{
return [self layoutHasSupplementaryViewOfKind:kind inSection:section collectionView:collectionView] ? 1 : 0;
}
#pragma mark - Private helpers
- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView
{
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
if (_delegateImplementsReferenceSizeForHeader) {
return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:section];
} else {
return [self.layout headerReferenceSize];
}
} else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) {
if (_delegateImplementsReferenceSizeForFooter) {
return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:section];
} else {
return [self.layout footerReferenceSize];
}
} else {
return CGSizeZero;
}
}
- (BOOL)layoutHasSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView
{
CGSize size = [self sizeForSupplementaryViewOfKind:kind inSection:section collectionView:collectionView];
if ([self usedLayoutValueForSize:size] > 0) {
return YES;
} else {
return NO;
}
}
- (CGFloat)usedLayoutValueForSize:(CGSize)size
{
if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
return size.height;
} else {
return size.width;
}
}
- (id<ASCollectionViewDelegateFlowLayout>)delegateForCollectionView:(ASCollectionView *)collectionView
{
return (id<ASCollectionViewDelegateFlowLayout>)collectionView.asyncDelegate;
}
@end

View File

@@ -137,7 +137,9 @@ typedef struct ASRangeGeometry ASRangeGeometry;
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds];
for (UICollectionViewLayoutAttributes *la in layoutAttributes) {
[indexPathSet addObject:la.indexPath];
if (la.representedElementCategory == UICollectionElementCategoryCell) {
[indexPathSet addObject:la.indexPath];
}
}
return indexPathSet;
}

View File

@@ -0,0 +1,159 @@
/* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASDataController.h"
@interface ASDataController (Subclasses)
#pragma mark - Internal editing & completed store querying
/**
* Provides a collection of index paths for nodes of the given kind that are currently in the editing store
*/
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind;
/**
* Read-only access to the underlying editing nodes of the given kind
*/
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind;
/**
* Read only access to the underlying completed nodes of the given kind
*/
- (NSMutableArray *)completedNodesOfKind:(NSString *)kind;
#pragma mark - Node sizing
/**
* Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`.
*/
- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock;
/*
* Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes.
*
* @discussion Once nodes have loaded their views, we can't layout in the background so this is a chance
* to do so immediately on the main thread.
*/
- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths;
/**
* Provides the size range for a specific node during the layout process.
*/
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
#pragma mark - Node & Section Insertion/Deletion API
/**
* Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes.
*/
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock;
/**
* Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes.
*/
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock;
/**
* Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished.
*/
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock;
/**
* Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished.
*/
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock;
#pragma mark - Data Manipulation Hooks
/**
* Notifies the subclass to perform any work needed before the data controller is reloaded entirely
*
* @discussion This method will be performed before the data controller enters its editing queue, usually on the main
* thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*/
- (void)prepareForReloadData;
/**
* Notifies the subclass that the data controller is about to reload its data entirely
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform new node creation like supplementary views
* or header/footer nodes.
*/
- (void)willReloadData;
/**
* Notifies the subclass to perform setup before sections are inserted in the data controller
*
* @discussion This method will be performed before the data controller enters its editing queue, usually on the main
* thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param sections Indices of sections to be inserted
*/
- (void)prepareForInsertSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will insert new sections at the given position
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param sections Indices of sections to be inserted
*/
- (void)willInsertSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will delete sections at the given positions
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param sections Indices of sections to be deleted
*/
- (void)willDeleteSections:(NSIndexSet *)sections;
/**
* Notifies the subclass to perform any work needed before the given sections will be reloaded.
*
* @discussion This method will be performed before the data controller enters its editing queue, usually on the main
* thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param sections Indices of sections to be reloaded
*/
- (void)prepareForReloadSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will reload the sections in the given index set
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param sections Indices of sections to be reloaded
*/
- (void)willReloadSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will move a section to a new position
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param section Index of current section position
* @param newSection Index of new section position
*/
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection;
@end

View File

@@ -14,6 +14,8 @@
@class ASCellNode;
@class ASDataController;
FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
typedef NSUInteger ASDataControllerAnimationOptions;
/**
@@ -40,7 +42,7 @@ typedef NSUInteger ASDataControllerAnimationOptions;
/**
Fetch the number of sections.
*/
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController;
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController;
/**
Lock the data source for data fetching.
@@ -90,7 +92,6 @@ typedef NSUInteger ASDataControllerAnimationOptions;
@end
/**
* Controller to layout data in background, and managed data updating.
*
@@ -156,10 +157,12 @@ typedef NSUInteger ASDataControllerAnimationOptions;
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Re-measures all loaded nodes. Used to respond to a change in size of the containing view
* Re-measures all loaded nodes in the backing store.
*
* @discussion Used to respond to a change in size of the containing view
* (e.g. ASTableView or ASCollectionView after an orientation change).
*/
- (void)relayoutAllRows;
- (void)relayoutAllNodes;
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
@@ -177,6 +180,9 @@ typedef NSUInteger ASDataControllerAnimationOptions;
- (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths;
- (NSArray *)completedNodes; // This provides efficient access to the entire _completedNodes multidimensional array.
/**
* Direct access to the nodes that have completed calculation and layout
*/
- (NSArray *)completedNodes;
@end

View File

@@ -21,12 +21,16 @@
const static NSUInteger kASDataControllerSizingCountPerProcessor = 5;
NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
static void *kASSizingQueueContext = &kASSizingQueueContext;
@interface ASDataController () {
NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available.
NSMutableArray *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable.
NSMutableArray *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes.
NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable.
NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes.
NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking.
NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes.
@@ -52,9 +56,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
return nil;
}
_completedNodes = [NSMutableArray array];
_editingNodes = [NSMutableArray array];
_completedNodes = [NSMutableDictionary dictionary];
_editingNodes = [NSMutableDictionary dictionary];
_completedNodes[ASDataControllerRowNodeKind] = [NSMutableArray array];
_editingNodes[ASDataControllerRowNodeKind] = [NSMutableArray array];
_pendingEditCommandBlocks = [NSMutableArray array];
_editingTransactionQueue = [[NSOperationQueue alloc] init];
@@ -96,26 +103,53 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Cell Layout
/*
* FIXME: Shouldn't this method, as well as `_layoutNodes:atIndexPaths:withAnimationOptions:` use the word "measure" instead?
*
* Once nodes have loaded their views, we can't layout in the background so this is a chance
* to do so immediately on the main thread.
*/
- (void)_layoutNodesWithMainThreadAffinity:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths {
- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
// Processing in batches
for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) {
NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize));
NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange];
NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange];
[self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock];
}
}
- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths {
NSAssert(NSThread.isMainThread, @"Main thread layout must be on the main thread.");
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, __unused BOOL * stop) {
ASCellNode *node = nodes[idx];
if (node.isNodeLoaded) {
ASSizeRange constrainedSize = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
[self _layoutNode:node withConstrainedSize:constrainedSize];
}
}];
}
- (void)_layoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
/**
* Measure and layout the given node with the constrained size range.
*/
- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize
{
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
}
/**
* Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store.
*/
- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
// Insert finished nodes into data storage
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}
- (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"Cell node layout must be initiated from edit transaction queue");
@@ -131,7 +165,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
for (NSUInteger k = j; k < j + batchCount; k++) {
ASCellNode *node = nodes[k];
if (!node.isNodeLoaded) {
nodeBoundSizes[k] = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPaths[k]];
nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]];
}
}
@@ -139,11 +173,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
for (NSUInteger k = j; k < j + batchCount; k++) {
ASCellNode *node = nodes[k];
// Only measure nodes whose views aren't loaded, since we're in the background.
// We should already have measured loaded nodes before we left the main thread, using _layoutNodesWithMainThreadAffinity:
// We should already have measured loaded nodes before we left the main thread, using layoutLoadedNodes:ofKind:atIndexPaths:
if (!node.isNodeLoaded) {
ASSizeRange constrainedSize = nodeBoundSizes[k];
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
[self _layoutNode:node withConstrainedSize:nodeBoundSizes[k]];
}
}
});
@@ -152,83 +184,147 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated.
dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER);
free(nodeBoundSizes);
// Insert finished nodes into data storage
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
if (completionBlock)
completionBlock(nodes, indexPaths);
}
- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
// Processing in batches
for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) {
NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize));
NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange];
NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange];
[self _layoutNodes:batchedNodes atIndexPaths:batchedIndexPaths withAnimationOptions:animationOptions];
}
return [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
}
#pragma mark - Internal Data Querying + Editing
#pragma mark - External Data Querying + Editing
- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{
if (indexPaths.count == 0)
return;
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths, nodes);
NSMutableArray *editingNodes = _editingNodes[kind];
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes);
_editingNodes[kind] = editingNodes;
// Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads.
NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_editingNodes);
NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes);
ASDisplayNodePerformBlockOnMainThread(^{
_completedNodes = completedNodes;
if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
_completedNodes[kind] = completedNodes;
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
});
}
- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{
if (indexPaths.count == 0)
return;
LOG(@"_deleteNodesAtIndexPaths:%@, full index paths in _editingNodes = %@", indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes));
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths);
LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind]));
NSMutableArray *editingNodes = _editingNodes[kind];
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths);
_editingNodes[kind] = editingNodes;
ASDisplayNodePerformBlockOnMainThread(^{
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, indexPaths);
if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
});
}
- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock
{
if (indexSet.count == 0)
return;
[_editingNodes insertObjects:sections atIndexes:indexSet];
if (_editingNodes[kind] == nil) {
_editingNodes[kind] = [NSMutableArray array];
}
[_editingNodes[kind] insertObjects:sections atIndexes:indexSet];
// Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads.
NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections);
ASDisplayNodePerformBlockOnMainThread(^{
[_completedNodes insertObjects:sectionsForCompleted atIndexes:indexSet];
if (_delegateDidInsertSections)
[_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
[_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet];
if (completionBlock) {
completionBlock(sections, indexSet);
}
});
}
- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock
{
if (indexSet.count == 0)
return;
[_editingNodes removeObjectsAtIndexes:indexSet];
[_editingNodes[kind] removeObjectsAtIndexes:indexSet];
ASDisplayNodePerformBlockOnMainThread(^{
[_completedNodes removeObjectsAtIndexes:indexSet];
[_completedNodes[kind] removeObjectsAtIndexes:indexSet];
if (completionBlock) {
completionBlock(indexSet);
}
});
}
#pragma mark - Internal Data Querying + Editing
/**
* Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes.
*
* @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy
* of the editing nodes. The delegate is invoked on the main thread.
*/
- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}
/**
* Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed.
*
* @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread.
* Once the backing stores are consistent, the delegate is invoked on the main thread.
*/
- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}
/**
* Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate.
*
* @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted
* in the completed store on the main thread. The delegate is invoked on the main thread.
*/
- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) {
if (_delegateDidInsertSections)
[_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
}];
}
/**
* Removes sections at the given indicies from the backing store and notifies the delegate.
*
* @discussion Section array are first removed from the editing store, then the associated section in the completed
* store is removed on the main thread. The delegate is invoked on the main thread.
*/
- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) {
if (_delegateDidDeleteSections)
[_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
});
}];
}
#pragma mark - Initial Load & Full Reload (External API)
@@ -239,7 +335,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
ASDisplayNodeAssertMainThread();
[self accessDataSourceWithBlock:^{
NSMutableArray *indexPaths = [NSMutableArray array];
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self];
// insert sections
[self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0];
@@ -266,24 +362,30 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{
NSUInteger sectionCount = [_dataSource dataControllerNumberOfSections:self];
NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self];
NSMutableArray *updatedNodes = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths];
[self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths];
// Allow subclasses to perform setup before going into the edit transaction
[self prepareForReloadData];
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - reloadData");
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _editingNodes.count)];
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
[self willReloadData];
// Insert each section
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
@@ -304,6 +406,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Data Source Access (Calling _dataSource)
/**
* Safely locks access to the data source and executes the given block, unlocking once complete.
*
* @discussion When `asyncDataFetching` is enabled, the block is executed on a background thread.
*/
- (void)accessDataSourceWithBlock:(dispatch_block_t)block
{
if (_asyncDataFetchingEnabled) {
@@ -319,6 +426,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}
}
/**
* Fetches row nodes and their specified index paths for the provided sections from the data source.
*
* @discussion Results are stored in the passed mutable arrays.
*/
- (void)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet mutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths
{
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
@@ -333,9 +445,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}];
}
/**
* Fetches row nodes and their specified index paths for all sections from the data source.
*
* @discussion Results are stored in the passed mutable arrays.
*/
- (void)_populateFromEntireDataSourceWithMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths
{
NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self];
NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self];
for (NSUInteger i = 0; i < sectionNum; i++) {
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i];
@@ -375,7 +492,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
ASDisplayNodePerformBlockOnMainThread(^{
// Deep copy _completedNodes to _externalCompletedNodes.
// Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate.
_externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes);
_externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]);
LOG(@"endUpdatesWithCompletion - begin updates call to delegate");
[_delegate dataControllerBeginUpdates:self];
@@ -403,6 +520,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}
}
/**
* Queues the given operation until an `endUpdates` synchronize update is completed.
*
* If this method is called outside of a begin/endUpdates batch update, the block is
* executed immediately.
*/
- (void)performEditCommandWithBlock:(void (^)(void))block
{
// This method needs to block the thread and synchronously perform the operation if we are not
@@ -416,49 +539,55 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Section Editing (External API)
- (void)insertSections:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - insertSections: %@", indexSet);
LOG(@"Edit Command - insertSections: %@", sections);
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{
NSMutableArray *updatedNodes = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromDataSourceWithSectionIndexSet:indexSet mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths];
[self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths];
[self prepareForInsertSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - insertSections: %@", indexSet);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count];
for (NSUInteger i = 0; i < indexSet.count; i++) {
[self willInsertSections:sections];
LOG(@"Edit Transaction - insertSections: %@", sections);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self _insertSections:sectionArray atIndexSet:indexSet withAnimationOptions:animationOptions];
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
[self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
}];
}];
}];
}
- (void)deleteSections:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - deleteSections: %@", indexSet);
LOG(@"Edit Command - deleteSections: %@", sections);
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[_editingTransactionQueue addOperationWithBlock:^{
[self willDeleteSections:sections];
// remove elements
LOG(@"Edit Transaction - deleteSections: %@", indexSet);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, indexSet);
LOG(@"Edit Transaction - deleteSections: %@", sections);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}];
}
@@ -481,12 +610,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// at this time. Thus _editingNodes could be empty and crash in ASIndexPathsForMultidimensional[...]
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths];
[self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths];
[self prepareForReloadSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, sections);
[self willReloadSections:sections];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes));
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]));
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
@@ -506,12 +639,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
[_editingTransactionQueue addOperationWithBlock:^{
[self willMoveSection:section toSection:newSection];
// remove elements
LOG(@"Edit Transaction - moveSection");
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// update the section of indexpaths
@@ -527,6 +662,49 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}];
}
#pragma mark - Backing store manipulation optional hooks (Subclass API)
- (void)prepareForReloadData
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willReloadData
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)prepareForInsertSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willInsertSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willDeleteSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)prepareForReloadSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willReloadSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
#pragma mark - Row Editing (External API)
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@@ -546,7 +724,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:nodes atIndexPaths:indexPaths];
[self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths];
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
@@ -596,7 +774,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}
// Measure nodes whose views are loaded before we leave the main thread
[self _layoutNodesWithMainThreadAffinity:nodes atIndexPaths:indexPaths];
[self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths];
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
@@ -607,41 +785,46 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}];
}
- (void)relayoutAllRows
- (void)relayoutAllNodes
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - relayoutRows");
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
void (^relayoutNodesBlock)(NSMutableArray *) = ^void(NSMutableArray *nodes) {
if (!nodes.count) {
return;
}
[self accessDataSourceWithBlock:^{
[nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
ASSizeRange constrainedSize = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
}];
}];
}];
};
// Can't relayout right away because _completedNodes may not be up-to-date,
// i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes
// (see _layoutNodes:atIndexPaths:withAnimationOptions:).
[_editingTransactionQueue addOperationWithBlock:^{
ASDisplayNodePerformBlockOnMainThread(^{
relayoutNodesBlock(_completedNodes);
for (NSString *kind in [_completedNodes keyEnumerator]) {
[self _relayoutNodesOfKind:kind];
}
});
}];
}];
}
- (void)_relayoutNodesOfKind:(NSString *)kind
{
ASDisplayNodeAssertMainThread();
NSArray *nodes = [self completedNodesOfKind:kind];
if (!nodes.count) {
return;
}
[self accessDataSourceWithBlock:^{
[nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
[node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
}];
}];
}];
}
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
@@ -651,7 +834,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, [NSArray arrayWithObject:indexPath]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]);
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
@@ -662,6 +845,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}];
}
#pragma mark - Data Querying (Subclass API)
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind
{
return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : [NSArray array];
}
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind
{
return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array];
}
- (NSMutableArray *)completedNodesOfKind:(NSString *)kind
{
return _completedNodes[kind];
}
#pragma mark - Data Querying (External API)
- (NSUInteger)numberOfSections
@@ -711,7 +911,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (NSArray *)completedNodes
{
ASDisplayNodeAssertMainThread();
return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes;
return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind];
}
#pragma mark - Dealloc
@@ -719,15 +919,17 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)dealloc
{
ASDisplayNodeAssertMainThread();
[_completedNodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
if (node.isNodeLoaded) {
if (node.layerBacked) {
[node.layer removeFromSuperlayer];
} else {
[node.view removeFromSuperview];
[_completedNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
[nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
if (node.isNodeLoaded) {
if (node.layerBacked) {
[node.layer removeFromSuperlayer];
} else {
[node.view removeFromSuperview];
}
}
}
}];
}];
}];
}

View File

@@ -41,7 +41,7 @@
*
* @param contentView UIView to add a (sized) node's view to.
*
* @param node The ASCellNode to be added.
* @param cellNode The cell node to be added.
*/
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node;

View File

@@ -48,7 +48,7 @@
#pragma mark - View manipulation
- (void)moveNode:(ASCellNode *)node toView:(UIView *)view
- (void)moveCellNode:(ASCellNode *)node toView:(UIView *)view
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(node, @"Cannot move a nil node to a view");
@@ -75,7 +75,7 @@
// coalesce these events -- handling them multiple times per runloop is noisy and expensive
_queuedRangeUpdate = YES;
[self performSelector:@selector(updateVisibleNodeIndexPaths)
withObject:nil
afterDelay:0
@@ -158,9 +158,9 @@
return rangeType == ASLayoutRangeTypeRender;
}
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)cellNode
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node
{
if (cellNode.view.superview == contentView) {
if (node.view.superview == contentView) {
// this content view is already correctly configured
return;
}
@@ -170,7 +170,7 @@
[view removeFromSuperview];
}
[self moveNode:cellNode toView:contentView];
[self moveCellNode:node toView:contentView];
}

View File

@@ -0,0 +1,368 @@
/* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <XCTest/XCTest.h>
#import <UIKit/UIKit.h>
#import "ASCollectionView.h"
#import "ASCollectionViewFlowLayoutInspector.h"
/**
* Test Data Source
*/
@interface InspectorTestDataSource : NSObject <ASCollectionViewDataSource>
@end
@implementation InspectorTestDataSource
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [[ASCellNode alloc] init];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 0;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 2;
}
@end
@interface ASCollectionViewFlowLayoutInspectorTests : XCTestCase
@end
/**
* Test Delegate for Header Reference Size Implementation
*/
@interface HeaderReferenceSizeTestDelegate : NSObject <ASCollectionViewDelegateFlowLayout>
@end
@implementation HeaderReferenceSizeTestDelegate
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return CGSizeMake(125.0, 125.0);
}
@end
/**
* Test Delegate for Footer Reference Size Implementation
*/
@interface FooterReferenceSizeTestDelegate : NSObject <ASCollectionViewDelegateFlowLayout>
@end
@implementation FooterReferenceSizeTestDelegate
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
{
return CGSizeMake(125.0, 125.0);
}
@end
@implementation ASCollectionViewFlowLayoutInspectorTests
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
#pragma mark - #collectionView:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:
// Vertical
// Delegate implementation
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderDelegateImplementation
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterDelegateImplementation
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
FooterReferenceSizeTestDelegate *delegate = [[FooterReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
// Size implementation
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderProperty
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.headerReferenceSize = CGSizeMake(125.0, 125.0);
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterProperty
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(collectionView.bounds.size.width, 125.0));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
// Horizontal
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderDelegateImplementation
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.height));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterDelegateImplementation
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
FooterReferenceSizeTestDelegate *delegate = [[FooterReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.height));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the values returned in the delegate implementation");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
// Size implementation
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderProperty
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.headerReferenceSize = CGSizeMake(125.0, 125.0);
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.width));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterProperty
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeMake(125.0, collectionView.bounds.size.height));
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a size constrained by the size set on the layout");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsZeroSizeWhenNoReferenceSizeIsImplemented
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeZero);
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a zero size");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
#pragma mark - #collectionView:numberOfSectionsForSupplementaryNodeOfKind:
- (void)testThatItRespondsWithTheDefaultNumberOfSections
{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
XCTAssert(sections == 1, @"should return 1 by default");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItProvidesTheNumberOfSectionsInTheDataSource
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
XCTAssert(sections == 2, @"should return 2");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
#pragma mark - #collectionView:supplementaryNodesOfKind:inSection:
- (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheDelegate
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionHeader inSection:0];
XCTAssert(count == 1, @"should have a header supplementary view");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheLayout
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0];
XCTAssert(count == 1, @"should have a footer supplementary view");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
- (void)testThatItReturnsNoneWhenNoReferenceSizeIsImplemented
{
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:NO];
collectionView.asyncDataSource = dataSource;
collectionView.asyncDelegate = delegate;
ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout];
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0];
XCTAssert(count == 0, @"should not have a footer supplementary view");
collectionView.asyncDataSource = nil;
collectionView.asyncDelegate = nil;
}
@end

View File

@@ -6,7 +6,9 @@
//
#import <XCTest/XCTest.h>
#import <AsyncDisplayKit/ASCollectionView.h>
#import "ASCollectionView.h"
#import "ASCollectionDataController.h"
#import "ASCollectionViewFlowLayoutInspector.h"
@interface ASCollectionViewTestDelegate : NSObject <ASCollectionViewDataSource, ASCollectionViewDelegate>
@@ -73,13 +75,43 @@
@end
@interface ASCollectionView (InternalTesting)
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController;
@end
@interface ASCollectionViewTests : XCTestCase
@end
@implementation ASCollectionViewTests
- (void)DISABLED_testCollectionViewController {
- (void)testThatItSetsALayoutInspectorForFlowLayouts
{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
XCTAssert(collectionView.layoutDelegate != nil, @"should automatically set a layout delegate for flow layouts");
XCTAssert([collectionView.layoutDelegate isKindOfClass:[ASCollectionViewFlowLayoutInspector class]], @"should have a flow layout inspector by default");
}
- (void)testThatItDoesNotSetALayoutInspectorForCustomLayouts
{
UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
XCTAssert(collectionView.layoutDelegate == nil, @"should not set a layout delegate for custom layouts");
}
- (void)testThatRegisteringASupplementaryNodeStoresItForIntrospection
{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
[collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
XCTAssertEqualObjects([collectionView supplementaryNodeKindsInDataController:nil], @[UICollectionElementKindSectionHeader]);
}
- (void)DISABLED_testCollectionViewController
{
ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil];
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];

Binary file not shown.

Binary file not shown.

View File

@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */; settings = {ASSET_TAGS = (); }; };
9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; settings = {ASSET_TAGS = (); }; };
AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; };
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; };
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; };
@@ -16,6 +18,9 @@
/* Begin PBXFileReference section */
2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SupplementaryNode.h; sourceTree = "<group>"; };
9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SupplementaryNode.m; sourceTree = "<group>"; };
9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = "<group>"; };
AC3C4A5E1A11F47200143C57 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
AC3C4A621A11F47200143C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AC3C4A631A11F47200143C57 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@@ -76,6 +81,8 @@
AC3C4A691A11F47200143C57 /* ViewController.m */,
AC3C4A8D1A11F80C00143C57 /* Images.xcassets */,
AC3C4A611A11F47200143C57 /* Supporting Files */,
9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */,
9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */,
);
indentWidth = 2;
path = Sample;
@@ -88,6 +95,7 @@
children = (
AC3C4A621A11F47200143C57 /* Info.plist */,
AC3C4A631A11F47200143C57 /* main.m */,
9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */,
);
name = "Supporting Files";
sourceTree = "<group>";
@@ -159,6 +167,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */,
AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -204,6 +213,7 @@
buildActionMask = 2147483647;
files = (
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */,
9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */,
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */,
AC3C4A641A11F47200143C57 /* main.m in Sources */,
);

View File

@@ -26,6 +26,8 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>Launchboard</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
</dependencies>
<scenes/>
</document>

View File

@@ -0,0 +1,18 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface SupplementaryNode : ASCellNode
- (instancetype)initWithText:(NSString *)text;
@end

View File

@@ -0,0 +1,55 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "SupplementaryNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
static CGFloat kInsets = 15.0;
@implementation SupplementaryNode {
ASTextNode *_textNode;
}
- (instancetype)initWithText:(NSString *)text
{
self = [super init];
if (self != nil) {
_textNode = [[ASTextNode alloc] init];
_textNode.attributedString = [[NSAttributedString alloc] initWithString:text
attributes:[self textAttributes]];
[self addSubnode:_textNode];
}
return self;
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASCenterLayoutSpec *center = [[ASCenterLayoutSpec alloc] init];
center.centeringOptions = ASCenterLayoutSpecCenteringXY;
center.child = _textNode;
UIEdgeInsets insets = UIEdgeInsetsMake(kInsets, kInsets, kInsets, kInsets);
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:center];
}
#pragma mark - Text Formatting
- (NSDictionary *)textAttributes
{
return @{
NSFontAttributeName: [UIFont systemFontOfSize:18.0],
NSForegroundColorAttributeName: [UIColor whiteColor],
};
}
@end

View File

@@ -12,8 +12,9 @@
#import "ViewController.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "SupplementaryNode.h"
@interface ViewController () <ASCollectionViewDataSource, ASCollectionViewDelegate>
@interface ViewController () <ASCollectionViewDataSource, ASCollectionViewDelegateFlowLayout>
{
ASCollectionView *_collectionView;
}
@@ -32,13 +33,17 @@
return nil;
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.headerReferenceSize = CGSizeMake(50.0, 50.0);
layout.footerReferenceSize = CGSizeMake(50.0, 50.0);
_collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:YES];
_collectionView.asyncDataSource = self;
_collectionView.asyncDelegate = self;
_collectionView.backgroundColor = [UIColor whiteColor];
[_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
[_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter];
return self;
}
@@ -73,9 +78,26 @@
return node;
}
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [kind isEqualToString:UICollectionElementKindSectionHeader] ? @"Header" : @"Footer";
SupplementaryNode *node = [[SupplementaryNode alloc] initWithText:text];
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
node.backgroundColor = [UIColor blueColor];
} else {
node.backgroundColor = [UIColor redColor];
}
return node;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 300;
return 10;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 100;
}
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView
@@ -95,7 +117,7 @@
[context completeBatchFetching:YES];
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0);
}