diff --git a/AsyncDisplayKit-Prefix.gcda b/AsyncDisplayKit-Prefix.gcda index 450c9162de..72e86999c9 100644 Binary files a/AsyncDisplayKit-Prefix.gcda and b/AsyncDisplayKit-Prefix.gcda differ 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 4269a60b2e..2e0296d33b 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 */; }; @@ -523,7 +527,7 @@ 057D02C51AC0A66700C7AC3C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 057D02C61AC0A66700C7AC3C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEditableTextNode.h; sourceTree = ""; }; - 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASEditableTextNode.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASEditableTextNode.mm; sourceTree = ""; }; 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAsyncDisplayKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; 058D09AF195D04C000B7D73C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 058D09B3195D04C000B7D73C /* AsyncDisplayKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit-Prefix.pch"; sourceTree = ""; }; @@ -644,6 +648,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 = ""; }; @@ -893,6 +899,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 */, @@ -1246,6 +1254,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 */, @@ -1438,6 +1447,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 */, @@ -1513,6 +1523,7 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, + 1B86F48711505F91D5FEF571 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1612,6 +1623,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; @@ -1687,6 +1713,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 */, 0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */, @@ -1815,6 +1842,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 5496725f0f..d1e575383e 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -95,6 +95,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 ae73d5c1e6..16d8f32d83 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 8292280a57..efd7ef18f1 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -6,7 +6,7 @@ // Copyright (c) 2015 Facebook. All rights reserved. // -#import +#import NS_ASSUME_NONNULL_BEGIN @@ -20,6 +20,50 @@ NS_ASSUME_NONNULL_BEGIN @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 NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.m index aac4989325..ddef197cca 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.m @@ -7,6 +7,7 @@ // #import "ASCollectionNode.h" +#import "ASDisplayNode+Subclasses.h" @implementation ASCollectionNode @@ -43,4 +44,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 44ac1b2ca0..5c4d967051 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -24,6 +24,11 @@ NS_ASSUME_NONNULL_BEGIN */ typedef UIView * _Nonnull(^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. */ @@ -687,7 +692,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. */ @@ -698,7 +702,6 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) * @param node The node to be added. */ - (void)addSubnode:(ASDisplayNode *)node; -- (NSString *)name; @end diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 0ca022172e..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,9 +1871,29 @@ 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."); - subnodeFrame.origin = subnodeLayout.position; - subnodeFrame.size = subnodeLayout.size; + 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"); + adjustedOrigin.x = 0; + } + if (isfinite(adjustedOrigin.y) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedOrigin.y = 0; + } + subnodeFrame.origin = adjustedOrigin; + + CGSize adjustedSize = subnodeLayout.size; + if (isfinite(adjustedSize.width) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid size"); + adjustedSize.width = 0; + } + if (isfinite(adjustedSize.height) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedSize.height = 0; + } + subnodeFrame.size = adjustedSize; + subnode = ((ASDisplayNode *)subnodeLayout.layoutableObject); [subnode setFrame:subnodeFrame]; } @@ -2107,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); @@ -2250,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/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index eb450b1c25..6feaacbe1e 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -13,7 +13,10 @@ NS_ASSUME_NONNULL_BEGIN @interface ASMapNode : ASImageNode -- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate NS_DESIGNATED_INITIALIZER; +/** + The current region of ASMapNode. This can be set at any time and ASMapNode will animate the change. + */ +@property (nonatomic, assign) MKCoordinateRegion region; /** This is the MKMapView that is the live map part of ASMapNode. This will be nil if .liveMap = NO. Note, MKMapView is *not* thread-safe. diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index 311000362e..80a76fc82d 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -17,70 +17,80 @@ MKMapSnapshotter *_snapshotter; MKMapSnapshotOptions *_options; NSArray *_annotations; - ASDisplayNode *_mapNode; CLLocationCoordinate2D _centerCoordinateOfMap; } @end @implementation ASMapNode -@synthesize liveMap = _liveMap; @synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange; @synthesize mapDelegate = _mapDelegate; +@synthesize region = _region; -- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate +#pragma mark - Lifecycle +- (instancetype)init { if (!(self = [super init])) { return nil; } self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); self.clipsToBounds = YES; - + _needsMapReloadOnBoundsChange = YES; - _liveMap = NO; + _centerCoordinateOfMap = kCLLocationCoordinate2DInvalid; - + _region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(43.432858, 13.183671), MKCoordinateSpanMake(0.2, 0.2)); + _options = [[MKMapSnapshotOptions alloc] init]; - _options.region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000);; + _options.region = _region; return self; } -- (void)setAnnotations:(NSArray *)annotations +- (void)didLoad { - ASDN::MutexLocker l(_propertyLock); - _annotations = [annotations copy]; - if (annotations.count != _annotations.count) { - // Redraw - [self setNeedsDisplay]; + [super didLoad]; + if ([self wasLiveMapPreviously]) { + self.userInteractionEnabled = YES; + [self addLiveMap]; } } -- (void)setUpSnapshotter +- (void)fetchData { - if (!_snapshotter) { - ASDisplayNodeAssert(!CGSizeEqualToSize(CGSizeZero, self.calculatedSize), @"self.calculatedSize can not be zero. Make sure that you are setting a preferredFrameSize or wrapping ASMapNode in a ASRatioLayoutSpec or similar."); - _options.size = self.calculatedSize; - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; + [super fetchData]; + if ([self wasLiveMapPreviously]) { + [self addLiveMap]; + } else { + [self setUpSnapshotter]; + [self takeSnapshot]; } } +- (void)clearFetchedData +{ + [super clearFetchedData]; + if (self.isLiveMap) { + [self removeLiveMap]; + } +} + +#pragma mark - Settings + - (BOOL)isLiveMap { - ASDN::MutexLocker l(_propertyLock); - return _liveMap; + return (_mapView != nil); } - (void)setLiveMap:(BOOL)liveMap { - ASDN::MutexLocker l(_propertyLock); - if (liveMap == _liveMap) { - return; - } - _liveMap = liveMap; liveMap ? [self addLiveMap] : [self removeLiveMap]; } +- (BOOL)wasLiveMapPreviously +{ + return CLLocationCoordinate2DIsValid(_centerCoordinateOfMap); +} - (BOOL)needsMapReloadOnBoundsChange { @@ -94,23 +104,26 @@ _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; } -- (void)fetchData +- (MKCoordinateRegion)region { - [super fetchData]; - if (_liveMap && !_mapNode) { - [self addLiveMap]; - } - else { - [self setUpSnapshotter]; + ASDN::MutexLocker l(_propertyLock); + return _region; +} + +- (void)setRegion:(MKCoordinateRegion)region +{ + ASDN::MutexLocker l(_propertyLock); + _region = region; + if (self.isLiveMap) { + [_mapView setRegion:_region animated:YES]; + } else { + _options.region = _region; + [self resetSnapshotter]; [self takeSnapshot]; } } -- (void)clearFetchedData -{ - [super clearFetchedData]; - [self removeLiveMap]; -} +#pragma mark - Snapshotter - (void)takeSnapshot { @@ -150,42 +163,60 @@ } } -- (void)resetSnapshotter +- (void)setUpSnapshotter { - if (!_snapshotter.isLoading) { + if (!_snapshotter) { + ASDisplayNodeAssert(!CGSizeEqualToSize(CGSizeZero, self.calculatedSize), @"self.calculatedSize can not be zero. Make sure that you are setting a preferredFrameSize or wrapping ASMapNode in a ASRatioLayoutSpec or similar."); _options.size = self.calculatedSize; _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; } } -#pragma mark - Action +- (void)resetSnapshotter +{ + if (!_snapshotter.isLoading) { + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; + } +} + +#pragma mark - Actions - (void)addLiveMap { - if (self.isNodeLoaded && !_mapNode) { - _mapNode = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ - _mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)]; - _mapView.delegate = _mapDelegate; - [_mapView setRegion:_options.region]; - [_mapView addAnnotations:_annotations]; - return _mapView; - }]; - [self addSubnode:_mapNode]; + ASDisplayNodeAssertMainThread(); + if (!self.isLiveMap) { + __weak ASMapNode *weakSelf = self; + _mapView = [[MKMapView alloc] initWithFrame:CGRectZero]; + _mapView.delegate = weakSelf.mapDelegate; + [_mapView setRegion:_options.region]; + [_mapView addAnnotations:_annotations]; + [weakSelf setNeedsLayout]; + [weakSelf.view addSubview:_mapView]; if (CLLocationCoordinate2DIsValid(_centerCoordinateOfMap)) { [_mapView setCenterCoordinate:_centerCoordinateOfMap]; + } else { + _centerCoordinateOfMap = _options.region.center; } } } - (void)removeLiveMap { - if (_mapNode) { - _centerCoordinateOfMap = _mapView.centerCoordinate; - [_mapNode removeFromSupernode]; - _mapView = nil; - _mapNode = nil; + _centerCoordinateOfMap = _mapView.centerCoordinate; + [_mapView removeFromSuperview]; + _mapView = nil; +} + +- (void)setAnnotations:(NSArray *)annotations +{ + ASDN::MutexLocker l(_propertyLock); + _annotations = [annotations copy]; + if (self.isLiveMap) { + [_mapView removeAnnotations:_mapView.annotations]; + [_mapView addAnnotations:annotations]; + } else { + [self takeSnapshot]; } - self.image = nil; } #pragma mark - Layout @@ -193,18 +224,15 @@ - (void)layout { [super layout]; - if (_mapView) { + if (self.isLiveMap) { _mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); - } - else { + } else { // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. - if (!CGSizeEqualToSize(_options.size, self.bounds.size)) { - if (_needsMapReloadOnBoundsChange && self.image) { - [self resetSnapshotter]; - [self takeSnapshot]; - } + if (!CGSizeEqualToSize(_options.size, self.bounds.size) && _needsMapReloadOnBoundsChange) { + _options.size = self.bounds.size; + [self resetSnapshotter]; + [self takeSnapshot]; } } } - -@end +@end \ No newline at end of file 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/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index 5946fe46d4..8014db0f06 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -196,6 +196,9 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { /** @abstract Responds to actions from links in the text node. + @discussion The delegate must be set before the node is loaded, and implement + textNode:longPressedLinkAttribute:value:atPoint:textRange: in order for + the long press gesture recognizer to be installed. */ @property (nonatomic, weak) id delegate; @@ -235,6 +238,8 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { @param value The value of the tapped attribute. @param point The point within textNode, in textNode's coordinate system, that was tapped. @param textRange The range of highlighted text. + @discussion In addition to implementing this method, the delegate must be set on the text + node before it is loaded (the recognizer is created in -didLoad) */ - (void)textNode:(ASTextNode *)textNode longPressedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange; diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 315638093f..56490d18ba 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -23,6 +23,7 @@ #import "ASInternalHelpers.h" #import "ASEqualityHelpers.h" +#import "ASLayout.h" static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15; static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1; @@ -211,8 +212,9 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation { [super didLoad]; - // If we are view-backed, support gesture interaction. - if (!self.isLayerBacked) { + // If we are view-backed and the delegate cares, support the long-press callback. + SEL longPressCallback = @selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:); + if (!self.isLayerBacked && [self.delegate respondsToSelector:longPressCallback]) { _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_handleLongPress:)]; _longPressGestureRecognizer.cancelsTouchesInView = self.longPressCancelsTouches; _longPressGestureRecognizer.delegate = self; @@ -289,7 +291,29 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (BOOL)_needInvalidateRenderer:(CGSize)newSize { - return !CGSizeEqualToSize(newSize, _constrainedSize); + if (!_renderer) { + return YES; + } + + // If the size is not the same as the constraint we provided to the renderer, start out assuming we need + // a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated. + CGSize oldSize = _renderer.constrainedSize; + + if (CGSizeEqualToSize(newSize, oldSize)) { + return NO; + } else { + // It is very common to have a constrainedSize with a concrete, specific width but +Inf height. + // In this case, as long as the text node has bounds as large as the full calculatedLayout suggests, + // it means that the text has all the room it needs (as it was not vertically bounded). So, we will not + // experience truncation and don't need to recreate the renderer with the size it already calculated, + // as this would essentially serve to set its constrainedSize to be its calculatedSize (unnecessary). + ASLayout *layout = self.calculatedLayout; + if (layout != nil && CGSizeEqualToSize(newSize, layout.size)) { + return NO; + } else { + return YES; + } + } } #pragma mark - Modifying User Text @@ -305,6 +329,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation _attributedString = ASCleanseAttributedStringOfCoreTextAttributes(attributedString); + // Sync the truncation string with attributes from the updated _attributedString + // Without this, the size calculation of the text with truncation applied will + // not take into account the attributes of attributedString in the last line + [self _updateComposedTruncationString]; + // We need an entirely new renderer [self _invalidateRenderer]; @@ -1024,9 +1053,14 @@ static NSAttributedString *DefaultTruncationAttributedString() #pragma mark - Truncation Message -- (void)_invalidateTruncationString +- (void)_updateComposedTruncationString { _composedTruncationString = [self _prepareTruncationStringForDrawing:[self _composedTruncationString]]; +} + +- (void)_invalidateTruncationString +{ + [self _updateComposedTruncationString]; [self _invalidateRenderer]; ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ [self setNeedsDisplay]; 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 a3a1b418ed..38b7b5a042 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.h +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.h @@ -17,6 +17,9 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)sharedImageDownloader; ++ (instancetype)new __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); +- (instancetype)init __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); + @end NS_ASSUME_NONNULL_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/Layout/ASDimension.mm b/AsyncDisplayKit/Layout/ASDimension.mm index a0737f6682..a1e42c4b76 100644 --- a/AsyncDisplayKit/Layout/ASDimension.mm +++ b/AsyncDisplayKit/Layout/ASDimension.mm @@ -57,7 +57,7 @@ CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat parent case ASRelativeDimensionTypePoints: return dimension.value; case ASRelativeDimensionTypePercent: - return round(dimension.value * parent); + return dimension.value * parent; } } diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index 92e2d307ec..745b325e52 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -11,6 +11,7 @@ #import "ASLayout.h" #import "ASAssert.h" #import "ASLayoutSpecUtilities.h" +#import "ASInternalHelpers.h" #import CGPoint const CGPointNull = {NAN, NAN}; @@ -37,8 +38,12 @@ extern BOOL CGPointIsNull(CGPoint point) ASLayout *l = [super new]; if (l) { l->_layoutableObject = layoutableObject; - l->_size = size; - l->_position = position; + l->_size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height)); + if (CGPointIsNull(position) == NO) { + l->_position = CGPointMake(ASCeilPixelValue(position.x), ASCeilPixelValue(position.y)); + } else { + l->_position = position; + } l->_sublayouts = [sublayouts copy]; } return l; 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 dcc2ea26f2..d5f4c2043e 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -21,6 +21,10 @@ NS_ASSUME_NONNULL_BEGIN +// 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. @@ -35,14 +39,17 @@ NS_ASSUME_NONNULL_BEGIN 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/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m b/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m index e317559572..d2f7465349 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m +++ b/AsyncDisplayKit/TextKit/ASTextKitCoreTextAdditions.m @@ -88,7 +88,7 @@ NSDictionary *NSAttributedStringAttributesForCoreTextAttributes(NSDictionary *co cleanAttributes[NSForegroundColorAttributeName] = [UIColor colorWithCGColor:(CGColorRef)coreTextValue]; } // kCTParagraphStyleAttributeName -> NSParagraphStyleAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTParagraphStyleAttributeName]) { + else if ([coreTextKey isEqualToString:(NSString *)kCTParagraphStyleAttributeName] && ![coreTextValue isKindOfClass:[NSParagraphStyle class]]) { cleanAttributes[NSParagraphStyleAttributeName] = [NSParagraphStyle paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextValue]; } // kCTStrokeWidthAttributeName -> NSStrokeWidthAttributeName diff --git a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm index 61764eaa95..4988d286d6 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitTailTruncater.mm @@ -62,6 +62,7 @@ effectiveRange:NULL]; NSParagraphStyle *paragraphStyle = [textStorage attributesAtIndex:[layoutManager characterIndexForGlyphAtIndex:lastVisibleGlyphIndex] effectiveRange:NULL][NSParagraphStyleAttributeName]; + // We assume LTR so long as the writing direction is not BOOL rtlWritingDirection = paragraphStyle ? paragraphStyle.baseWritingDirection == NSWritingDirectionRightToLeft : NO; // We only want to treat the trunction rect as left-aligned in the case that we are right-aligned and our writing 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 f67fcad822..4c45aeadba 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) { @@ -1343,9 +1343,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]; @@ -1353,6 +1356,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point [b release]; [c release]; [d release]; + [e release]; } - (void)testAppleBugInsertSubview @@ -1416,11 +1420,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/AsyncDisplayKitTests/ASTextKitCoreTextAdditionsTests.m b/AsyncDisplayKitTests/ASTextKitCoreTextAdditionsTests.m index a33c9e2313..6fa7fd7aed 100644 --- a/AsyncDisplayKitTests/ASTextKitCoreTextAdditionsTests.m +++ b/AsyncDisplayKitTests/ASTextKitCoreTextAdditionsTests.m @@ -12,6 +12,11 @@ #import "ASTextKitCoreTextAdditions.h" +BOOL floatsCloseEnough(CGFloat float1, CGFloat float2) { + CGFloat epsilon = 0.00001; + return (fabs(float1 - float2) < epsilon); +} + @interface ASTextKitCoreTextAdditionsTests : XCTestCase @end @@ -44,5 +49,21 @@ XCTAssertTrue([testString isEqualToAttributedString:actualCleansedString], @"Expected the output string %@ to be the same as the input %@ if there are no core text attributes", actualCleansedString, testString); } +- (void)testNSParagraphStyleNoCleansing +{ + NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + paragraphStyle.lineSpacing = 10.0; + + //NSUnderlineStyleAttributeName flags the unsupported CT attribute check + NSDictionary *attributes = @{NSParagraphStyleAttributeName:paragraphStyle, + NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle)}; + + NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"Test" attributes:attributes]; + NSAttributedString *cleansedString = ASCleanseAttributedStringOfCoreTextAttributes(attributedString); + + NSParagraphStyle *cleansedParagraphStyle = [cleansedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL]; + + XCTAssertTrue(floatsCloseEnough(cleansedParagraphStyle.lineSpacing, paragraphStyle.lineSpacing), @"Expected the output line spacing: %f to be equal to the input line spacing: %f", cleansedParagraphStyle.lineSpacing, paragraphStyle.lineSpacing); +} @end diff --git a/AsyncDisplayKitTests/ASTextNodeTests.m b/AsyncDisplayKitTests/ASTextNodeTests.m index dec0625867..7409e6d6de 100644 --- a/AsyncDisplayKitTests/ASTextNodeTests.m +++ b/AsyncDisplayKitTests/ASTextNodeTests.m @@ -54,7 +54,7 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta) { [super setUp]; _textNode = [[ASTextNode alloc] init]; - + UIFontDescriptor *desc = [UIFontDescriptor fontDescriptorWithName:@"Didot" size:18]; NSArray *arr = @@ -69,7 +69,17 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta) [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." attributes:d]; NSMutableParagraphStyle *para = [NSMutableParagraphStyle new]; para.alignment = NSTextAlignmentCenter; - [mas addAttribute:NSParagraphStyleAttributeName value:para range:NSMakeRange(0,mas.length)]; + para.lineSpacing = 1.0; + [mas addAttribute:NSParagraphStyleAttributeName value:para + range:NSMakeRange(0, mas.length - 1)]; + + // Vary the linespacing on the last line + NSMutableParagraphStyle *lastLinePara = [NSMutableParagraphStyle new]; + lastLinePara.alignment = para.alignment; + lastLinePara.lineSpacing = 5.0; + [mas addAttribute:NSParagraphStyleAttributeName value:lastLinePara + range:NSMakeRange(mas.length - 1, 1)]; + _attributedString = mas; _textNode.attributedString = _attributedString; } 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 @@ + + + + + + +