diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 4e7bbcaed0..a800f93bbf 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |spec| spec.name = 'AsyncDisplayKit' - spec.version = '1.9.3' + spec.version = '1.9.4' spec.license = { :type => 'BSD' } spec.homepage = 'http://asyncdisplaykit.org' spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' } spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' - spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.3' } + spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.4' } spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/' diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index cb6e776a63..74d5e714bf 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -190,6 +190,10 @@ 258FF4281C0D152600A83844 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */; }; 25A977EF1C0D2A5500406B62 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */; }; 25BAA16F1C0D18D2002747C7 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */; }; + 25E327561C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 25E327571C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 25E327581C16819500A2170C /* ASPagerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25E327551C16819500A2170C /* ASPagerNode.m */; }; + 25E327591C16819500A2170C /* ASPagerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25E327551C16819500A2170C /* ASPagerNode.m */; }; 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 */; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; @@ -647,6 +651,8 @@ 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextNodeWordKerner.m; path = TextKit/ASTextNodeWordKerner.m; sourceTree = ""; }; 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerVisible.h; sourceTree = ""; }; 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerVisible.mm; sourceTree = ""; }; + 25E327541C16819500A2170C /* ASPagerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPagerNode.h; sourceTree = ""; }; + 25E327551C16819500A2170C /* ASPagerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPagerNode.m; sourceTree = ""; }; 2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = ""; }; 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = ""; }; 292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerPreload.h; sourceTree = ""; }; @@ -901,6 +907,8 @@ 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */, 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */, 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */, + 25E327541C16819500A2170C /* ASPagerNode.h */, + 25E327551C16819500A2170C /* ASPagerNode.m */, D785F6601A74327E00291744 /* ASScrollNode.h */, D785F6611A74327E00291744 /* ASScrollNode.m */, B0F880581BEAEC7500D17647 /* ASTableNode.h */, @@ -1255,6 +1263,7 @@ 058D0A83195D060300B7D73C /* ASBaseDefines.h in Headers */, 054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */, 2967F9E21AB0A5190072E4AB /* ASBasicImageDownloaderInternal.h in Headers */, + 25E327561C16819500A2170C /* ASPagerNode.h in Headers */, 299DA1A91A828D2900162D41 /* ASBatchContext.h in Headers */, 251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */, 044285071BAA63FE00D16268 /* ASBatchFetching.h in Headers */, @@ -1448,6 +1457,7 @@ B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, 254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, + 25E327571C16819500A2170C /* ASPagerNode.h in Headers */, B35062551B010EFD0018CF92 /* ASSentinel.h in Headers */, 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, @@ -1524,6 +1534,7 @@ 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, 527A806066E1F4E2795090DF /* Embed Pods Frameworks */, + 1B86F48711505F91D5FEF571 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1623,6 +1634,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1B86F48711505F91D5FEF571 /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 2E61B6A0DB0F436A9DDBE86F /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1713,6 +1739,7 @@ 058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */, 058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */, 058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */, + 25E327581C16819500A2170C /* ASPagerNode.m in Sources */, 058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */, 058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */, AEEC47E21C20C2DD00EC1693 /* ASVideoNode.mm in Sources */, @@ -1843,6 +1870,7 @@ B350621A1B010EFD0018CF92 /* ASDealloc2MainObject.m in Sources */, 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */, B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, + 25E327591C16819500A2170C /* ASPagerNode.m in Sources */, B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */, 254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */, B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */, diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 78821ef2d8..b89574983f 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -93,6 +93,18 @@ typedef NSUInteger ASCellNodeAnimation; */ - (void)setNeedsLayout; +/** + * @abstract Initializes a cell with a given viewControllerBlock. + * + * @param viewBlock The block that will be used to create the backing view. + * @param didLoadBlock The block that will be called after the view created by the viewBlock is loaded + * + * @return An ASCellNode created using the root view of the view controller provided by the viewControllerBlock. + * The view controller's root view is resized to match the calcuated size produced during layout. + * + */ +- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock; + @end diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index a4357479f9..f757f863ed 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -18,6 +18,15 @@ #pragma mark - #pragma mark ASCellNode +@interface ASCellNode () +{ + ASDisplayNodeDidLoadBlock _nodeLoadedBlock; + UIViewController *_viewController; + ASDisplayNode *_viewControllerNode; +} + +@end + @implementation ASCellNode - (instancetype)init @@ -32,6 +41,49 @@ return self; } +- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock +{ + if (!(self = [super init])) + return nil; + + ASDisplayNodeAssertNotNil(viewControllerBlock, @"should initialize with a valid block that returns a UIViewController"); + + if (viewControllerBlock) { + _viewController = viewControllerBlock(); + + __weak UIViewController *weakViewController = _viewController; + _viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ + return weakViewController.view; + } didLoadBlock:didLoadBlock]; + + [self addSubnode:_viewControllerNode]; + _nodeLoadedBlock = didLoadBlock; + } + + return self; +} + +//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +//{ +// _viewControllerNode.frame = (CGRect){{0,0}, constrainedSize.max}; +// NSLog(@"%f %f", constrainedSize.max.width, constrainedSize.max.height); +// return [super layoutSpecThatFits:constrainedSize]; +//} + +- (void)layout +{ + [super layout]; + + _viewControllerNode.frame = self.bounds; +} + +- (void)layoutDidFinish +{ + [super layoutDidFinish]; + + _viewControllerNode.frame = self.bounds; +} + - (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock { ASDisplayNodeAssertNotSupported(); @@ -98,7 +150,8 @@ #pragma mark - #pragma mark ASTextCellNode -@interface ASTextCellNode () { +@interface ASTextCellNode () +{ NSString *_text; ASTextNode *_textNode; } diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index c0489b2297..4f860cb646 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -6,7 +6,7 @@ // Copyright (c) 2015 Facebook. All rights reserved. // -#import +#import /** * ASCollectionNode is a node based class that wraps an ASCollectionView. It can be used @@ -18,4 +18,48 @@ @property (nonatomic, readonly) ASCollectionView *view; +/** + * Tuning parameters for a range type. + * + * @param rangeType The range type to get the tuning parameters for. + * + * @returns A tuning parameter value for the given range type. + * + * Defaults to the render range having one sceenful both leading and trailing and the preload range having two + * screenfuls in both directions. + */ +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; + +/** + * Set the tuning parameters for a range type. + * + * @param tuningParameters The tuning parameters to store for a range type. + * @param rangeType The range type to set the tuning parameters for. + */ +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; + +/** + * Reload everything from scratch, destroying the working range and all cached nodes. + * + * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on + * the main thread. + * @warning This method is substantially more expensive than UICollectionView's version. + */ +- (void)reloadDataWithCompletion:(void (^)())completion; + +/** + * Reload everything from scratch, destroying the working range and all cached nodes. + * + * @warning This method is substantially more expensive than UICollectionView's version. + */ +- (void)reloadData; + +/** + * Reload everything from scratch entirely on the main thread, destroying the working range and all cached nodes. + * + * @warning This method is substantially more expensive than UICollectionView's version and will block the main thread + * while all the cells load. + */ +- (void)reloadDataImmediately; + @end diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.m index 0b77d09ff1..cd0b7367c6 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.m @@ -7,6 +7,7 @@ // #import "ASCollectionNode.h" +#import "ASDisplayNode+Subclasses.h" @implementation ASCollectionNode @@ -42,4 +43,31 @@ [self.view clearFetchedData]; } +#pragma mark - ASCollectionView Forwards + +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType +{ + return [self.view tuningParametersForRangeType:rangeType]; +} + +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType +{ + return [self.view setTuningParameters:tuningParameters forRangeType:rangeType]; +} + +- (void)reloadDataWithCompletion:(void (^)())completion +{ + [self.view reloadDataWithCompletion:completion]; +} + +- (void)reloadData +{ + [self.view reloadData]; +} + +- (void)reloadDataImmediately +{ + [self.view reloadDataImmediately]; +} + @end diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 23eb84a6b5..b18ce1415d 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -22,6 +22,11 @@ */ typedef UIView *(^ASDisplayNodeViewBlock)(); +/** + * UIView creation block. Used to create the backing view of a new display node. + */ +typedef UIViewController *(^ASDisplayNodeViewControllerBlock)(); + /** * CALayer creation block. Used to create the backing layer of a new display node. */ @@ -685,7 +690,6 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) * @param node The node to be added. */ - (void)addSubnode:(ASDisplayNode *)node; -- (NSString *)name; @end /** CALayer(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to a CALayer. */ @@ -696,7 +700,6 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) * @param node The node to be added. */ - (void)addSubnode:(ASDisplayNode *)node; -- (NSString *)name; @end @interface ASDisplayNode (Deprecated) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 7758561d96..2d92c1883d 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -140,7 +140,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits; } - return overrides; } @@ -197,6 +196,29 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return [_ASDisplayLayer class]; } ++ (void)scheduleNodeForDisplay:(ASDisplayNode *)node +{ + ASDisplayNodeAssertMainThread(); + static NSMutableSet *nodesToDisplay = nil; + static BOOL displayScheduled = NO; + if (!nodesToDisplay) { + nodesToDisplay = [[NSMutableSet alloc] init]; + } + [nodesToDisplay addObject:node]; + if (!displayScheduled) { + displayScheduled = YES; + // It's essenital that any layout pass that is scheduled during the current + // runloop has a chance to be applied / scheduled, so always perform this after the current runloop. + dispatch_async(dispatch_get_main_queue(), ^{ + displayScheduled = NO; + for (ASDisplayNode *node in nodesToDisplay) { + [node __recursivelyTriggerDisplayAndBlock:NO]; + } + nodesToDisplay = nil; + }); + } +} + #pragma mark - Lifecycle - (void)_staticInitialize @@ -271,7 +293,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return self; } - - (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock { return [self initWithLayerBlock:layerBlock didLoadBlock:nil]; @@ -293,7 +314,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return self; } - - (void)dealloc { ASDisplayNodeAssertMainThread(); @@ -710,6 +730,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)recursivelyDisplayImmediately { ASDN::MutexLocker l(_propertyLock); + for (ASDisplayNode *child in _subnodes) { [child recursivelyDisplayImmediately]; } @@ -926,6 +947,10 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD _subnodes = [[NSMutableArray alloc] init]; [_subnodes addObject:subnode]; + + // This call will apply our .hierarchyState to the new subnode. + // If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState. + [subnode __setSupernode:self]; if (self.nodeLoaded) { // If this node has a view or layer, force the subnode to also create its view or layer and add it to the hierarchy here. @@ -945,8 +970,6 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD if (isMovingEquivalentParents) { [subnode __decrementVisibilityNotificationsDisabled]; } - - [subnode __setSupernode:self]; } /* @@ -970,13 +993,14 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD if (isMovingEquivalentParents) { [subnode __incrementVisibilityNotificationsDisabled]; } + [subnode removeFromSupernode]; - + [oldSubnode removeFromSupernode]; + if (!_subnodes) _subnodes = [[NSMutableArray alloc] init]; - - [oldSubnode removeFromSupernode]; [_subnodes insertObject:subnode atIndex:subnodeIndex]; + [subnode __setSupernode:self]; // Don't bother inserting the view/layer if in a rasterized subtree, because there are no layers in the hierarchy and none of this could possibly work. if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) { @@ -1004,8 +1028,6 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD if (isMovingEquivalentParents) { [subnode __decrementVisibilityNotificationsDisabled]; } - - [subnode __setSupernode:self]; } - (void)replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode @@ -1195,21 +1217,33 @@ static NSInteger incrementIfFound(NSInteger i) { - (void)removeFromSupernode { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); - if (!_supernode) - return; + BOOL shouldRemoveFromSuperviewOrSuperlayer = NO; + + { + ASDN::MutexLocker l(_propertyLock); + if (!_supernode) + return; + // Check to ensure that our view or layer is actually inside of our supernode; otherwise, don't remove it. + // Though _ASDisplayView decouples the supernode if it is inserted inside another view hierarchy, this is + // more difficult to guarantee with _ASDisplayLayer because CoreAnimation doesn't have a -didMoveToSuperlayer. + + if (self.nodeLoaded && _supernode.nodeLoaded) { + if (_flags.layerBacked || _supernode.layerBacked) { + shouldRemoveFromSuperviewOrSuperlayer = (_layer.superlayer == _supernode.layer); + } else { + shouldRemoveFromSuperviewOrSuperlayer = (_view.superview == _supernode.view); + } + } + } + // Do this before removing the view from the hierarchy, as the node will clear its supernode pointer when its view is removed from the hierarchy. + // This call may result in the object being destroyed. [_supernode _removeSubnode:self]; - if (ASDisplayNodeThreadIsMain()) { - if (_flags.layerBacked) { - [_layer removeFromSuperlayer]; - } else { - [_view removeFromSuperview]; - } - } else { - dispatch_async(dispatch_get_main_queue(), ^{ + if (shouldRemoveFromSuperviewOrSuperlayer) { + ASPerformBlockOnMainThread(^{ + ASDN::MutexLocker l(_propertyLock); if (_flags.layerBacked) { [_layer removeFromSuperlayer]; } else { @@ -1221,49 +1255,53 @@ static NSInteger incrementIfFound(NSInteger i) { - (BOOL)__visibilityNotificationsDisabled { + // Currently, this method is only used by the testing infrastructure to verify this internal feature. ASDN::MutexLocker l(_propertyLock); return _flags.visibilityNotificationsDisabled > 0; } +- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled +{ + ASDN::MutexLocker l(_propertyLock); + return (_hierarchyState & ASHierarchyStateTransitioningSupernodes); +} + - (void)__incrementVisibilityNotificationsDisabled { ASDN::MutexLocker l(_propertyLock); const size_t maxVisibilityIncrement = (1ULL< 0, @"Can't decrement past 0"); - if (_flags.visibilityNotificationsDisabled > 0) + if (_flags.visibilityNotificationsDisabled > 0) { _flags.visibilityNotificationsDisabled--; -} - -// This uses the layer hieararchy for safety. Who knows what people might do and it would be bad to have visibilty out of sync -- (BOOL)__hasParentWithVisibilityNotificationsDisabled -{ - CALayer *layer = _layer; - do { - ASDisplayNode *node = ASLayerToDisplayNode(layer); - if (node) { - if (node->_flags.visibilityNotificationsDisabled) { - return YES; - } - } - layer = layer.superlayer; - } while (layer); - - return NO; + } + if (_flags.visibilityNotificationsDisabled == 0) { + // Must have just transitioned from 1 to 0. Notify all subnodes that we are no longer in a disabled state. + // FIXME: This system should be revisited when refactoring and consolidating the implementation of the + // addSubnode: and insertSubnode:... methods. As implemented, though logically irrelevant for expected use cases, + // multiple nodes in the subtree below may have a non-zero visibilityNotification count and still have + // the ASHierarchyState bit cleared (the only value checked when reading this state). + [self exitHierarchyState:ASHierarchyStateTransitioningSupernodes]; + } } - (void)__enterHierarchy { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy"); - if (!self.inHierarchy && !_flags.visibilityNotificationsDisabled && ![self __hasParentWithVisibilityNotificationsDisabled]) { + if (!self.inHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { self.inHierarchy = YES; _flags.isEnteringHierarchy = YES; if (self.shouldRasterizeDescendants) { @@ -1275,7 +1313,7 @@ static NSInteger incrementIfFound(NSInteger i) { _flags.isEnteringHierarchy = NO; CALayer *layer = self.layer; - if (!self.layer.contents) { + if (!layer.contents) { [layer setNeedsDisplay]; } } @@ -1285,7 +1323,7 @@ static NSInteger incrementIfFound(NSInteger i) { { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy"); - if (self.inHierarchy && !_flags.visibilityNotificationsDisabled && ![self __hasParentWithVisibilityNotificationsDisabled]) { + if (self.inHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { self.inHierarchy = NO; [self.asyncLayer cancelAsyncDisplay]; @@ -1453,7 +1491,7 @@ static NSInteger incrementIfFound(NSInteger i) { [_placeholderLayer removeFromSuperlayer]; } -void recursivelyEnsureDisplayForLayer(CALayer *layer) +void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) { // This recursion must handle layers in various states: // 1. Just added to hierarchy, CA hasn't yet called -display @@ -1472,26 +1510,26 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work. for (CALayer *sublayer in layer.sublayers) { - recursivelyEnsureDisplayForLayer(sublayer); + recursivelyTriggerDisplayForLayer(sublayer, shouldBlock); } - // As the recursion unwinds, verify each transaction is complete and block if it is not. - // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. - BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); - if (waitUntilComplete) { - for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { - // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. - // This significantly reduces time on the main thread relative to UIKit. - [transaction waitUntilComplete]; + if (shouldBlock) { + // As the recursion unwinds, verify each transaction is complete and block if it is not. + // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. + BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); + if (waitUntilComplete) { + for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { + // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. + // This significantly reduces time on the main thread relative to UIKit. + [transaction waitUntilComplete]; + } } } } -- (void)recursivelyEnsureDisplay +- (void)__recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock { ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(self.isNodeLoaded, @"Node must have layer or view loaded to use -recursivelyEnsureDisplay"); - ASDisplayNodeAssert(self.inHierarchy && (self.isLayerBacked || self.view.window != nil), @"Node must be in a hierarchy to use -recursivelyEnsureDisplay"); CALayer *layer = self.layer; // -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout, @@ -1500,7 +1538,12 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) if ([layer needsLayout]) { [layer layoutIfNeeded]; } - recursivelyEnsureDisplayForLayer(layer); + recursivelyTriggerDisplayForLayer(layer, shouldBlock); +} + +- (void)recursivelyEnsureDisplay +{ + [self __recursivelyTriggerDisplayAndBlock:YES]; } - (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay @@ -1778,6 +1821,19 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) _hierarchyState = newState; } + // Entered or exited contents rendering state. + if ((newState & ASHierarchyStateRangeManaged) != (oldState & ASHierarchyStateRangeManaged)) { + if (newState & ASHierarchyStateRangeManaged) { + [self enterInterfaceState:self.supernode.interfaceState]; + } else { + // The case of exiting a range-managed state should be fairly rare. Adding or removing the node + // to a view hierarchy will cause its interfaceState to be either fully set or unset (all fields), + // but because we might be about to be added to a view hierarchy, exiting the interface state now + // would cause inefficient churn. The tradeoff is that we may not clear contents / fetched data + // for nodes that are removed from a managed state and then retained but not used (bad idea anyway!) + } + } + if (newState != oldState) { LOG(@"setHierarchyState: oldState = %lu, newState = %lu", (unsigned long)oldState, (unsigned long)newState); } @@ -1815,7 +1871,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) ASDisplayNode *subnode = nil; CGRect subnodeFrame = CGRectZero; for (ASLayout *subnodeLayout in _layout.sublayouts) { - ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout."); + ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); CGPoint adjustedOrigin = subnodeLayout.position; if (isfinite(adjustedOrigin.x) == NO) { ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); @@ -2127,6 +2183,9 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, return _replaceAsyncSentinel != nil; } +// FIXME: This method doesn't appear to be called, and could be removed. +// However, it may be useful for an API similar to what Paper used to create a new node hierarchy, +// trigger asynchronous measurement and display on it, and have it swap out and replace an old hierarchy. - (ASSentinel *)_asyncReplaceSentinel { ASDN::MutexLocker l(_propertyLock); @@ -2270,32 +2329,33 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; @implementation UIView (AsyncDisplayKit) -- (void)addSubnode:(ASDisplayNode *)node +- (void)addSubnode:(ASDisplayNode *)subnode { - if (node.layerBacked) { - [self.layer addSublayer:node.layer]; + if (subnode.layerBacked) { + // Call -addSubnode: so that we use the asyncdisplaykit_node path if possible. + [self.layer addSubnode:subnode]; } else { - [self addSubview:node.view]; + ASDisplayNode *selfNode = self.asyncdisplaykit_node; + if (selfNode) { + [selfNode addSubnode:subnode]; + } else { + [self addSubview:subnode.view]; + } } } -- (NSString *)name -{ - return self.asyncdisplaykit_node.name; -} - @end @implementation CALayer (AsyncDisplayKit) -- (void)addSubnode:(ASDisplayNode *)node +- (void)addSubnode:(ASDisplayNode *)subnode { - [self addSublayer:node.layer]; -} - -- (NSString *)name -{ - return self.asyncdisplaykit_node.name; + ASDisplayNode *selfNode = self.asyncdisplaykit_node; + if (selfNode) { + [selfNode addSubnode:subnode]; + } else { + [self addSublayer:subnode.layer]; + } } @end diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h new file mode 100644 index 0000000000..36355c91dd --- /dev/null +++ b/AsyncDisplayKit/ASPagerNode.h @@ -0,0 +1,27 @@ +// +// ASPagerNode.h +// AsyncDisplayKit +// +// Created by Levi McCallum on 12/7/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import + +@protocol ASPagerNodeDataSource; + +@interface ASPagerNode : ASCollectionNode + +@property (weak, nonatomic) id dataSource; + +- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; + +@end + +@protocol ASPagerNodeDataSource + +- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; + +- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; + +@end \ No newline at end of file diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m new file mode 100644 index 0000000000..fef60e2e2d --- /dev/null +++ b/AsyncDisplayKit/ASPagerNode.m @@ -0,0 +1,79 @@ +// +// ASPagerNode.m +// AsyncDisplayKit +// +// Created by Levi McCallum on 12/7/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "ASPagerNode.h" + +#import + +@interface ASPagerNode () { + UICollectionViewFlowLayout *_flowLayout; +} + +@end + +@implementation ASPagerNode + +- (instancetype)init +{ + _flowLayout = [[UICollectionViewFlowLayout alloc] init]; + _flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; + _flowLayout.minimumInteritemSpacing = 0; + _flowLayout.minimumLineSpacing = 0; + + self = [super initWithCollectionViewLayout:_flowLayout]; + if (self != nil) { + } + return self; +} + +- (void)didLoad +{ + [super didLoad]; + + self.view.asyncDataSource = self; + self.view.asyncDelegate = self; + + self.view.pagingEnabled = YES; + self.view.allowsSelection = NO; + self.view.showsVerticalScrollIndicator = NO; + self.view.showsHorizontalScrollIndicator = NO; + + ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; + ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; + [self setTuningParameters:preloadParams forRangeType:ASLayoutRangeTypePreload]; + [self setTuningParameters:renderParams forRangeType:ASLayoutRangeTypeRender]; +} + +#pragma mark - Helpers + +- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated +{ + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; + [self.view scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated]; +} + +#pragma mark - ASCollectionViewDataSource + +- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssert(self.dataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); + return [self.dataSource pagerNode:self nodeAtIndex:indexPath.item]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ + ASDisplayNodeAssert(self.dataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); + return [self.dataSource numberOfPagesInPagerNode:self]; +} + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +{ + return ASSizeRangeMake(CGSizeZero, self.view.bounds.size); +} + +@end diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 2c73367e96..ddb6838037 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -532,6 +532,22 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark - #pragma mark Intercepted selectors +- (void)setTableHeaderView:(UIView *)tableHeaderView +{ + // Typically the view will be nil before setting it, but reset state if it is being re-hosted. + [self.tableHeaderView.asyncdisplaykit_node exitHierarchyState:ASHierarchyStateRangeManaged]; + [super setTableHeaderView:tableHeaderView]; + [self.tableHeaderView.asyncdisplaykit_node enterHierarchyState:ASHierarchyStateRangeManaged]; +} + +- (void)setTableFooterView:(UIView *)tableFooterView +{ + // Typically the view will be nil before setting it, but reset state if it is being re-hosted. + [self.tableFooterView.asyncdisplaykit_node exitHierarchyState:ASHierarchyStateRangeManaged]; + [super setTableFooterView:tableFooterView]; + [self.tableFooterView.asyncdisplaykit_node enterHierarchyState:ASHierarchyStateRangeManaged]; +} + - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { _ASTableViewCell *cell = [self dequeueReusableCellWithIdentifier:kCellReuseIdentifier forIndexPath:indexPath]; diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 7040834238..0cb9a32f56 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -30,6 +30,8 @@ #import +#import + #import #import diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.h b/AsyncDisplayKit/Details/ASBasicImageDownloader.h index ecb4861911..7f7a86c907 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.h +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.h @@ -16,4 +16,7 @@ + (instancetype)sharedImageDownloader; ++ (instancetype)new __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); +- (instancetype)init __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); + @end diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm index ad1026d013..73301919cc 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -205,14 +205,14 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext static ASBasicImageDownloader *sharedImageDownloader = nil; static dispatch_once_t once = 0; dispatch_once(&once, ^{ - sharedImageDownloader = [[ASBasicImageDownloader alloc] init]; + sharedImageDownloader = [[ASBasicImageDownloader alloc] _init]; }); return sharedImageDownloader; } #pragma mark Lifecycle. -- (instancetype)init +- (instancetype)_init { if (!(self = [super init])) return nil; diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index 206b7d7e62..4872707e7e 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -17,8 +17,9 @@ @end @implementation ASRangeHandlerRender -@synthesize workingWindow = _workingWindow; +#if USE_WORKING_WINDOW +@synthesize workingWindow = _workingWindow; - (UIWindow *)workingWindow { ASDisplayNodeAssertMainThread(); @@ -45,6 +46,7 @@ [self node:node exitedRangeOfType:ASLayoutRangeTypeRender]; } } +#endif - (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType { @@ -60,12 +62,17 @@ // The node un-suspends display. [node enterInterfaceState:ASInterfaceStateDisplay]; + +#if USE_WORKING_WINDOW // Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile. // Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations. // Any view-backed nodes will still create their views in order to assemble the layer heirarchy, and they will // also assemble a view subtree for the node, but we avoid the much more significant expense triggered by a view // being added or removed from an onscreen window (responder chain setup, will/DidMoveToWindow: recursive calls, etc) [[[self workingWindow] layer] addSublayer:node.layer]; +#else + [node recursivelyEnsureDisplay]; // Need to do this without waiting +#endif } - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType @@ -93,6 +100,7 @@ // The node calls clearCurrentContents and suspends display [node exitInterfaceState:ASInterfaceStateDisplay]; +#if USE_WORKING_WINDOW if (node.layer.superlayer != [[self workingWindow] layer]) { // In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range. if (![node isLayerBacked]) { @@ -104,6 +112,13 @@ // At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell. [node.layer removeFromSuperlayer]; +#else + if (![node isLayerBacked]) { + [node.view removeFromSuperview]; + } else { + [node.layer removeFromSuperlayer]; + } +#endif } @end diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 12622d3bb6..3152e4d7ce 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -147,11 +147,9 @@ - (void)displayImmediately { - // REVIEW: Should this respect isDisplaySuspended? If so, we'd probably want to synchronously display when - // setDisplaySuspended:No is called, rather than just scheduling. The thread affinity for the displayImmediately - // call will be tricky if we need to support this, though. It probably should just execute if displayImmediately is - // called directly. The caller should be responsible for not calling displayImmediately if it wants to obey the - // suspended state. + // This method is a low-level bypass that avoids touching CA, including any reset of the + // needsDisplay flag, until the .contents property is set with the result. + // It is designed to be able to block the thread of any caller and fully execute the display. ASDisplayNodeAssertMainThread(); [self display:NO]; diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 027a312733..0d4e8c77ca 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -63,6 +63,22 @@ return self; } +- (void)willMoveToWindow:(UIWindow *)newWindow +{ + BOOL visible = (newWindow != nil); + if (visible && !_node.inHierarchy) { + [_node __enterHierarchy]; + } +} + +- (void)didMoveToWindow +{ + BOOL visible = (self.window != nil); + if (!visible && _node.inHierarchy) { + [_node __exitHierarchy]; + } +} + - (void)willMoveToSuperview:(UIView *)newSuperview { // Keep the node alive while the view is in a view hierarchy. This helps ensure that async-drawing views can always @@ -76,28 +92,74 @@ else if (currentSuperview && !newSuperview) { self.keepalive_node = nil; } -} + + if (newSuperview) { + ASDisplayNode *supernode = _node.supernode; + BOOL supernodeLoaded = supernode.nodeLoaded; + ASDisplayNodeAssert(!supernode.isLayerBacked, @"Shouldn't be possible for _ASDisplayView's supernode to be layer-backed."); + + BOOL needsSupernodeUpdate = NO; -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - BOOL visible = newWindow != nil; - if (visible && !_node.inHierarchy) { - [_node __enterHierarchy]; - } else if (!visible && _node.inHierarchy) { - [_node __exitHierarchy]; + if (supernode) { + if (supernodeLoaded) { + if (supernode.layerBacked) { + // See comment in -didMoveToSuperview. This case should be avoided, but is possible with app-level coding errors. + needsSupernodeUpdate = (supernode.layer != newSuperview.layer); + } else { + // If we have a supernode, compensate for users directly messing with views by hitching up to any new supernode. + needsSupernodeUpdate = (supernode.view != newSuperview); + } + } else { + needsSupernodeUpdate = YES; + } + } else { + // If we have no supernode and we are now in a view hierarchy, check to see if we can hook up to a supernode. + needsSupernodeUpdate = (newSuperview != nil); + } + + if (needsSupernodeUpdate) { + // -removeFromSupernode is called by -addSubnode:, if it is needed. + [newSuperview.asyncdisplaykit_node addSubnode:_node]; + } } + } - (void)didMoveToSuperview { - // FIXME maybe move this logic into ASDisplayNode addSubnode/removeFromSupernode - UIView *superview = self.superview; - - // If superview's node is different from supernode's view, fix it by setting supernode to the new superview's node. Got that? - if (!superview) - [_node __setSupernode:nil]; - else if (superview != _node.supernode.view) - [_node __setSupernode:superview.asyncdisplaykit_node]; + ASDisplayNode *supernode = _node.supernode; + ASDisplayNodeAssert(!supernode.isLayerBacked, @"Shouldn't be possible for superview's node to be layer-backed."); + + if (supernode) { + ASDisplayNodeAssertTrue(_node.nodeLoaded); + UIView *superview = self.superview; + BOOL supernodeLoaded = supernode.nodeLoaded; + BOOL needsSupernodeRemoval = NO; + + if (superview) { + // If our new superview is not the same as the supernode's view, or the supernode has no view, disconnect. + if (supernodeLoaded) { + if (supernode.layerBacked) { + // As asserted at the top, this shouldn't be possible, but in production with assertions disabled it can happen. + // We try to make such code behave as well as feasible because it's not that hard of an error to make if some deep + // child node of a layer-backed node happens to be view-backed, but it is not supported and should be avoided. + needsSupernodeRemoval = (supernode.layer != superview.layer); + } else { + needsSupernodeRemoval = (supernode.view != superview); + } + } else { + needsSupernodeRemoval = YES; + } + } else { + // If supernode is loaded but our superview is nil, the user manually removed us, so disconnect supernode. + needsSupernodeRemoval = supernodeLoaded; + } + + if (needsSupernodeRemoval) { + // The node will only disconnect from its supernode, not removeFromSuperview, in this condition. + [_node removeFromSupernode]; + } + } } - (void)setNeedsDisplay diff --git a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m b/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m index 9aa3bd8fdb..5f57b5aa86 100644 --- a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m +++ b/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m @@ -111,7 +111,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) [paddedLines addObject:paddedLine]; } concatenatedLines = paddedLines; - totalLineLength += difference; + // totalLineLength += difference; } concatenatedLines = [self appendTopAndBottomToBoxString:concatenatedLines parent:parent]; return [concatenatedLines componentsJoinedByString:@"\n"]; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index 4242881a5b..525d1b4bf8 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -304,10 +304,9 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, // for async display, capture the current displaySentinel value to bail early when the job is executed if another is // enqueued // for sync display, just use nil for the displaySentinel and go - // - // REVIEW: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing - // from the displayQueue? do we want to put in some kind of timer to not cancel early fails from displaySentinel - // changes? + + // FIXME: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing + // from the displayQueue? Need to not cancel early fails from displaySentinel changes. ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil); int64_t displaySentinelValue = [displaySentinel increment]; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+DebugTiming.mm b/AsyncDisplayKit/Private/ASDisplayNode+DebugTiming.mm index dde8b0bd25..442c70a270 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+DebugTiming.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+DebugTiming.mm @@ -7,12 +7,10 @@ */ #import "ASDisplayNode+DebugTiming.h" - #import "ASDisplayNodeInternal.h" @implementation ASDisplayNode (DebugTiming) - #if TIME_DISPLAYNODE_OPS - (NSTimeInterval)debugTimeToCreateView { @@ -83,6 +81,4 @@ #endif - - @end diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 58cc12c35c..701be8940e 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -19,6 +19,10 @@ #import "ASThread.h" #import "ASLayoutOptions.h" +// Project-wide control for whether the offscreen UIWindow is used for display, or if +// ASDK's internal system for coalescing and triggering display events is used. +#define USE_WORKING_WINDOW 1 + /** Hierarchy state is propogated from nodes to all of their children when certain behaviors are required from the subtree. Examples include rasterization and external driving of the .interfaceState property. @@ -33,14 +37,17 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) { /** The node may or may not have a supernode, but no supernode has a special hierarchy-influencing option enabled. */ - ASHierarchyStateNormal = 0, + ASHierarchyStateNormal = 0, /** The node has a supernode with .shouldRasterizeDescendants = YES. Note: the root node of the rasterized subtree (the one with the property set on it) will NOT have this state set. */ - ASHierarchyStateRasterized = 1 << 0, + ASHierarchyStateRasterized = 1 << 0, /** The node or one of its supernodes is managed by a class like ASRangeController. Most commonly, these nodes are ASCellNode objects or a subnode of one, and are used in ASTableView or ASCollectionView. These nodes also recieve regular updates to the .interfaceState property with more detailed status information. */ - ASHierarchyStateRangeManaged = 1 << 1, + ASHierarchyStateRangeManaged = 1 << 1, + /** Down-propogated version of _flags.visibilityNotificationsDisabled. This flag is very rarely set, but by having it + locally available to nodes, they do not have to walk up supernodes at the critical points it is checked. */ + ASHierarchyStateTransitioningSupernodes = 1 << 2 }; @interface ASDisplayNode () <_ASDisplayLayerDelegate> diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index b23b994f8c..2239cc038a 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -221,6 +221,8 @@ - (void)setNeedsDisplay { + _bridge_prologue; + if (_hierarchyState & ASHierarchyStateRasterized) { ASPerformBlockOnMainThread(^{ // The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node @@ -238,7 +240,19 @@ [rasterizedContainerNode setNeedsDisplay]; }); } else { - [_layer setNeedsDisplay]; + // If not rasterized (and therefore we certainly have a view or layer), + // Send the message to the view/layer first, as scheduleNodeForDisplay may call -displayIfNeeded. + // Wrapped / synchronous nodes created with initWithView/LayerBlock: do not need scheduleNodeForDisplay, + // as they don't need to display in the working range at all - since at all times onscreen, one + // -setNeedsDisplay to the CALayer will result in a synchronous display in the next frame. + + _messageToViewOrLayer(setNeedsDisplay); + +#if !USE_WORKING_WINDOW + if (_layer && !self.isSynchronous) { + [ASDisplayNode scheduleNodeForDisplay:self]; + } +#endif } } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index f30361a3ab..e16eb99092 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -105,9 +105,10 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) NSTimeInterval _debugTimeToAddSubnodeViews; NSTimeInterval _debugTimeForDidLoad; #endif - } ++ (void)scheduleNodeForDisplay:(ASDisplayNode *)node; + // The _ASDisplayLayer backing the node, if any. @property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer; @@ -134,6 +135,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) // Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this. - (BOOL)__visibilityNotificationsDisabled; +- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled; - (void)__incrementVisibilityNotificationsDisabled; - (void)__decrementVisibilityNotificationsDisabled; diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m index b174722341..e0d7e3c9fc 100644 --- a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m +++ b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m @@ -16,8 +16,9 @@ @implementation ASBasicImageDownloaderTests -- (void)testAsynchronouslyDownloadTheSameURLTwice { - ASBasicImageDownloader *downloader = [ASBasicImageDownloader new]; +- (void)testAsynchronouslyDownloadTheSameURLTwice +{ + ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader]; NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"]; diff --git a/AsyncDisplayKitTests/ASDisplayNodeAppearanceTests.m b/AsyncDisplayKitTests/ASDisplayNodeAppearanceTests.m index e08f50d15f..5b0a0a753f 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeAppearanceTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeAppearanceTests.m @@ -49,6 +49,7 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C @interface ASDisplayNode (PrivateStuffSoWeDontPullInCPPInternalH) - (BOOL)__visibilityNotificationsDisabled; +- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled; - (id)initWithViewClass:(Class)viewClass; - (id)initWithLayerClass:(Class)layerClass; @end @@ -360,6 +361,7 @@ static UIView *viewWithName(NSString *name) { } if (useManualDisable) { XCTAssertTrue([child __visibilityNotificationsDisabled], @"Should not have re-enabled yet"); + XCTAssertTrue([child __selfOrParentHasVisibilityNotificationsDisabled], @"Should not have re-enabled yet"); ASDisplayNodeEnableHierarchyNotifications(child); } @@ -377,6 +379,7 @@ static UIView *viewWithName(NSString *name) { } if (useManualDisable) { XCTAssertTrue([child __visibilityNotificationsDisabled], @"Should not have re-enabled yet"); + XCTAssertTrue([child __selfOrParentHasVisibilityNotificationsDisabled], @"Should not have re-enabled yet"); ASDisplayNodeEnableHierarchyNotifications(child); } @@ -390,6 +393,7 @@ static UIView *viewWithName(NSString *name) { // Make sure that we don't leave these unbalanced XCTAssertFalse([child __visibilityNotificationsDisabled], @"Unbalanced visibility notifications calls"); + XCTAssertFalse([child __selfOrParentHasVisibilityNotificationsDisabled], @"Should not have re-enabled yet"); [window release]; } diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index c49f56a660..ae7c4acf9c 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -36,11 +36,11 @@ static CALayer *layerWithName(NSString *name) { } static NSString *orderStringFromSublayers(CALayer *l) { - return [[l.sublayers valueForKey:@"name"] componentsJoinedByString:@","]; + return [[[l.sublayers valueForKey:@"asyncdisplaykit_node"] valueForKey:@"name"] componentsJoinedByString:@","]; } static NSString *orderStringFromSubviews(UIView *v) { - return [[v.subviews valueForKey:@"name"] componentsJoinedByString:@","]; + return [[[v.subviews valueForKey:@"asyncdisplaykit_node"] valueForKey:@"name"] componentsJoinedByString:@","]; } static NSString *orderStringFromSubnodes(ASDisplayNode *n) { @@ -1342,9 +1342,12 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point [parent insertSubnode:c belowSubnode:b]; XCTAssertEqualObjects(orderStringFromSublayers(parent.layer), @"a,e,d,c,b", @"Didn't match"); - XCTAssertEqual(3u, parent.subnodes.count, @"Should have the right subnode count"); + XCTAssertEqual(4u, parent.subnodes.count, @"Should have the right subnode count"); XCTAssertEqual(4u, parent.view.subviews.count, @"Should have the right subview count"); XCTAssertEqual(5u, parent.layer.sublayers.count, @"Should have the right sublayer count"); + + [e removeFromSuperlayer]; + XCTAssertEqual(4u, parent.layer.sublayers.count, @"Should have the right sublayer count"); //TODO: assert that things deallocate immediately and don't have latent autoreleases in here [parent release]; @@ -1352,6 +1355,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point [b release]; [c release]; [d release]; + [e release]; } - (void)testAppleBugInsertSubview @@ -1415,11 +1419,11 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point [parent.view insertSubview:d aboveSubview:a.view]; XCTAssertEqualObjects(orderStringFromSublayers(parent.layer), @"a,d,b", @"Didn't match"); - // (a,e,d,b) => (a,d,>c<,b) + // (a,d,b) => (a,d,>c<,b) [parent insertSubnode:c belowSubnode:b]; XCTAssertEqualObjects(orderStringFromSublayers(parent.layer), @"a,d,c,b", @"Didn't match"); - XCTAssertEqual(3u, parent.subnodes.count, @"Should have the right subnode count"); + XCTAssertEqual(4u, parent.subnodes.count, @"Should have the right subnode count"); XCTAssertEqual(4u, parent.view.subviews.count, @"Should have the right subview count"); XCTAssertEqual(4u, parent.layer.sublayers.count, @"Should have the right sublayer count"); diff --git a/examples/CollectionViewWithViewControllerCells/Podfile b/examples/CollectionViewWithViewControllerCells/Podfile new file mode 100644 index 0000000000..6c012e3c04 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj b/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..a93761d93a --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,381 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */; }; + 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.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 */; }; + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; }; + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; }; + AEE6B3E51C16B65600238D20 /* ImageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AEE6B3E41C16B65600238D20 /* ImageViewController.m */; }; + FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F02BAF78E68BC56FD8C161B7 /* libPods.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 25A1FA831C02F7AC00193875 /* MosaicCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MosaicCollectionViewLayout.h; sourceTree = ""; }; + 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MosaicCollectionViewLayout.m; sourceTree = ""; }; + 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 = ""; }; + 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SupplementaryNode.h; sourceTree = ""; }; + 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SupplementaryNode.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 = ""; }; + AC3C4A631A11F47200143C57 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + AC3C4A651A11F47200143C57 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + AC3C4A661A11F47200143C57 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + AC3C4A681A11F47200143C57 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + AC3C4A691A11F47200143C57 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + AEE6B3E31C16B65600238D20 /* ImageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageViewController.h; sourceTree = ""; }; + AEE6B3E41C16B65600238D20 /* ImageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageViewController.m; sourceTree = ""; }; + CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + F02BAF78E68BC56FD8C161B7 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AC3C4A5B1A11F47200143C57 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 90A2B9C5397C46134C8A793B /* Pods */ = { + isa = PBXGroup; + children = ( + 2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */, + CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + AC3C4A551A11F47200143C57 = { + isa = PBXGroup; + children = ( + AC3C4A601A11F47200143C57 /* Sample */, + AC3C4A5F1A11F47200143C57 /* Products */, + 90A2B9C5397C46134C8A793B /* Pods */, + D6E38FF0CB18E3F55CF06437 /* Frameworks */, + ); + sourceTree = ""; + }; + AC3C4A5F1A11F47200143C57 /* Products */ = { + isa = PBXGroup; + children = ( + AC3C4A5E1A11F47200143C57 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + AC3C4A601A11F47200143C57 /* Sample */ = { + isa = PBXGroup; + children = ( + 25A1FA831C02F7AC00193875 /* MosaicCollectionViewLayout.h */, + 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */, + AC3C4A651A11F47200143C57 /* AppDelegate.h */, + AC3C4A661A11F47200143C57 /* AppDelegate.m */, + AC3C4A681A11F47200143C57 /* ViewController.h */, + AC3C4A691A11F47200143C57 /* ViewController.m */, + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */, + AC3C4A611A11F47200143C57 /* Supporting Files */, + 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */, + 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */, + AEE6B3E31C16B65600238D20 /* ImageViewController.h */, + AEE6B3E41C16B65600238D20 /* ImageViewController.m */, + ); + indentWidth = 2; + path = Sample; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + AC3C4A611A11F47200143C57 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + AC3C4A621A11F47200143C57 /* Info.plist */, + AC3C4A631A11F47200143C57 /* main.m */, + 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D6E38FF0CB18E3F55CF06437 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F02BAF78E68BC56FD8C161B7 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + AC3C4A5D1A11F47200143C57 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */, + AC3C4A5A1A11F47200143C57 /* Sources */, + AC3C4A5B1A11F47200143C57 /* Frameworks */, + AC3C4A5C1A11F47200143C57 /* Resources */, + A6902C454C7661D0D277AC62 /* Copy Pods Resources */, + EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = AC3C4A5E1A11F47200143C57 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + AC3C4A561A11F47200143C57 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + AC3C4A5D1A11F47200143C57 = { + CreatedOnToolsVersion = 6.1; + }; + }; + }; + buildConfigurationList = AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = AC3C4A551A11F47200143C57; + productRefGroup = AC3C4A5F1A11F47200143C57 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AC3C4A5D1A11F47200143C57 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + AC3C4A5C1A11F47200143C57 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */, + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + A6902C454C7661D0D277AC62 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "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"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + AC3C4A5A1A11F47200143C57 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */, + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, + 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */, + AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, + AC3C4A641A11F47200143C57 /* main.m in Sources */, + AEE6B3E51C16B65600238D20 /* ImageViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + AC3C4A7F1A11F47200143C57 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AC3C4A801A11F47200143C57 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + AC3C4A821A11F47200143C57 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */; + 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; + }; + name = Debug; + }; + AC3C4A831A11F47200143C57 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */; + 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; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A7F1A11F47200143C57 /* Debug */, + AC3C4A801A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A821A11F47200143C57 /* Debug */, + AC3C4A831A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = AC3C4A561A11F47200143C57 /* Project object */; +} diff --git a/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..f49edc75d6 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/CollectionViewWithViewControllerCells/Sample.xcworkspace/contents.xcworkspacedata b/examples/CollectionViewWithViewControllerCells/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/CollectionViewWithViewControllerCells/Sample/AppDelegate.h b/examples/CollectionViewWithViewControllerCells/Sample/AppDelegate.h new file mode 100644 index 0000000000..2aa29369b4 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/AppDelegate.h @@ -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 + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/CollectionViewWithViewControllerCells/Sample/AppDelegate.m b/examples/CollectionViewWithViewControllerCells/Sample/AppDelegate.m new file mode 100644 index 0000000000..6d9b473532 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/AppDelegate.m @@ -0,0 +1,29 @@ +/* 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 "AppDelegate.h" + +#import "ViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[ViewController alloc] init]; + + [self.window makeKeyAndVisible]; + + return YES; +} + +@end diff --git a/examples/CollectionViewWithViewControllerCells/Sample/ImageViewController.h b/examples/CollectionViewWithViewControllerCells/Sample/ImageViewController.h new file mode 100644 index 0000000000..668840d050 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/ImageViewController.h @@ -0,0 +1,16 @@ +/* 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 + +@interface ImageViewController : UIViewController +- (instancetype)initWithImage:(UIImage *)image; +@end diff --git a/examples/CollectionViewWithViewControllerCells/Sample/ImageViewController.m b/examples/CollectionViewWithViewControllerCells/Sample/ImageViewController.m new file mode 100644 index 0000000000..77b35b8a32 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/ImageViewController.m @@ -0,0 +1,50 @@ +/* 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 "ImageViewController.h" + +@interface ImageViewController () +@property (nonatomic) UIImageView *imageView; +@end + +@implementation ImageViewController + +- (instancetype)initWithImage:(UIImage *)image { + if (!(self = [super init])) { return nil; } + + self.imageView = [[UIImageView alloc] initWithImage:image]; + + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self.view addSubview:self.imageView]; + + UIGestureRecognizer *tap = [[UIGestureRecognizer alloc] initWithTarget:self action:@selector(tapped)]; + [self.view addGestureRecognizer:tap]; + + self.imageView.contentMode = UIViewContentModeScaleAspectFill; +} + +- (void)tapped; +{ + NSLog(@"tapped!"); +} + +- (void)viewWillLayoutSubviews +{ + self.imageView.frame = self.view.bounds; +} + +@end diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..f0fce54771 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,39 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "filename" : "Default-568h@2x.png", + "minimum-system-version" : "7.0", + "subtype" : "retina4", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "scale" : "1x", + "orientation" : "portrait" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "orientation" : "portrait" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "filename" : "Default-568h@2x.png", + "subtype" : "retina4", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png new file mode 100644 index 0000000000..1547a98454 Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_0.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_0.imageset/Contents.json new file mode 100644 index 0000000000..4eaff61cc1 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_0.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_0.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_0.imageset/image_0.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_0.imageset/image_0.jpg new file mode 100644 index 0000000000..4a365897ea Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_0.imageset/image_0.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_1.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_1.imageset/Contents.json new file mode 100644 index 0000000000..80c90eca3e --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_1.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_1.imageset/image_1.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_1.imageset/image_1.jpg new file mode 100644 index 0000000000..5cb4828f44 Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_1.imageset/image_1.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_10.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_10.imageset/Contents.json new file mode 100644 index 0000000000..d61e934e39 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_10.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_10.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_10.imageset/image_10.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_10.imageset/image_10.jpg new file mode 100644 index 0000000000..ea5cd6d268 Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_10.imageset/image_10.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_11.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_11.imageset/Contents.json new file mode 100644 index 0000000000..94921077f9 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_11.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_11.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_11.imageset/image_11.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_11.imageset/image_11.jpg new file mode 100644 index 0000000000..e93c68e512 Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_11.imageset/image_11.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_12.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_12.imageset/Contents.json new file mode 100644 index 0000000000..61488a9fdc --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_12.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_12.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_12.imageset/image_12.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_12.imageset/image_12.jpg new file mode 100644 index 0000000000..d520b6d80f Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_12.imageset/image_12.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_13.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_13.imageset/Contents.json new file mode 100644 index 0000000000..7f83f8a390 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_13.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_13.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_13.imageset/image_13.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_13.imageset/image_13.jpg new file mode 100644 index 0000000000..c0232370cd Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_13.imageset/image_13.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_2.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_2.imageset/Contents.json new file mode 100644 index 0000000000..774cde7833 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_2.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_2.imageset/image_2.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_2.imageset/image_2.jpg new file mode 100644 index 0000000000..175343454d Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_2.imageset/image_2.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_3.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_3.imageset/Contents.json new file mode 100644 index 0000000000..c0abe414cd --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_3.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_3.imageset/image_3.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_3.imageset/image_3.jpg new file mode 100644 index 0000000000..f5398cac79 Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_3.imageset/image_3.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_4.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_4.imageset/Contents.json new file mode 100644 index 0000000000..55a498a8a0 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_4.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_4.imageset/image_4.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_4.imageset/image_4.jpg new file mode 100644 index 0000000000..2a6fe4c264 Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_4.imageset/image_4.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_5.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_5.imageset/Contents.json new file mode 100644 index 0000000000..9a1181e83b --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_5.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_5.imageset/image_5.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_5.imageset/image_5.jpg new file mode 100644 index 0000000000..4e507b8064 Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_5.imageset/image_5.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_6.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_6.imageset/Contents.json new file mode 100644 index 0000000000..6aef7d6047 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_6.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_6.imageset/image_6.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_6.imageset/image_6.jpg new file mode 100644 index 0000000000..35fe778b3a Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_6.imageset/image_6.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_7.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_7.imageset/Contents.json new file mode 100644 index 0000000000..acdb0e87f0 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_7.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_7.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_7.imageset/image_7.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_7.imageset/image_7.jpg new file mode 100644 index 0000000000..8f5e037722 Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_7.imageset/image_7.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_8.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_8.imageset/Contents.json new file mode 100644 index 0000000000..40d616ed40 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_8.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_8.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_8.imageset/image_8.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_8.imageset/image_8.jpg new file mode 100644 index 0000000000..5651436bb6 Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_8.imageset/image_8.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_9.imageset/Contents.json b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_9.imageset/Contents.json new file mode 100644 index 0000000000..b3b3c74e12 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_9.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "image_9.jpg" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_9.imageset/image_9.jpg b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_9.imageset/image_9.jpg new file mode 100644 index 0000000000..9fb6e47d3f Binary files /dev/null and b/examples/CollectionViewWithViewControllerCells/Sample/Images.xcassets/image_9.imageset/image_9.jpg differ diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Info.plist b/examples/CollectionViewWithViewControllerCells/Sample/Info.plist new file mode 100644 index 0000000000..eeb71a8d35 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + + CFBundleIdentifier + com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + Launchboard + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/CollectionViewWithViewControllerCells/Sample/Launchboard.storyboard b/examples/CollectionViewWithViewControllerCells/Sample/Launchboard.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/Launchboard.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/CollectionViewWithViewControllerCells/Sample/MosaicCollectionViewLayout.h b/examples/CollectionViewWithViewControllerCells/Sample/MosaicCollectionViewLayout.h new file mode 100644 index 0000000000..03b8af5f7c --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/MosaicCollectionViewLayout.h @@ -0,0 +1,30 @@ +// +// MosaicCollectionViewLayout.h +// Sample +// +// Created by McCallum, Levi on 11/22/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import +#import + +@interface MosaicCollectionViewLayout : UICollectionViewLayout + +@property (assign, nonatomic) NSUInteger numberOfColumns; +@property (assign, nonatomic) CGFloat columnSpacing; +@property (assign, nonatomic) UIEdgeInsets sectionInset; +@property (assign, nonatomic) UIEdgeInsets interItemSpacing; +@property (assign, nonatomic) CGFloat headerHeight; + +@end + +@protocol MosaicCollectionViewLayoutDelegate + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(MosaicCollectionViewLayout *)layout originalItemSizeAtIndexPath:(NSIndexPath *)indexPath; + +@end + +@interface MosaicCollectionViewLayoutInspector : NSObject + +@end \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/MosaicCollectionViewLayout.m b/examples/CollectionViewWithViewControllerCells/Sample/MosaicCollectionViewLayout.m new file mode 100644 index 0000000000..0e2c65d027 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/MosaicCollectionViewLayout.m @@ -0,0 +1,230 @@ +// +// MosaicCollectionViewLayout.m +// Sample +// +// Created by McCallum, Levi on 11/22/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import "MosaicCollectionViewLayout.h" + +@implementation MosaicCollectionViewLayout { + NSMutableArray *_columnHeights; + NSMutableArray *_itemAttributes; + NSMutableDictionary *_headerAttributes; + NSMutableArray *_allAttributes; +} + +- (instancetype)init +{ + self = [super init]; + if (self != nil) { + self.numberOfColumns = 3; + self.columnSpacing = 10.0; + self.sectionInset = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); + self.interItemSpacing = UIEdgeInsetsMake(10.0, 0, 10.0, 0); + } + return self; +} + +- (void)prepareLayout +{ + _itemAttributes = [NSMutableArray array]; + _columnHeights = [NSMutableArray array]; + _allAttributes = [NSMutableArray array]; + _headerAttributes = [NSMutableDictionary dictionary]; + + CGFloat top = 0; + + NSInteger numberOfSections = [self.collectionView numberOfSections]; + for (NSUInteger section = 0; section < numberOfSections; section++) { + NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section]; + + top += _sectionInset.top; + + if (_headerHeight > 0) { + CGSize headerSize = [self _headerSizeForSection:section]; + UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes + layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; + attributes.frame = CGRectMake(_sectionInset.left, top, headerSize.width, headerSize.height); + _headerAttributes[@(section)] = attributes; + [_allAttributes addObject:attributes]; + top = CGRectGetMaxY(attributes.frame); + } + + [_columnHeights addObject:[NSMutableArray array]]; + for (NSUInteger idx = 0; idx < self.numberOfColumns; idx++) { + [_columnHeights[section] addObject:@(top)]; + } + + CGFloat columnWidth = [self _columnWidthForSection:section]; + [_itemAttributes addObject:[NSMutableArray array]]; + for (NSUInteger idx = 0; idx < numberOfItems; idx++) { + NSUInteger columnIndex = [self _shortestColumnIndexInSection:section]; + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:section]; + + CGSize itemSize = [self _itemSizeAtIndexPath:indexPath]; + CGFloat xOffset = _sectionInset.left + (columnWidth + _columnSpacing) * columnIndex; + CGFloat yOffset = [_columnHeights[section][columnIndex] floatValue]; + + UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes + layoutAttributesForCellWithIndexPath:indexPath]; + attributes.frame = CGRectMake(xOffset, yOffset, itemSize.width, itemSize.height); + + _columnHeights[section][columnIndex] = @(CGRectGetMaxY(attributes.frame) + _interItemSpacing.bottom); + + [_itemAttributes[section] addObject:attributes]; + [_allAttributes addObject:attributes]; + } + + NSUInteger columnIndex = [self _tallestColumnIndexInSection:section]; + top = [_columnHeights[section][columnIndex] floatValue] - _interItemSpacing.bottom + _sectionInset.bottom; + + for (NSUInteger idx = 0; idx < [_columnHeights[section] count]; idx++) { + _columnHeights[section][idx] = @(top); + } + } +} + +- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect +{ + NSMutableArray *includedAttributes = [NSMutableArray array]; + // Slow search for small batches + for (UICollectionViewLayoutAttributes *attributes in _allAttributes) { + if (CGRectIntersectsRect(attributes.frame, rect)) { + [includedAttributes addObject:attributes]; + } + } + return includedAttributes; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section >= _itemAttributes.count) { + return nil; + } else if (indexPath.item >= [_itemAttributes[indexPath.section] count]) { + return nil; + } + return _itemAttributes[indexPath.section][indexPath.item]; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath +{ + if ([elementKind isEqualToString:UICollectionElementKindSectionHeader]) { + return _headerAttributes[@(indexPath.section)]; + } + return nil; +} + +- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds +{ + if (!CGRectEqualToRect(self.collectionView.bounds, newBounds)) { + return YES; + } + return NO; +} + +- (CGFloat)_widthForSection:(NSUInteger)section +{ + return self.collectionView.bounds.size.width - _sectionInset.left - _sectionInset.right; +} + +- (CGFloat)_columnWidthForSection:(NSUInteger)section +{ + return ([self _widthForSection:section] - ((_numberOfColumns - 1) * _columnSpacing)) / _numberOfColumns; +} + +- (CGSize)_itemSizeAtIndexPath:(NSIndexPath *)indexPath +{ + CGSize size = CGSizeMake([self _columnWidthForSection:indexPath.section], 0); + CGSize originalSize = [[self _delegate] collectionView:self.collectionView layout:self originalItemSizeAtIndexPath:indexPath]; + if (originalSize.height > 0 && originalSize.width > 0) { + size.height = originalSize.height / originalSize.width * size.width; + } + return size; +} + +- (CGSize)_headerSizeForSection:(NSUInteger)section +{ + return CGSizeMake([self _widthForSection:section], _headerHeight); +} + +- (CGSize)collectionViewContentSize +{ + CGFloat height = [[[_columnHeights lastObject] firstObject] floatValue]; + return CGSizeMake(self.collectionView.bounds.size.width, height); +} + +- (NSUInteger)_tallestColumnIndexInSection:(NSUInteger)section +{ + __block NSUInteger index = 0; + __block CGFloat tallestHeight = 0; + [_columnHeights[section] enumerateObjectsUsingBlock:^(NSNumber *height, NSUInteger idx, BOOL *stop) { + if (height.floatValue > tallestHeight) { + index = idx; + tallestHeight = height.floatValue; + } + }]; + return index; +} + +- (NSUInteger)_shortestColumnIndexInSection:(NSUInteger)section +{ + __block NSUInteger index = 0; + __block CGFloat shortestHeight = CGFLOAT_MAX; + [_columnHeights[section] enumerateObjectsUsingBlock:^(NSNumber *height, NSUInteger idx, BOOL *stop) { + if (height.floatValue < shortestHeight) { + index = idx; + shortestHeight = height.floatValue; + } + }]; + return index; +} + +- (id)_delegate +{ + return (id)self.collectionView.delegate; +} + +@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]); +} + +/** + * Asks the inspector for the number of supplementary sections in the collection view for the given kind. + */ +- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind +{ + if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { + return [[collectionView asyncDataSource] numberOfSectionsInCollectionView:collectionView]; + } else { + return 0; + } +} + +/** + * 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 \ No newline at end of file diff --git a/examples/CollectionViewWithViewControllerCells/Sample/SupplementaryNode.h b/examples/CollectionViewWithViewControllerCells/Sample/SupplementaryNode.h new file mode 100644 index 0000000000..f75c929684 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/SupplementaryNode.h @@ -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 + +@interface SupplementaryNode : ASCellNode + +- (instancetype)initWithText:(NSString *)text; + +@end diff --git a/examples/CollectionViewWithViewControllerCells/Sample/SupplementaryNode.m b/examples/CollectionViewWithViewControllerCells/Sample/SupplementaryNode.m new file mode 100644 index 0000000000..76ba17b4b6 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/SupplementaryNode.m @@ -0,0 +1,52 @@ +/* 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 +#import +#import + +@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 = ASCenterLayoutSpecCenteringY; + center.child = _textNode; + return center; +} + +#pragma mark - Text Formatting + +- (NSDictionary *)textAttributes +{ + return @{ + NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline], + NSForegroundColorAttributeName: [UIColor grayColor], + }; +} + +@end diff --git a/examples/CollectionViewWithViewControllerCells/Sample/ViewController.h b/examples/CollectionViewWithViewControllerCells/Sample/ViewController.h new file mode 100644 index 0000000000..d0e9200d88 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/ViewController.h @@ -0,0 +1,16 @@ +/* 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 + +@interface ViewController : UIViewController + +@end diff --git a/examples/CollectionViewWithViewControllerCells/Sample/ViewController.m b/examples/CollectionViewWithViewControllerCells/Sample/ViewController.m new file mode 100644 index 0000000000..36f0ef5011 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/ViewController.m @@ -0,0 +1,124 @@ +/* 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 "ViewController.h" + +#import +#import "MosaicCollectionViewLayout.h" +#import "SupplementaryNode.h" +#import "ImageViewController.h" + +static NSUInteger kNumberOfImages = 14; + +@interface ViewController () +{ + NSMutableArray *_sections; + ASCollectionView *_collectionView; + MosaicCollectionViewLayoutInspector *_layoutInspector; +} + +@end + +@implementation ViewController + +#pragma mark - +#pragma mark UIViewController. + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + _sections = [NSMutableArray array]; + [_sections addObject:[NSMutableArray array]]; + for (NSUInteger idx = 0, section = 0; idx < kNumberOfImages; idx++) { + NSString *name = [NSString stringWithFormat:@"image_%lu.jpg", (unsigned long)idx]; + [_sections[section] addObject:[UIImage imageNamed:name]]; + if ((idx + 1) % 5 == 0 && idx < kNumberOfImages - 1) { + section++; + [_sections addObject:[NSMutableArray array]]; + } + } + + MosaicCollectionViewLayout *layout = [[MosaicCollectionViewLayout alloc] init]; + layout.numberOfColumns = 2; + layout.headerHeight = 44.0; + + _layoutInspector = [[MosaicCollectionViewLayoutInspector alloc] init]; + + _collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:YES]; + _collectionView.asyncDataSource = self; + _collectionView.asyncDelegate = self; + _collectionView.layoutInspector = _layoutInspector; + _collectionView.backgroundColor = [UIColor whiteColor]; + + [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self.view addSubview:_collectionView]; +} + +- (void)viewWillLayoutSubviews +{ + _collectionView.frame = self.view.bounds; +} + +- (BOOL)prefersStatusBarHidden +{ + return YES; +} + +- (void)reloadTapped +{ + [_collectionView reloadData]; +} + +#pragma mark - +#pragma mark ASCollectionView data source. + +- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + ASCellNode *node = [[ASCellNode alloc] initWithViewControllerBlock:^UIViewController *{ + return [[ImageViewController alloc] initWithImage:_sections[indexPath.section][indexPath.item]]; + } didLoadBlock:nil]; + + node.layer.borderWidth = 1.0; + node.layer.borderColor = [UIColor blackColor].CGColor; + + return node; +} + +- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + NSString *text = [NSString stringWithFormat:@"Section %d", (int)indexPath.section + 1]; + return [[SupplementaryNode alloc] initWithText:text]; +} + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return _sections.count; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return [_sections[section] count]; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout originalItemSizeAtIndexPath:(NSIndexPath *)indexPath +{ + return [(UIImage *)_sections[indexPath.section][indexPath.item] size]; +} + +@end diff --git a/examples/CollectionViewWithViewControllerCells/Sample/main.m b/examples/CollectionViewWithViewControllerCells/Sample/main.m new file mode 100644 index 0000000000..592423d8f6 --- /dev/null +++ b/examples/CollectionViewWithViewControllerCells/Sample/main.m @@ -0,0 +1,19 @@ +/* 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 +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata b/examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/PagerNode/Default-568h@2x.png b/examples/PagerNode/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples/PagerNode/Default-568h@2x.png differ diff --git a/examples/PagerNode/Default-667h@2x.png b/examples/PagerNode/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples/PagerNode/Default-667h@2x.png differ diff --git a/examples/PagerNode/Default-736h@3x.png b/examples/PagerNode/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples/PagerNode/Default-736h@3x.png differ diff --git a/examples/PagerNode/Podfile b/examples/PagerNode/Podfile new file mode 100644 index 0000000000..6c012e3c04 --- /dev/null +++ b/examples/PagerNode/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/PagerNode/Sample.xcodeproj/project.pbxproj b/examples/PagerNode/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..ad6e24433a --- /dev/null +++ b/examples/PagerNode/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,358 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 252041E21C167DFC00E264C8 /* PageNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 252041E11C167DFC00E264C8 /* PageNode.m */; }; + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + 252041E01C167DFC00E264C8 /* PageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PageNode.h; sourceTree = ""; }; + 252041E11C167DFC00E264C8 /* PageNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PageNode.m; sourceTree = ""; }; + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 252041E01C167DFC00E264C8 /* PageNode.h */, + 252041E11C167DFC00E264C8 /* PageNode.m */, + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */, + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0710; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "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"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + 252041E21C167DFC00E264C8 /* PageNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/PagerNode/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/PagerNode/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/PagerNode/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/PagerNode/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/PagerNode/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..0b71c455d1 --- /dev/null +++ b/examples/PagerNode/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/PagerNode/Sample.xcworkspace/contents.xcworkspacedata b/examples/PagerNode/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/PagerNode/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/PagerNode/Sample/AppDelegate.h b/examples/PagerNode/Sample/AppDelegate.h new file mode 100644 index 0000000000..2aa29369b4 --- /dev/null +++ b/examples/PagerNode/Sample/AppDelegate.h @@ -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 + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/PagerNode/Sample/AppDelegate.m b/examples/PagerNode/Sample/AppDelegate.m new file mode 100644 index 0000000000..1dea563b77 --- /dev/null +++ b/examples/PagerNode/Sample/AppDelegate.m @@ -0,0 +1,27 @@ +/* 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 "AppDelegate.h" + +#import "ViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/PagerNode/Sample/Info.plist b/examples/PagerNode/Sample/Info.plist new file mode 100644 index 0000000000..fb4115c84c --- /dev/null +++ b/examples/PagerNode/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/PagerNode/Sample/PageNode.h b/examples/PagerNode/Sample/PageNode.h new file mode 100644 index 0000000000..b8843378de --- /dev/null +++ b/examples/PagerNode/Sample/PageNode.h @@ -0,0 +1,13 @@ +// +// PageNode.h +// Sample +// +// Created by McCallum, Levi on 12/7/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import + +@interface PageNode : ASCellNode + +@end diff --git a/examples/PagerNode/Sample/PageNode.m b/examples/PagerNode/Sample/PageNode.m new file mode 100644 index 0000000000..2a60c8982b --- /dev/null +++ b/examples/PagerNode/Sample/PageNode.m @@ -0,0 +1,24 @@ +// +// PageNode.m +// Sample +// +// Created by McCallum, Levi on 12/7/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "PageNode.h" + +@implementation PageNode + +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize +{ + return [ASLayout layoutWithLayoutableObject:self size:constrainedSize.max]; +} + +- (void)fetchData +{ + [super fetchData]; + NSLog(@"Fetching data for node: %@", self); +} + +@end diff --git a/examples/PagerNode/Sample/ViewController.h b/examples/PagerNode/Sample/ViewController.h new file mode 100644 index 0000000000..d4ec993c7b --- /dev/null +++ b/examples/PagerNode/Sample/ViewController.h @@ -0,0 +1,16 @@ +/* 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 + +@interface ViewController : ASViewController + +@end diff --git a/examples/PagerNode/Sample/ViewController.m b/examples/PagerNode/Sample/ViewController.m new file mode 100644 index 0000000000..e10d013ec8 --- /dev/null +++ b/examples/PagerNode/Sample/ViewController.m @@ -0,0 +1,57 @@ +/* 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 "ViewController.h" + +#import + +#import "PageNode.h" + +@interface ViewController () + +- (ASPagerNode *)node; + +@end + +@implementation ViewController + +- (instancetype)init +{ + if (!(self = [super initWithNode:[[ASPagerNode alloc] init]])) + return nil; + + [self node].dataSource = self; + + self.title = @"Pages"; + + return self; +} + +- (ASPagerNode *)node +{ + return (ASPagerNode *)[super node]; +} + +#pragma mark - ASPagerNodeDataSource + +- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode +{ + return 5; +} + +- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index +{ + PageNode *page = [[PageNode alloc] init]; + page.backgroundColor = [UIColor blueColor]; + return page; +} + +@end diff --git a/examples/PagerNode/Sample/main.m b/examples/PagerNode/Sample/main.m new file mode 100644 index 0000000000..ae9488711c --- /dev/null +++ b/examples/PagerNode/Sample/main.m @@ -0,0 +1,20 @@ +/* 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 + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata b/examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + +