From f71eba77afe8565efab111a7bc8575275dac6c03 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 4 Feb 2017 20:38:45 -0800 Subject: [PATCH] [ASCollectionView] Finish support for interoperability with base-class UICollectionViewCells. This also supports supplementary nodes. It builds off of Adlai's .interop flag but makes necessary improvements for all of the delegate methods to work in practice with heterogenous cell types. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 - AsyncDisplayKit/ASCellNode+Internal.h | 4 +- AsyncDisplayKit/ASCollectionNode.h | 35 +++++ AsyncDisplayKit/ASCollectionView.mm | 146 +++++++++++------- .../Private/ASCollectionInteropProtocols.h | 35 ----- .../Sample.xcodeproj/project.pbxproj | 28 ++-- .../Sample/ImageCellNode.h | 1 + .../Sample/ImageCellNode.m | 10 ++ .../Sample/ImageCollectionViewCell.h | 13 ++ .../Sample/ImageCollectionViewCell.m | 46 ++++++ .../Sample/MosaicCollectionViewLayout.h | 7 +- .../Sample/MosaicCollectionViewLayout.m | 41 +---- .../Sample/ViewController.m | 61 +++++++- 13 files changed, 281 insertions(+), 150 deletions(-) delete mode 100644 AsyncDisplayKit/Private/ASCollectionInteropProtocols.h create mode 100644 examples/CustomCollectionView/Sample/ImageCollectionViewCell.h create mode 100644 examples/CustomCollectionView/Sample/ImageCollectionViewCell.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index dae88335..deb14f55 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -311,7 +311,6 @@ CC0F885C1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = CC0F885A1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC0F885F1E4280B800576FED /* _ASCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0F885D1E4280B800576FED /* _ASCollectionViewCell.m */; }; CC0F88601E4280B800576FED /* _ASCollectionViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */; settings = {ATTRIBUTES = (Private, ); }; }; - CC0F88611E4280C900576FED /* ASCollectionInteropProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = CC6363E11E32C00800D8A8DE /* ASCollectionInteropProtocols.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC0F88621E4281E200576FED /* ASSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC0F88631E4281E700576FED /* ASSupplementaryNodeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC0F886B1E4286FA00576FED /* ReferenceImages_32 in Resources */ = {isa = PBXBuildFile; fileRef = CC0F88681E4286FA00576FED /* ReferenceImages_32 */; }; @@ -721,7 +720,6 @@ CC54A81D1D7008B300296A24 /* ASDispatchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASDispatchTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; CC57EAF91E394EA40034C595 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBlockTypes.h; sourceTree = ""; }; - CC6363E11E32C00800D8A8DE /* ASCollectionInteropProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionInteropProtocols.h; sourceTree = ""; }; CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = ""; }; CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = ""; }; CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = ""; }; @@ -1340,7 +1338,6 @@ CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */ = { isa = PBXGroup; children = ( - CC6363E11E32C00800D8A8DE /* ASCollectionInteropProtocols.h */, CCBD05DF1E4147B000D18509 /* ASIGListAdapterBasedDataSource.h */, CCBD05DE1E4147B000D18509 /* ASIGListAdapterBasedDataSource.m */, ); @@ -1416,7 +1413,6 @@ A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */, 34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */, B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */, - CC0F88611E4280C900576FED /* ASCollectionInteropProtocols.h in Headers */, B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */, B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */, B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h index b541012b..91777e25 100644 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -63,7 +63,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy, nullable) NSIndexPath *cachedIndexPath; -@property (weak, nonatomic, nullable) ASDisplayNode *owningNode; +@property (nonatomic, weak, nullable) ASDisplayNode *owningNode; + +@property (nonatomic, assign) BOOL shouldUseUIKitCell; @end diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index 14dc7109..bd28ccac 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -706,4 +706,39 @@ NS_ASSUME_NONNULL_BEGIN @end +@protocol ASCollectionDataSourceInterop + +/** + * This method offers compatibility with synchronous, standard UICollectionViewCell objects. + * These cells will **not** have the performance benefits of ASCellNodes (like preloading, async layout, and + * async drawing) - even when mixed within the same ASCollectionNode. + * + * In order to use this method, you must: + * 1. Implement it on your ASCollectionDataSource object. + * 2. Call registerCellClass: on the collectionNode.view (in viewDidLoad, or register an onDidLoad: block). + * 3. Return nil from the nodeBlockForItem...: or nodeForItem...: method. NOTE: it is an error to return + * nil from within a nodeBlock, if you have returned a nodeBlock object. + * 4. Lastly, you must implement a method to provide the size for the cell. There are two ways this is done: + * 4a. UICollectionViewFlowLayout (incl. ASPagerNode). Implement + collectionNode:constrainedSizeForItemAtIndexPath:. + * 4b. Custom collection layouts. Set .view.layoutInspector and have it implement + collectionView:constrainedSizeForNodeAtIndexPath:. + * + * For an example of using this method with all steps above (including a custom layout, 4b.), + * see the app in examples/CustomCollectionView and enable kShowUICollectionViewCells = YES. + */ +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + +@end + +@protocol ASCollectionDelegateInterop + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; + +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; + +@end + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 72784cbc..a937d638 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -28,7 +28,6 @@ #import #import #import -#import #import /** @@ -450,7 +449,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; if (asyncDelegate == nil) { _asyncDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDataSourceFlags = {}; + _asyncDelegateFlags = {}; } else { _asyncDelegate = asyncDelegate; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; @@ -857,49 +856,79 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; return [[self nodeForItemAtIndexPath:indexPath] calculatedSize]; } -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForHeaderInSection:(NSInteger)section { - return [self supplementaryNodeForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]].calculatedSize; + ASCellNode *cell = [self supplementaryNodeForElementKind:UICollectionElementKindSectionHeader + atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; + if (cell.shouldUseUIKitCell && _asyncDelegateFlags.interop) { + if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) { + return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForHeaderInSection:section]; + } + } + return cell.calculatedSize; } -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForFooterInSection:(NSInteger)section { - return [self supplementaryNodeForElementKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]].calculatedSize; + ASCellNode *cell = [self supplementaryNodeForElementKind:UICollectionElementKindSectionFooter + atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; + if (cell.shouldUseUIKitCell && _asyncDelegateFlags.interop) { + if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]) { + return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForFooterInSection:section]; + } + } + return cell.calculatedSize; } - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - UICollectionReusableView *view; - if (_asyncDataSource && _asyncDataSourceFlags.interop) { + UICollectionReusableView *view = nil; + ASCellNode *node = [_dataController supplementaryNodeOfKind:kind atIndexPath:indexPath]; + + if (node.shouldUseUIKitCell && _asyncDataSourceFlags.interop) { view = [(id)_asyncDataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; } else { + ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self); + if ([_registeredSupplementaryKinds containsObject:kind] == NO) { + [self registerSupplementaryNodeOfKind:kind]; + } view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; + if (node) { + [_rangeController configureContentView:view forCellNode:node]; + } } - - ASCellNode *node = [_dataController supplementaryNodeOfKind:kind atIndexPath:indexPath]; - ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self); - [_rangeController configureContentView:view forCellNode:node]; + return view; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - _ASCollectionViewCell *cell; - if (_asyncDataSource && _asyncDataSourceFlags.interop) { + UICollectionViewCell *cell = nil; + ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; + + if (node.shouldUseUIKitCell && _asyncDataSourceFlags.interop) { cell = [(id)_asyncDataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; } else { + ASDisplayNodeAssert(node != nil, @"Cell node should exist. indexPath = %@, collectionDataSource = %@", indexPath, self); cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; + [(_ASCollectionViewCell *)cell setNode:node]; + [_rangeController configureContentView:cell.contentView forCellNode:node]; } - - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - cell.node = node; - [_rangeController configureContentView:cell.contentView forCellNode:node]; - return cell; } - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + // Since _ASCollectionViewCell is not available for subclassing, this is faster than isKindOfClass: + // We must exit early here, because only _ASCollectionViewCell implements the -node accessor method. + if ([cell class] != [_ASCollectionViewCell class]) { + if (_asyncDelegateFlags.interop) { + [(id )_asyncDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; + } + [_rangeController setNeedsUpdate]; + return; + } + ASCellNode *cellNode = [cell node]; cellNode.scrollView = collectionView; @@ -914,12 +943,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath); - if (_asyncDelegateFlags.interop) { - [(id)_asyncDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; - } else if (_asyncDelegateFlags.collectionNodeWillDisplayItem) { - if (ASCollectionNode *collectionNode = self.collectionNode) { - [_asyncDelegate collectionNode:collectionNode willDisplayItemWithNode:cellNode]; - } + if (_asyncDelegateFlags.collectionNodeWillDisplayItem && self.collectionNode != nil) { + [_asyncDelegate collectionNode:self.collectionNode willDisplayItemWithNode:cellNode]; } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -938,12 +963,20 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + // Since _ASCollectionViewCell is not available for subclassing, this is faster than isKindOfClass: + // We must exit early here, because only _ASCollectionViewCell implements the -node accessor method. + if ([cell class] != [_ASCollectionViewCell class]) { + if (_asyncDelegateFlags.interop) { + [(id )_asyncDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; + } + [_rangeController setNeedsUpdate]; + return; + } + ASCellNode *cellNode = [cell node]; ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); - if (_asyncDelegateFlags.interop) { - [(id)_asyncDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; - } else if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) { + if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) { if (ASCollectionNode *collectionNode = self.collectionNode) { [_asyncDelegate collectionNode:collectionNode didEndDisplayingItemWithNode:cellNode]; } @@ -1369,44 +1402,47 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; #pragma mark - ASDataControllerSource -- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { +- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath +{ ASCellNodeBlock block = nil; + ASCellNode *cell = nil; if (_asyncDataSourceFlags.collectionNodeNodeBlockForItem) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); block = [_asyncDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath]; } else if (_asyncDataSourceFlags.collectionNodeNodeForItem) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - ASCellNode *node = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath]; - if ([node isKindOfClass:[ASCellNode class]]) { - block = ^{ - return node; - }; - } else { - ASDisplayNodeFailAssert(@"Data source returned invalid node from tableNode:nodeForRowAtIndexPath:. Node: %@", node); - } - } else if (_asyncDataSourceFlags.collectionViewNodeBlockForItem) { + cell = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" + } else if (_asyncDataSourceFlags.collectionViewNodeBlockForItem) { block = [_asyncDataSource collectionView:self nodeBlockForItemAtIndexPath:indexPath]; } else if (_asyncDataSourceFlags.collectionViewNodeForItem) { - ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; + cell = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; + } #pragma clang diagnostic pop - if ([node isKindOfClass:[ASCellNode class]]) { - block = ^{ - return node; - }; - } else { - ASDisplayNodeFailAssert(@"Data source returned invalid node from tableView:nodeForRowAtIndexPath:. Node: %@", node); - } + + // Handle nil node block or cell + if (cell && [cell isKindOfClass:[ASCellNode class]]) { + block = ^{ + return cell; + }; } - // Handle nil node block if (block == nil) { - ASDisplayNodeFailAssert(@"ASTableNode could not get a node block for row at index path %@", indexPath); - block = ^{ - return [[ASCellNode alloc] init]; - }; + if (_asyncDataSourceFlags.interop) { + block = ^{ + ASCellNode *cell = [[ASCellNode alloc] init]; + cell.shouldUseUIKitCell = YES; + cell.style.preferredSize = CGSizeZero; + return cell; + }; + } else { + ASDisplayNodeFailAssert(@"ASCollection could not get a node block for row at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the protocol!", indexPath, cell, block); + block = ^{ + return [[ASCellNode alloc] init]; + }; + } } // Wrap the node block @@ -1419,7 +1455,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; node.interactionDelegate = strongSelf; } if (_inverted) { - node.transform = CATransform3DMakeScale(1, -1, 1) ; + node.transform = CATransform3DMakeScale(1, -1, 1) ; } return node; }; @@ -1479,6 +1515,12 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; node = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; #pragma clang diagnostic pop } + + if (node == nil && _asyncDataSourceFlags.interop) { + node = [[ASCellNode alloc] init]; + node.shouldUseUIKitCell = YES; + } + ASDisplayNodeAssert(node != nil, @"A node must be returned for supplementary element of kind '%@' at index path '%@'", kind, indexPath); return node; } diff --git a/AsyncDisplayKit/Private/ASCollectionInteropProtocols.h b/AsyncDisplayKit/Private/ASCollectionInteropProtocols.h deleted file mode 100644 index 7dfb8ccc..00000000 --- a/AsyncDisplayKit/Private/ASCollectionInteropProtocols.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// ASCollectionDataSourceInterop.h -// AsyncDisplayKit -// -// Created by Adlai Holler on 1/20/17. -// Copyright © 2017 Facebook. All rights reserved. -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Protocols that allow the data source/delegate extra hooks, - * to facilitate interop e.g. with IGListKit. - */ - -@protocol ASCollectionDataSourceInterop - -- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -@end - -@protocol ASCollectionDelegateInterop - -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; - -@end - -NS_ASSUME_NONNULL_END diff --git a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj index 44f51e47..d443a206 100644 --- a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */; }; 25A1FA881C02FCB000193875 /* ImageCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA871C02FCB000193875 /* ImageCellNode.m */; }; 576F970133B34DFD583D5CE4 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CC0FB9EE0030992E8FBC0A0 /* libPods-Sample.a */; }; + 80364CCA1E3D95A90094400C /* ImageCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 80364CC91E3D95A90094400C /* ImageCollectionViewCell.m */; }; 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; }; AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; }; AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; }; @@ -23,6 +24,8 @@ 25A1FA861C02FCB000193875 /* ImageCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageCellNode.h; sourceTree = ""; }; 25A1FA871C02FCB000193875 /* ImageCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageCellNode.m; sourceTree = ""; }; 4CC0FB9EE0030992E8FBC0A0 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 80364CC81E3D95A90094400C /* ImageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageCollectionViewCell.h; sourceTree = ""; }; + 80364CC91E3D95A90094400C /* ImageCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageCollectionViewCell.m; sourceTree = ""; }; 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = ""; }; 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 = ""; }; @@ -84,6 +87,8 @@ AC3C4A661A11F47200143C57 /* AppDelegate.m */, AC3C4A681A11F47200143C57 /* ViewController.h */, AC3C4A691A11F47200143C57 /* ViewController.m */, + 80364CC81E3D95A90094400C /* ImageCollectionViewCell.h */, + 80364CC91E3D95A90094400C /* ImageCollectionViewCell.m */, 25A1FA861C02FCB000193875 /* ImageCellNode.h */, 25A1FA871C02FCB000193875 /* ImageCellNode.m */, AC3C4A8D1A11F80C00143C57 /* Images.xcassets */, @@ -120,12 +125,12 @@ isa = PBXNativeTarget; buildConfigurationList = AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */, + F868CFBB21824CC9521B6588 /* [CP] Check Pods Manifest.lock */, AC3C4A5A1A11F47200143C57 /* Sources */, AC3C4A5B1A11F47200143C57 /* Frameworks */, AC3C4A5C1A11F47200143C57 /* Resources */, - A6902C454C7661D0D277AC62 /* Copy Pods Resources */, - 3760AAE3843D6EA89A9A166B /* Embed Pods Frameworks */, + A6902C454C7661D0D277AC62 /* [CP] Copy Pods Resources */, + 3760AAE3843D6EA89A9A166B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -181,14 +186,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3760AAE3843D6EA89A9A166B /* Embed Pods Frameworks */ = { + 3760AAE3843D6EA89A9A166B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -196,14 +201,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - A6902C454C7661D0D277AC62 /* Copy Pods Resources */ = { + A6902C454C7661D0D277AC62 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -211,19 +216,19 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; showEnvVarsInLog = 0; }; - F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = { + F868CFBB21824CC9521B6588 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -237,6 +242,7 @@ AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, AC3C4A641A11F47200143C57 /* main.m in Sources */, + 80364CCA1E3D95A90094400C /* ImageCollectionViewCell.m in Sources */, 25A1FA881C02FCB000193875 /* ImageCellNode.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -328,6 +334,7 @@ buildSettings = { ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = 1; @@ -340,6 +347,7 @@ buildSettings = { ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = 1; diff --git a/examples/CustomCollectionView/Sample/ImageCellNode.h b/examples/CustomCollectionView/Sample/ImageCellNode.h index 6a0f4c63..970503c0 100644 --- a/examples/CustomCollectionView/Sample/ImageCellNode.h +++ b/examples/CustomCollectionView/Sample/ImageCellNode.h @@ -22,5 +22,6 @@ @interface ImageCellNode : ASCellNode - (instancetype)initWithImage:(UIImage *)image; +@property (nonatomic, strong) UIImage *image; @end diff --git a/examples/CustomCollectionView/Sample/ImageCellNode.m b/examples/CustomCollectionView/Sample/ImageCellNode.m index 73b1d373..722d8def 100644 --- a/examples/CustomCollectionView/Sample/ImageCellNode.m +++ b/examples/CustomCollectionView/Sample/ImageCellNode.m @@ -39,4 +39,14 @@ return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:_imageNode]; } +- (void)setImage:(UIImage *)image +{ + _imageNode.image = image; +} + +- (UIImage *)image +{ + return _imageNode.image; +} + @end diff --git a/examples/CustomCollectionView/Sample/ImageCollectionViewCell.h b/examples/CustomCollectionView/Sample/ImageCollectionViewCell.h new file mode 100644 index 00000000..8ca69a42 --- /dev/null +++ b/examples/CustomCollectionView/Sample/ImageCollectionViewCell.h @@ -0,0 +1,13 @@ +// +// ImageCollectionViewCell.h +// Sample +// +// Created by Hannah Troisi on 1/28/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +@interface ImageCollectionViewCell : UICollectionViewCell + +@end diff --git a/examples/CustomCollectionView/Sample/ImageCollectionViewCell.m b/examples/CustomCollectionView/Sample/ImageCollectionViewCell.m new file mode 100644 index 00000000..9ef4a662 --- /dev/null +++ b/examples/CustomCollectionView/Sample/ImageCollectionViewCell.m @@ -0,0 +1,46 @@ +// +// ImageCollectionViewCell.m +// Sample +// +// Created by Hannah Troisi on 1/28/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ImageCollectionViewCell.h" + +@implementation ImageCollectionViewCell +{ + UILabel *_title; + UILabel *_description; +} + +- (id)initWithFrame:(CGRect)aRect +{ + self = [super initWithFrame:aRect]; + if (self) { + _title = [[UILabel alloc] init]; + _title.text = @"UICollectionViewCell"; + [self.contentView addSubview:_title]; + + _description = [[UILabel alloc] init]; + _description.text = @"description for cell"; + [self.contentView addSubview:_description]; + + self.contentView.backgroundColor = [UIColor orangeColor]; + } + return self; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + [_title sizeToFit]; + [_description sizeToFit]; + + CGRect frame = _title.frame; + frame.origin.y = _title.frame.size.height; + _description.frame = frame; +} + +@end diff --git a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.h b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.h index 404e55d0..82649f73 100644 --- a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.h +++ b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.h @@ -28,6 +28,9 @@ @property (assign, nonatomic) UIEdgeInsets interItemSpacing; @property (assign, nonatomic) CGFloat headerHeight; +- (CGSize)itemSizeAtIndexPath:(NSIndexPath *)indexPath; +- (CGSize)headerSizeForSection:(NSInteger)section; + @end @protocol MosaicCollectionViewLayoutDelegate @@ -35,7 +38,3 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(MosaicCollectionViewLayout *)layout originalItemSizeAtIndexPath:(NSIndexPath *)indexPath; @end - -@interface MosaicCollectionViewLayoutInspector : NSObject - -@end diff --git a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m index 7396de22..633fd0fc 100644 --- a/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m +++ b/examples/CustomCollectionView/Sample/MosaicCollectionViewLayout.m @@ -54,7 +54,7 @@ top += _sectionInset.top; if (_headerHeight > 0) { - CGSize headerSize = [self _headerSizeForSection:section]; + CGSize headerSize = [self headerSizeForSection:section]; UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; @@ -75,7 +75,7 @@ NSUInteger columnIndex = [self _shortestColumnIndexInSection:section]; NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:section]; - CGSize itemSize = [self _itemSizeAtIndexPath:indexPath]; + CGSize itemSize = [self itemSizeAtIndexPath:indexPath]; CGFloat xOffset = _sectionInset.left + (columnWidth + _columnSpacing) * columnIndex; CGFloat yOffset = [_columnHeights[section][columnIndex] floatValue]; @@ -146,7 +146,7 @@ return ([self _widthForSection:section] - ((_numberOfColumns - 1) * _columnSpacing)) / _numberOfColumns; } -- (CGSize)_itemSizeAtIndexPath:(NSIndexPath *)indexPath +- (CGSize)itemSizeAtIndexPath:(NSIndexPath *)indexPath { CGSize size = CGSizeMake([self _columnWidthForSection:indexPath.section], 0); CGSize originalSize = [[self _delegate] collectionView:self.collectionView layout:self originalItemSizeAtIndexPath:indexPath]; @@ -156,7 +156,7 @@ return size; } -- (CGSize)_headerSizeForSection:(NSUInteger)section +- (CGSize)headerSizeForSection:(NSInteger)section { return CGSizeMake([self _widthForSection:section], _headerHeight); } @@ -199,36 +199,3 @@ } @end - -@implementation MosaicCollectionViewLayoutInspector - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - MosaicCollectionViewLayout *layout = (MosaicCollectionViewLayout *)[collectionView collectionViewLayout]; - return ASSizeRangeMake(CGSizeZero, [layout _itemSizeAtIndexPath:indexPath]); -} - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - MosaicCollectionViewLayout *layout = (MosaicCollectionViewLayout *)[collectionView collectionViewLayout]; - return ASSizeRangeMake(CGSizeZero, [layout _headerSizeForSection:indexPath.section]); -} - -- (ASScrollDirection)scrollableDirections -{ - return ASScrollDirectionVerticalDirections; -} - -/** - * 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 -{ - if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { - return 1; - } else { - return 0; - } -} - -@end diff --git a/examples/CustomCollectionView/Sample/ViewController.m b/examples/CustomCollectionView/Sample/ViewController.m index 5d070ebc..e9e59f6b 100644 --- a/examples/CustomCollectionView/Sample/ViewController.m +++ b/examples/CustomCollectionView/Sample/ViewController.m @@ -20,14 +20,17 @@ #import #import "MosaicCollectionViewLayout.h" #import "ImageCellNode.h" +#import "ImageCollectionViewCell.h" +// This option demonstrates that raw UIKit cells can still be used alongside native ASCellNodes. +static BOOL kShowUICollectionViewCells = YES; +static NSString *kReuseIdentifier = @"ImageCollectionViewCell"; static NSUInteger kNumberOfImages = 14; -@interface ViewController () +@interface ViewController () { NSMutableArray *_sections; ASCollectionNode *_collectionNode; - MosaicCollectionViewLayoutInspector *_layoutInspector; } @end @@ -35,7 +38,7 @@ static NSUInteger kNumberOfImages = 14; @implementation ViewController #pragma mark - -#pragma mark UIViewController. +#pragma mark UIViewController - (instancetype)init { @@ -48,8 +51,6 @@ static NSUInteger kNumberOfImages = 14; _collectionNode.delegate = self; _collectionNode.backgroundColor = [UIColor whiteColor]; - _layoutInspector = [[MosaicCollectionViewLayoutInspector alloc] init]; - if (!(self = [super initWithNode:_collectionNode])) return nil; @@ -73,7 +74,8 @@ static NSUInteger kNumberOfImages = 14; { [super viewDidLoad]; - _collectionNode.view.layoutInspector = _layoutInspector; + _collectionNode.view.layoutInspector = self; + [_collectionNode.view registerClass:[ImageCollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier]; } - (void)reloadTapped @@ -85,6 +87,11 @@ static NSUInteger kNumberOfImages = 14; - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { + if (kShowUICollectionViewCells && indexPath.item % 3 == 1) { + // When enabled, return nil for every third cell and then cellForItemAtIndexPath: will be called. + return nil; + } + UIImage *image = _sections[indexPath.section][indexPath.item]; return ^{ return [[ImageCellNode alloc] initWithImage:image]; @@ -92,6 +99,31 @@ static NSUInteger kNumberOfImages = 14; } +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +{ + MosaicCollectionViewLayout *layout = (MosaicCollectionViewLayout *)[collectionView collectionViewLayout]; + return ASSizeRangeMake(CGSizeZero, [layout itemSizeAtIndexPath:indexPath]); +} + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + MosaicCollectionViewLayout *layout = (MosaicCollectionViewLayout *)[collectionView collectionViewLayout]; + return ASSizeRangeMake(CGSizeZero, [layout headerSizeForSection:indexPath.section]); +} + +- (ASScrollDirection)scrollableDirections +{ + return ASScrollDirectionVerticalDirections; +} + +/** + * 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 +{ + return [kind isEqualToString:UICollectionElementKindSectionHeader] ? 1 : 0; +} + - (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { NSDictionary *textAttributes = @{ @@ -116,7 +148,22 @@ static NSUInteger kNumberOfImages = 14; - (CGSize)collectionView:(ASCollectionNode *)collectionNode layout:(UICollectionViewLayout *)collectionViewLayout originalItemSizeAtIndexPath:(NSIndexPath *)indexPath { - return [(UIImage *)_sections[indexPath.section][indexPath.item] size]; + ASCellNode *cellNode = [collectionNode nodeForItemAtIndexPath:indexPath]; + if ([cellNode isKindOfClass:[ImageCellNode class]]) { + return [[(ImageCellNode *)cellNode image] size]; + } else { + return CGSizeMake(100, 100); // In kShowUICollectionViewCells = YES mode, make those cells 100x100. + } +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath +{ + return [_collectionNode.view dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + return nil; } @end