diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index dae88335b8..deb14f55f2 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 b541012b26..91777e25df 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 14dc7109a7..bd28ccac23 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 72784cbcc0..a937d6388f 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 7dfb8ccc14..0000000000 --- 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 44f51e47ea..d443a206ff 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 6a0f4c63d9..970503c00d 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 73b1d373a4..722d8def0d 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 0000000000..8ca69a4239 --- /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 0000000000..9ef4a66281 --- /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 404e55d0a0..82649f7363 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 7396de226d..633fd0fc67 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 5d070ebcd6..e9e59f6b35 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