mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-02-16 15:50:37 +00:00
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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -26,3 +26,5 @@ docs/.sass-cache
|
||||
*.lock
|
||||
|
||||
*.gcov
|
||||
*.gcno
|
||||
*.gcda
|
||||
|
||||
BIN
AsyncDisplayKit-Prefix.gcda
Normal file
BIN
AsyncDisplayKit-Prefix.gcda
Normal file
Binary file not shown.
@@ -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 */,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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];
|
||||
|
||||
39
AsyncDisplayKit/Details/ASCollectionDataController.h
Normal file
39
AsyncDisplayKit/Details/ASCollectionDataController.h
Normal 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
|
||||
217
AsyncDisplayKit/Details/ASCollectionDataController.mm
Normal file
217
AsyncDisplayKit/Details/ASCollectionDataController.mm
Normal 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
|
||||
@@ -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
|
||||
121
AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m
Normal file
121
AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
159
AsyncDisplayKit/Details/ASDataController+Subclasses.h
Normal file
159
AsyncDisplayKit/Details/ASDataController+Subclasses.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
|
||||
368
AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m
Normal file
368
AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m
Normal 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
|
||||
@@ -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)];
|
||||
|
||||
BIN
ObjectiveC.gcno
BIN
ObjectiveC.gcno
Binary file not shown.
BIN
QuartzCore.gcno
BIN
QuartzCore.gcno
Binary file not shown.
@@ -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 */,
|
||||
);
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>Launchboard</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
|
||||
7
examples/ASCollectionView/Sample/Launchboard.storyboard
Normal file
7
examples/ASCollectionView/Sample/Launchboard.storyboard
Normal 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>
|
||||
18
examples/ASCollectionView/Sample/SupplementaryNode.h
Normal file
18
examples/ASCollectionView/Sample/SupplementaryNode.h
Normal 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
|
||||
55
examples/ASCollectionView/Sample/SupplementaryNode.m
Normal file
55
examples/ASCollectionView/Sample/SupplementaryNode.m
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user