From 03bd1f4358955dd85a420330d85bf38a065054cd Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 27 Oct 2015 11:40:43 -0700 Subject: [PATCH 01/13] Expose ASViewController's constrained size for subclass customization --- AsyncDisplayKit/ASViewController.h | 9 +++++++++ AsyncDisplayKit/ASViewController.m | 12 +++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index f617ed4c43..281a5d0e06 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -20,4 +20,13 @@ - (instancetype)initWithNode:(ASDisplayNode *)node; +/** + * The constrained size used to measure the backing node. + * + * @discussion Defaults to providing a size range that uses the view controller view's bounds as + * both the min and max definitions. Override this method to provide a custom size range to the + * backing node. + */ +- (ASSizeRange)nodeConstrainedSize; + @end diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 15fe664323..3509ccbe85 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -40,9 +40,7 @@ - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; - CGSize viewSize = self.view.bounds.size; - ASSizeRange constrainedSize = ASSizeRangeMake(viewSize, viewSize); - [_node measureWithSizeRange:constrainedSize]; + [_node measureWithSizeRange:[self nodeConstrainedSize]]; } - (void)viewDidLayoutSubviews @@ -61,4 +59,12 @@ [_node recursivelyFetchData]; } +// MARK: - Layout Helpers + +- (ASSizeRange)nodeConstrainedSize +{ + CGSize viewSize = self.view.bounds.size; + return ASSizeRangeMake(viewSize, viewSize); +} + @end From 50485663b94d23eeaa9f1201f2461ccd34d0fa73 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 28 Oct 2015 16:09:56 -0700 Subject: [PATCH 02/13] Never use setAnimationsEnabled: so we don't risk corrupting enabledness --- AsyncDisplayKit/ASCollectionView.mm | 28 ++++++--------------- AsyncDisplayKit/ASTableView.mm | 20 --------------- AsyncDisplayKit/Private/ASInternalHelpers.h | 18 +++++++++++++ 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index c123914ab7..a60b20ba62 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -789,26 +789,14 @@ static BOOL _isInterceptedSelector(SEL sel) } return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - - BOOL animationsEnabled = NO; - - if (!animated) { - animationsEnabled = [UIView areAnimationsEnabled]; - [UIView setAnimationsEnabled:NO]; - } - - [super performBatchUpdates:^{ - [_batchUpdateBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) { - block(); - }]; - } completion:^(BOOL finished) { - if (!animated) { - [UIView setAnimationsEnabled:animationsEnabled]; - } - if (completion) { - completion(finished); - } - }]; + + ASPerformBlockWithoutAnimation(!animated, ^{ + [super performBatchUpdates:^{ + [_batchUpdateBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) { + block(); + }]; + } completion:completion]; + }); [_batchUpdateBlocks removeAllObjects]; _performingBatchUpdates = NO; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 6729242e9c..06991451e5 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -180,26 +180,6 @@ static BOOL _isInterceptedSelector(SEL sel) @implementation ASTableView -/** - @summary Conditionally performs UIView geometry changes in the given block without animation. - - Used primarily to circumvent UITableView forcing insertion animations when explicitly told not to via - `UITableViewRowAnimationNone`. More info: https://github.com/facebook/AsyncDisplayKit/pull/445 - - @param withoutAnimation Set to `YES` to perform given block without animation - @param block Perform UIView geometry changes within the passed block - */ -void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - if (withoutAnimation) { - BOOL animationsEnabled = [UIView areAnimationsEnabled]; - [UIView setAnimationsEnabled:NO]; - block(); - [UIView setAnimationsEnabled:animationsEnabled]; - } else { - block(); - } -} - + (Class)dataControllerClass { return [ASChangeSetDataController class]; diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 00bed4b1d6..dcd6b38b72 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -10,6 +10,7 @@ #include #import +#import #import "ASBaseDefines.h" ASDISPLAYNODE_EXTERN_C_BEGIN @@ -27,6 +28,23 @@ CGFloat ASRoundPixelValue(CGFloat f); ASDISPLAYNODE_EXTERN_C_END +/** + @summary Conditionally performs UIView geometry changes in the given block without animation. + + Used primarily to circumvent UITableView forcing insertion animations when explicitly told not to via + `UITableViewRowAnimationNone`. More info: https://github.com/facebook/AsyncDisplayKit/pull/445 + + @param withoutAnimation Set to `YES` to perform given block without animation + @param block Perform UIView geometry changes within the passed block + */ +ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { + if (withoutAnimation) { + [UIView performWithoutAnimation:block]; + } else { + block(); + } +} + @interface NSIndexPath (ASInverseComparison) - (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath; @end From cd31f884ddd630e4b1ad5454e95d653027d2f228 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 29 Oct 2015 15:58:43 +0200 Subject: [PATCH 03/13] Move ASDisplayNodePerformBlockOnMainThread to ASInternalHelpers --- AsyncDisplayKit/ASCollectionView.mm | 3 +-- AsyncDisplayKit/ASDisplayNode.mm | 13 +------------ AsyncDisplayKit/ASImageNode.mm | 6 +++--- AsyncDisplayKit/ASTableView.mm | 3 +-- AsyncDisplayKit/Details/ASDataController.mm | 16 ++++++++-------- AsyncDisplayKit/Details/ASRangeController.mm | 14 +++++++------- .../Private/ASDisplayNode+UIViewBridge.mm | 2 +- AsyncDisplayKit/Private/ASDisplayNodeInternal.h | 1 - AsyncDisplayKit/Private/ASInternalHelpers.h | 1 + AsyncDisplayKit/Private/ASInternalHelpers.mm | 11 +++++++++++ 10 files changed, 34 insertions(+), 36 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index c123914ab7..787d611e8c 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -12,7 +12,6 @@ #import "ASCollectionViewLayoutController.h" #import "ASRangeController.h" #import "ASCollectionDataController.h" -#import "ASDisplayNodeInternal.h" #import "ASBatchFetching.h" #import "UICollectionViewLayout+ASConvenience.h" #import "ASInternalHelpers.h" @@ -269,7 +268,7 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)reloadDataWithCompletion:(void (^)())completion { ASDisplayNodeAssert(self.asyncDelegate, @"ASCollectionView's asyncDelegate property must be set."); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _superIsPendingDataLoad = YES; [super reloadData]; }); diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index c299cb0d9c..dde440f2e2 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -57,17 +57,6 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); } -void ASDisplayNodePerformBlockOnMainThread(void (^block)()) -{ - if ([NSThread isMainThread]) { - block(); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - block(); - }); - } -} - void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()) { ASDisplayNodeCAssertNotNil(block, @"block is required"); @@ -79,7 +68,7 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block) // Hold the lock to avoid a race where the node gets loaded while the block is in-flight. ASDN::MutexLocker l(node->_propertyLock); if (node.nodeLoaded) { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ block(); }); } else { diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index fc5aa93dc6..3de855564b 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -128,7 +128,7 @@ _image = image; ASDN::MutexUnlocker u(_imageLock); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [self invalidateCalculatedLayout]; [self setNeedsDisplay]; }); @@ -306,7 +306,7 @@ // If we have an image to display, display it, respecting our recrop flag. if (self.image) { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ if (recropImmediately) [self displayImmediately]; else @@ -334,7 +334,7 @@ BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height)); // Re-display if we need to. - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage) [self setNeedsDisplay]; }); diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 6729242e9c..5bb0631ff3 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -14,7 +14,6 @@ #import "ASCollectionViewLayoutController.h" #import "ASLayoutController.h" #import "ASRangeController.h" -#import "ASDisplayNodeInternal.h" #import "ASBatchFetching.h" #import "ASInternalHelpers.h" #import "ASLayout.h" @@ -334,7 +333,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - (void)reloadDataWithCompletion:(void (^)())completion { ASDisplayNodeAssert(self.asyncDelegate, @"ASTableView's asyncDelegate property must be set."); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [super reloadData]; }); [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 7d7e81568c..e9a728c42e 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -14,7 +14,7 @@ #import "ASCellNode.h" #import "ASDisplayNode.h" #import "ASMultidimensionalArrayUtils.h" -#import "ASDisplayNodeInternal.h" +#import "ASInternalHelpers.h" #import "ASLayout.h" //#define LOG(...) NSLog(__VA_ARGS__) @@ -208,7 +208,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _completedNodes[kind] = completedNodes; if (completionBlock) { completionBlock(nodes, indexPaths); @@ -227,7 +227,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); if (completionBlock) { @@ -250,7 +250,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; if (completionBlock) { completionBlock(sections, indexSet); @@ -263,7 +263,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (indexSet.count == 0) return; [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; if (completionBlock) { completionBlock(indexSet); @@ -512,7 +512,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"endUpdatesWithCompletion - beginning"); [_editingTransactionQueue addOperationWithBlock:^{ - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ // Deep copy _completedNodes to _externalCompletedNodes. // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); @@ -532,7 +532,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_pendingEditCommandBlocks removeAllObjects]; [_editingTransactionQueue addOperationWithBlock:^{ - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ // Now that the transaction is done, _completedNodes can be accessed externally again. _externalCompletedNodes = nil; @@ -819,7 +819,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes // (see _layoutNodes:atIndexPaths:withAnimationOptions:). [_editingTransactionQueue addOperationWithBlock:^{ - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ for (NSString *kind in [_completedNodes keyEnumerator]) { [self _relayoutNodesOfKind:kind]; } diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 146b85897f..b511c4d43f 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -10,10 +10,10 @@ #import "ASAssert.h" #import "ASDisplayNodeExtras.h" -#import "ASDisplayNodeInternal.h" #import "ASMultiDimensionalArrayUtils.h" #import "ASRangeHandlerRender.h" #import "ASRangeHandlerPreload.h" +#import "ASInternalHelpers.h" @interface ASRangeController () { BOOL _rangeIsValid; @@ -177,13 +177,13 @@ #pragma mark - ASDataControllerDelegete - (void)dataControllerBeginUpdates:(ASDataController *)dataController { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [_delegate rangeControllerBeginUpdates:self]; }); } - (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ [_delegate rangeController:self endUpdatesAnimated:animated completion:completion]; }); } @@ -196,14 +196,14 @@ [nodeSizes addObject:[NSValue valueWithCGSize:node.calculatedSize]]; }]; - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); } - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); @@ -222,14 +222,14 @@ [sectionNodeSizes addObject:nodeSizes]; }]; - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASDisplayNodePerformBlockOnMainThread(^{ + ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 63ef4ed8da..652c1fa890 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -85,7 +85,7 @@ return _getFromLayer(cornerRadius); } --(void)setCornerRadius:(CGFloat)newCornerRadius +- (void)setCornerRadius:(CGFloat)newCornerRadius { _bridge_prologue; _setToLayer(cornerRadius, newCornerRadius); diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index c2daf155be..602da5aa17 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -20,7 +20,6 @@ #import "ASLayoutOptions.h" BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); -void ASDisplayNodePerformBlockOnMainThread(void (^block)()); void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()); typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 00bed4b1d6..563531476b 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -16,6 +16,7 @@ ASDISPLAYNODE_EXTERN_C_BEGIN BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector); BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector); +void ASPerformBlockOnMainThread(void (^block)()); CGFloat ASScreenScale(); diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm index d2337a44b8..89a0e81709 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -46,6 +46,17 @@ static void ASDispatchOnceOnMainThread(dispatch_once_t *predicate, dispatch_bloc } } +void ASPerformBlockOnMainThread(void (^block)()) +{ + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + block(); + }); + } +} + CGFloat ASScreenScale() { static CGFloat _scale; From 00400e166f830039fcc322a9066d8f99f5c5928b Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 29 Oct 2015 15:59:54 +0200 Subject: [PATCH 04/13] Introduce ASCellNodeDelegate - Cell node automatically notifies the delegate after a relayout (via -setNeedsLayout) that results in a new size. Confirming to ASCellNodeDelegate; ASTableView and ASCollectionView reload the calling cell upon notifications. These views automatically set themselves as delegate of every node. - The result is that ASCellNode subclasses don't need to manually notify the containing view. Thus, `-relayoutItemAtIndexPath` and `-relayoutRowAtIndexPath` are removed. - Kittens example is updated to reflect the change. --- AsyncDisplayKit/ASCellNode.h | 40 ++++++++++++++++++++++++ AsyncDisplayKit/ASCellNode.m | 17 ++++++++++ AsyncDisplayKit/ASCollectionView.h | 10 ------ AsyncDisplayKit/ASCollectionView.mm | 22 +++++++------ AsyncDisplayKit/ASDisplayNode.h | 8 ----- AsyncDisplayKit/ASTableView.h | 12 ------- AsyncDisplayKit/ASTableView.mm | 22 +++++++------ examples/Kittens/Sample/KittenNode.mm | 1 + examples/Kittens/Sample/ViewController.m | 1 - 9 files changed, 84 insertions(+), 49 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index c8523125ad..ba2e496f34 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -8,6 +8,24 @@ #import +@class ASCellNode; + +typedef NSUInteger ASCellNodeAnimation; + +@protocol ASCellNodeDelegate + +/** + * Notifies the delegate that the specified cell node has done a relayout that results in a new size. + * The notification is done on main thread. + * + * @param node A node informing the delegate about the relayout. + * + * @param newSize A new size that is resulted in the relayout. + * + * @param suggestedAnimation A constant that indicates how the delegate should animate. See UITableViewRowAnimation. + */ +- (void)node:(ASCellNode *)node didRelayoutToNewSize:(CGSize)newSize suggestedAnimation:(ASCellNodeAnimation)animation; +@end /** * Generic cell node. Subclass this instead of `ASDisplayNode` to use with `ASTableView` and `ASCollectionView`. @@ -53,6 +71,18 @@ */ @property (nonatomic, assign) BOOL highlighted; +/* + * A delegate to be notified (on main thread) after a relayout that results in a new size. + */ +@property (nonatomic, weak) id delegate; + +/* + * A constant that is passed to the delegate to indicate how a relayout is to be animated. + * + * @see UITableViewRowAnimation + */ +@property (nonatomic, assign) ASCellNodeAnimation relayoutAnimation; + /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding * these methods (e.g. for highlighting) requires the super method be called. @@ -62,6 +92,16 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; +/** + * Marks the node as needing layout. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread. + * + * If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated, + * and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size. + * + * Note: If the relayout causes a change in size, the delegate will be notified (on main thread) to relayout. + */ +- (void)setNeedsLayout; + @end diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index ae6b3f6761..10c8a90e5a 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -8,6 +8,7 @@ #import "ASCellNode.h" +#import "ASInternalHelpers.h" #import #import #import @@ -27,6 +28,7 @@ // use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; + _relayoutAnimation = UITableViewRowAnimationAutomatic; return self; } @@ -49,6 +51,21 @@ ASDisplayNodeAssert(!layerBacked, @"ASCellNode does not support layer-backing."); } +- (void)setNeedsLayout +{ + ASDisplayNodeAssertThreadAffinity(self); + + CGSize oldSize = self.calculatedSize; + [super setNeedsLayout]; + CGSize newSize = self.calculatedSize; + + if (_delegate != nil && !CGSizeEqualToSize(oldSize, newSize)) { + ASPerformBlockOnMainThread(^{ + [_delegate node:self didRelayoutToNewSize:newSize suggestedAnimation:_relayoutAnimation]; + }); + } +} + - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 4cea634226..eca5ba4883 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -223,16 +223,6 @@ */ - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths; -/** - * Relayouts the specified item. - * - * @param indexPath The index path identifying the item to relayout. - * - * @discussion This method must be called from the main thread. The relayout is excuted on main thread. - * The node of the specified item must be updated to cause layout changes before this method is called. - */ -- (void)relayoutItemAtIndexPath:(NSIndexPath *)indexPath; - /** * Moves the item at a specified location to a destination location. * diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 787d611e8c..6be2a94aa0 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -130,7 +130,7 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { _ASCollectionViewProxy *_proxyDataSource; _ASCollectionViewProxy *_proxyDelegate; @@ -457,14 +457,6 @@ static BOOL _isInterceptedSelector(SEL sel) [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; } -- (void)relayoutItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - [node setNeedsLayout]; - [super reloadItemsAtIndexPaths:@[indexPath]]; -} - - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); @@ -663,6 +655,7 @@ static BOOL _isInterceptedSelector(SEL sel) { ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + node.delegate = self; return node; } @@ -906,4 +899,15 @@ static BOOL _isInterceptedSelector(SEL sel) } } +#pragma mark - ASCellNodeDelegate + +- (void)node:(ASCellNode *)node didRelayoutToNewSize:(CGSize)newSize suggestedAnimation:(ASCellNodeAnimation)animation +{ + ASDisplayNodeAssertMainThread(); + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath != nil) { + [super reloadItemsAtIndexPaths:@[indexPath]]; + } +} + @end diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 27e6fc5a39..ad86f873fb 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -559,14 +559,6 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node); * * If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated, * and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size. - * - * Note: If the relayout causes a change in size of the root node that is attached to a container view, - * the container view must be notified to relayout. - * For ASTableView and ASCollectionView, instead of calling this method directly, - * it is recommended to call -relayoutRowAtIndexPath:withRowAnimation and -relayoutItemAtIndexPath: respectively. - * - * @see [ASTableView relayoutRowAtIndexPath:withRowAnimation:] - * @see [ASCollectionView relayoutItemAtIndexPath:] */ - (void)setNeedsLayout; diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 98753ca9a6..d7ea79dc2d 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -214,18 +214,6 @@ */ - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; -/** - * Relayouts the specified row using a given animation effect. - * - * @param indexPath The index path identifying the row to relayout. - * - * @param animation A constant that indicates how the relayout is to be animated. See UITableViewRowAnimation. - * - * @discussion This method must be called from the main thread. The relayout is excuted on main thread. - * The node of the specified row must be updated to cause layout changes before this method is called. - */ -- (void)relayoutRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation; - /** * Moves the row at a specified location to a destination location. * diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 5bb0631ff3..2ae576e6ca 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -151,7 +151,7 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark - #pragma mark ASTableView -@interface ASTableView () { +@interface ASTableView () { _ASTableViewProxy *_proxyDataSource; _ASTableViewProxy *_proxyDelegate; @@ -476,14 +476,6 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; } -- (void)relayoutRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - ASCellNode *node = [self nodeForRowAtIndexPath:indexPath]; - [node setNeedsLayout]; - [super reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:animation]; -} - - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); @@ -848,6 +840,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { { ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + node.delegate = self; return node; } @@ -922,4 +915,15 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { } } +#pragma mark - ASCellNodeDelegate + +- (void)node:(ASCellNode *)node didRelayoutToNewSize:(CGSize)newSize suggestedAnimation:(ASCellNodeAnimation)animation +{ + ASDisplayNodeAssertMainThread(); + NSIndexPath *indexPath = [self indexPathForNode:node]; + if (indexPath != nil) { + [super reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:(UITableViewRowAnimation)animation]; + } +} + @end diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index f6035e03be..adc656aa22 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -185,6 +185,7 @@ static const CGFloat kInnerPadding = 10.0f; - (void)toggleImageEnlargement { _isImageEnlarged = !_isImageEnlarged; + [self setNeedsLayout]; } - (void)toggleNodesSwap diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 41630435aa..3a0ca92728 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -121,7 +121,6 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell // Assume only kitten nodes are selectable (see -tableView:shouldHighlightRowAtIndexPath:). KittenNode *node = (KittenNode *)[_tableView nodeForRowAtIndexPath:indexPath]; [node toggleImageEnlargement]; - [_tableView relayoutRowAtIndexPath:indexPath withRowAnimation:UITableViewRowAnimationAutomatic]; } - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath From c7d18a4f1b3964e6d05e4a24ca8f00eec2ffceed Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 29 Oct 2015 16:38:48 +0200 Subject: [PATCH 05/13] ASTextCellNode triggers a relayout after a text change --- AsyncDisplayKit/ASCellNode.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 10c8a90e5a..1a1d58b5e7 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -139,7 +139,7 @@ static const CGFloat kFontSize = 18.0f; _text = [text copy]; _textNode.attributedString = [[NSAttributedString alloc] initWithString:_text attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kFontSize]}]; - + [self setNeedsLayout]; } @end From 5b8f7e978daa8b90ec5aac349392fda55d613bf6 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 29 Oct 2015 16:41:59 +0200 Subject: [PATCH 06/13] Improve documentation of ASCellNode setNeedsLayout --- AsyncDisplayKit/ASCellNode.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index ba2e496f34..fcc55411a7 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -98,7 +98,9 @@ typedef NSUInteger ASCellNodeAnimation; * If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated, * and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size. * - * Note: If the relayout causes a change in size, the delegate will be notified (on main thread) to relayout. + * If the relayout causes a change in size, the delegate will be notified on main thread. + * + * This method can be called inside of an animation block (to animate all of the layout changes). */ - (void)setNeedsLayout; From cf8946cf9707d3cbab816b8f11523a281d6e4c12 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 29 Oct 2015 10:30:12 -0700 Subject: [PATCH 07/13] Use fast enumeration when performing collection view update blocks --- AsyncDisplayKit/ASCollectionView.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index a60b20ba62..c964b89f3e 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -792,9 +792,9 @@ static BOOL _isInterceptedSelector(SEL sel) ASPerformBlockWithoutAnimation(!animated, ^{ [super performBatchUpdates:^{ - [_batchUpdateBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) { + for (dispatch_block_t block in _batchUpdateBlocks) { block(); - }]; + } } completion:completion]; }); From fb18e7635b46368084418f3eb0cb7eea54859431 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 29 Oct 2015 21:26:06 +0200 Subject: [PATCH 08/13] Notify ASCellNodeDelegate even if a relayout doesn't result in a resize If the delegate is an ASTableView, relayoutAnimation will still be considered for animation. --- AsyncDisplayKit/ASCellNode.h | 13 +++++-------- AsyncDisplayKit/ASCellNode.m | 9 +++------ AsyncDisplayKit/ASCollectionView.mm | 2 +- AsyncDisplayKit/ASTableView.mm | 2 +- examples/Kittens/Sample/KittenNode.mm | 12 +----------- 5 files changed, 11 insertions(+), 27 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index fcc55411a7..7d3bdfd583 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -15,16 +15,14 @@ typedef NSUInteger ASCellNodeAnimation; @protocol ASCellNodeDelegate /** - * Notifies the delegate that the specified cell node has done a relayout that results in a new size. + * Notifies the delegate that the specified cell node has done a relayout. * The notification is done on main thread. * * @param node A node informing the delegate about the relayout. * - * @param newSize A new size that is resulted in the relayout. - * - * @param suggestedAnimation A constant that indicates how the delegate should animate. See UITableViewRowAnimation. + * @param suggestedAnimation A constant indicates how the delegate should animate. See UITableViewRowAnimation. */ -- (void)node:(ASCellNode *)node didRelayoutToNewSize:(CGSize)newSize suggestedAnimation:(ASCellNodeAnimation)animation; +- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation; @end /** @@ -72,7 +70,7 @@ typedef NSUInteger ASCellNodeAnimation; @property (nonatomic, assign) BOOL highlighted; /* - * A delegate to be notified (on main thread) after a relayout that results in a new size. + * A delegate to be notified (on main thread) after a relayout. */ @property (nonatomic, weak) id delegate; @@ -97,8 +95,7 @@ typedef NSUInteger ASCellNodeAnimation; * * If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated, * and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size. - * - * If the relayout causes a change in size, the delegate will be notified on main thread. + * The delegate will then be notified on main thread. * * This method can be called inside of an animation block (to animate all of the layout changes). */ diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 1a1d58b5e7..37df35e864 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -53,15 +53,12 @@ - (void)setNeedsLayout { - ASDisplayNodeAssertThreadAffinity(self); - - CGSize oldSize = self.calculatedSize; + ASDisplayNodeAssertThreadAffinity(self); [super setNeedsLayout]; - CGSize newSize = self.calculatedSize; - if (_delegate != nil && !CGSizeEqualToSize(oldSize, newSize)) { + if (_delegate != nil) { ASPerformBlockOnMainThread(^{ - [_delegate node:self didRelayoutToNewSize:newSize suggestedAnimation:_relayoutAnimation]; + [_delegate node:self didRelayoutWithSuggestedAnimation:_relayoutAnimation]; }); } } diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 6be2a94aa0..0e16ce3120 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -901,7 +901,7 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark - ASCellNodeDelegate -- (void)node:(ASCellNode *)node didRelayoutToNewSize:(CGSize)newSize suggestedAnimation:(ASCellNodeAnimation)animation +- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation { ASDisplayNodeAssertMainThread(); NSIndexPath *indexPath = [self indexPathForNode:node]; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 2ae576e6ca..e1e91cee61 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -917,7 +917,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { #pragma mark - ASCellNodeDelegate -- (void)node:(ASCellNode *)node didRelayoutToNewSize:(CGSize)newSize suggestedAnimation:(ASCellNodeAnimation)animation +- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation { ASDisplayNodeAssertMainThread(); NSIndexPath *indexPath = [self indexPathForNode:node]; diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index adc656aa22..847a2629c7 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -191,17 +191,7 @@ static const CGFloat kInnerPadding = 10.0f; - (void)toggleNodesSwap { _swappedTextAndImage = !_swappedTextAndImage; - - [UIView animateWithDuration:0.15 animations:^{ - self.alpha = 0; - } completion:^(BOOL finished) { - [self setNeedsLayout]; - [self.view layoutIfNeeded]; - - [UIView animateWithDuration:0.15 animations:^{ - self.alpha = 1; - }]; - }]; + [self setNeedsLayout]; } @end From 29a12834b0d64b36a366c171d3270f06c0cbf09c Mon Sep 17 00:00:00 2001 From: Samuel Hsiung Date: Thu, 29 Oct 2015 19:39:32 -0700 Subject: [PATCH 09/13] adds convenience methods to return a vertical and horizontal stack spec --- AsyncDisplayKit/Layout/ASStackLayoutSpec.h | 10 ++++++++++ AsyncDisplayKit/Layout/ASStackLayoutSpec.mm | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index dd331f1405..ebf7130bfe 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -57,4 +57,14 @@ */ + (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children; +/** + * @return A stack layout spec with direction of ASStackLayoutDirectionVertical + **/ ++ (instancetype)verticalStackLayoutSpec; + +/** + * @return A stack layout spec with direction of ASStackLayoutDirectionHorizontal + **/ ++ (instancetype)horizontalStackLayoutSpec; + @end diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 2269ffffeb..0763bf2615 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -38,6 +38,20 @@ return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems children:children]; } ++ (instancetype)verticalStackLayoutSpec +{ + ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; + stackLayoutSpec.direction = ASStackLayoutDirectionVertical; + return stackLayoutSpec; +} + ++ (instancetype)horizontalStackLayoutSpec +{ + ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; + stackLayoutSpec.direction = ASStackLayoutDirectionHorizontal; + return stackLayoutSpec; +} + - (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children { if (!(self = [super init])) { From 3175ce2fe796ed593fc282037e40edadf644474c Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 30 Oct 2015 22:42:05 -0700 Subject: [PATCH 10/13] Fix retain cycle in basic, cache-less ASNetworkImageNodes This should not directly affect more complex apps that don't use the ASBasicImageDownloader. Also disables the default-on text placeholders, as they churn memory during the measurement pass. These were intended to be written with pure layers (without using backing stores), so I don't think it is a reasonable default-on behavior until that is fixed. --- AsyncDisplayKit/ASNetworkImageNode.h | 6 +++--- AsyncDisplayKit/ASNetworkImageNode.mm | 6 +++--- AsyncDisplayKit/ASTextNode.h | 9 +++++++++ AsyncDisplayKit/ASTextNode.mm | 5 ++++- AsyncDisplayKit/Details/ASBasicImageDownloader.h | 2 ++ AsyncDisplayKit/Details/ASBasicImageDownloader.mm | 10 ++++++++++ examples/Kittens/Sample/ViewController.m | 4 ---- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/AsyncDisplayKit/ASNetworkImageNode.h index 5dc723521e..c37d215d96 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/AsyncDisplayKit/ASNetworkImageNode.h @@ -23,10 +23,10 @@ @interface ASNetworkImageNode : ASImageNode /** - * The designated initializer. + * The designated initializer. Cache and Downloader are WEAK references. * - * @param cache The object that implements a cache of images for the image node. - * @param downloader The object that implements image downloading for the image node. Must not be nil. + * @param cache The object that implements a cache of images for the image node. Weak reference. + * @param downloader The object that implements image downloading for the image node. Must not be nil. Weak reference. * * @discussion If `cache` is nil, the receiver will not attempt to retrieve images from a cache before downloading them. * diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 3d2fad8906..aa8cb483c4 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -16,8 +16,8 @@ @interface ASNetworkImageNode () { ASDN::RecursiveMutex _lock; - id _cache; - id _downloader; + __weak id _cache; + __weak id _downloader; // Only access any of these with _lock. __weak id _delegate; @@ -51,7 +51,7 @@ - (instancetype)init { - return [self initWithCache:nil downloader:[[ASBasicImageDownloader alloc] init]]; + return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; } - (void)dealloc diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index e8d5b76154..fd8e0d1b6b 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -79,6 +79,15 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { #pragma mark - Placeholders +/** + * @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES. + * + * @discussion Defaults to NO. When YES, it draws rectangles for each line of text, + * following the true shape of the text's wrapping. This visually mirrors the overall + * shape and weight of paragraphs, making the appearance of the finished text less jarring. + */ +@property (nonatomic, assign) BOOL placeholderEnabled; + /** @abstract The placeholder color. */ diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index a88920892e..2f07bcef50 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -131,7 +131,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); // Placeholders - self.placeholderEnabled = YES; + // Disabled by default in ASDisplayNode, but add a few options for those who toggle + // on the special placeholder behavior of ASTextNode. _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); _placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0); } @@ -747,6 +748,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (UIImage *)placeholderImage { + // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. + // This would completely eliminate the memory and performance cost of the backing store. CGSize size = self.calculatedSize; UIGraphicsBeginImageContext(size); [self.placeholderColor setFill]; diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.h b/AsyncDisplayKit/Details/ASBasicImageDownloader.h index 2d96296280..ecb4861911 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.h +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.h @@ -14,4 +14,6 @@ */ @interface ASBasicImageDownloader : NSObject ++ (instancetype)sharedImageDownloader; + @end diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm index b1693c3017..ad1026d013 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -200,6 +200,16 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext @implementation ASBasicImageDownloader ++ (instancetype)sharedImageDownloader +{ + static ASBasicImageDownloader *sharedImageDownloader = nil; + static dispatch_once_t once = 0; + dispatch_once(&once, ^{ + sharedImageDownloader = [[ASBasicImageDownloader alloc] init]; + }); + return sharedImageDownloader; +} + #pragma mark Lifecycle. - (instancetype)init diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 41630435aa..dd2ac41e7b 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -166,8 +166,6 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell - (void)tableView:(UITableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context { - NSLog(@"adding kitties"); - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_async(dispatch_get_main_queue(), ^{ @@ -189,8 +187,6 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; [context completeBatchFetching:YES]; - - NSLog(@"kittens added"); }); }); } From 3416450f113b66dd35120ea6f234681353e61768 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 31 Oct 2015 15:12:05 -0700 Subject: [PATCH 11/13] Adding ASButtonNode. Simple but flexible, add features as desired! --- AsyncDisplayKit.xcodeproj/project.pbxproj | 81 ++++--- .../xcschemes/AsyncDisplayKit-iOS.xcscheme | 2 +- .../xcschemes/AsyncDisplayKit.xcscheme | 2 +- AsyncDisplayKit/ASButtonNode.h | 40 ++++ AsyncDisplayKit/ASButtonNode.mm | 222 ++++++++++++++++++ AsyncDisplayKit/ASTextNode.mm | 1 + AsyncDisplayKit/AsyncDisplayKit.h | 1 + .../Sample.xcodeproj/project.pbxproj | 16 ++ examples/Multiplex/Sample/ScreenNode.h | 2 +- examples/Multiplex/Sample/ScreenNode.m | 32 ++- examples/Multiplex/Sample/ViewController.m | 11 +- 11 files changed, 358 insertions(+), 52 deletions(-) create mode 100644 AsyncDisplayKit/ASButtonNode.h create mode 100644 AsyncDisplayKit/ASButtonNode.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 12e50d0543..2b412a4527 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -10,14 +10,14 @@ 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */; }; 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */; }; 044284FF1BAA3BD600D16268 /* UICollectionViewLayout+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 044285071BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; settings = {ASSET_TAGS = (); }; }; - 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; settings = {ASSET_TAGS = (); }; }; - 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; settings = {ASSET_TAGS = (); }; }; - 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; settings = {ASSET_TAGS = (); }; }; - 0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; settings = {ASSET_TAGS = (); }; }; - 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; settings = {ASSET_TAGS = (); }; }; - 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; settings = {ASSET_TAGS = (); }; }; - 044285101BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; settings = {ASSET_TAGS = (); }; }; + 044285071BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; }; + 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 044285051BAA63FE00D16268 /* ASBatchFetching.h */; }; + 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; }; + 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.m */; }; + 0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; + 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; + 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; }; + 044285101BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */; }; 0515EA211A15769900BA8B9A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 0515EA221A1576A100BA8B9A /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; 0516FA3C1A15563400B4EBED /* ASAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3A1A15563400B4EBED /* ASAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -67,7 +67,6 @@ 058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F9195D050800B7D73C /* _ASAsyncTransaction.m */; }; 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */; }; 058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */; }; - 058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A00195D050800B7D73C /* UIView+ASConvenience.m */; }; 058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */; }; 058D0A27195D050800B7D73C /* _ASPendingState.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A06195D050800B7D73C /* _ASPendingState.m */; }; 058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */; }; @@ -140,14 +139,14 @@ 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; - 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; settings = {ASSET_TAGS = (); }; }; - 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; settings = {ASSET_TAGS = (); }; }; - 251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ASSET_TAGS = (); }; }; - 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; settings = {ASSET_TAGS = (); }; }; - 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; settings = {ASSET_TAGS = (); }; }; - 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; settings = {ASSET_TAGS = (); }; }; + 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; }; + 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; + 251B8EF91BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; }; + 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; + 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; }; + 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.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 */; settings = {ASSET_TAGS = (); }; }; + 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -162,7 +161,7 @@ 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */; }; 2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; settings = {ASSET_TAGS = (); }; }; + 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; }; 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; }; @@ -217,8 +216,8 @@ 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C5586691BD549CB00B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; settings = {ASSET_TAGS = (); }; }; - 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; settings = {ASSET_TAGS = (); }; }; + 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; }; + 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */; }; 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C5FA3511B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C5FA3521B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5FA34F1B8F6ADF00A62714 /* ASLayoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -232,11 +231,12 @@ 9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; - 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; settings = {ASSET_TAGS = (); }; }; - 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; settings = {ASSET_TAGS = (); }; }; + 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; + 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; 9CDC18CC1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; }; + AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */; }; AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */; }; @@ -245,7 +245,6 @@ AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; }; AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */; }; AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */; }; - AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */; }; AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A501A1139C100143C57 /* ASCollectionView.mm */; }; @@ -372,7 +371,6 @@ B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */; }; B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062441B010EFD0018CF92 /* UIView+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A00195D050800B7D73C /* UIView+ASConvenience.m */; }; B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */; }; B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A02195D050800B7D73C /* _AS-objc-internal.h */; }; B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; }; @@ -399,12 +397,16 @@ B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; settings = {ASSET_TAGS = (); }; }; - CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; settings = {ASSET_TAGS = (); }; }; + CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; }; + CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; }; CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; + DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; + DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -607,11 +609,11 @@ 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackBaselinePositionedLayout.mm; sourceTree = ""; }; 9CDC18CB1B910E12004965E2 /* ASLayoutablePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutablePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutablePrivate.h; sourceTree = ""; }; 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewTests.m; sourceTree = ""; }; + AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASStaticLayoutSpecSnapshotTests.m; sourceTree = ""; }; AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASChangeSetDataController.h; sourceTree = ""; }; AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASChangeSetDataController.m; sourceTree = ""; }; AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASHierarchyChangeSet.h; sourceTree = ""; }; AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASHierarchyChangeSet.m; sourceTree = ""; }; - AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASStaticLayoutSpecSnapshotTests.m; sourceTree = ""; }; AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = ""; }; AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionView.h; sourceTree = ""; }; AC3C4A501A1139C100143C57 /* ASCollectionView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionView.mm; sourceTree = ""; }; @@ -667,6 +669,8 @@ D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; + DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; + DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -788,6 +792,8 @@ AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */, 058D09D5195D050800B7D73C /* ASControlNode.h */, 058D09D6195D050800B7D73C /* ASControlNode.m */, + DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */, + DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */, 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */, 058D09D8195D050800B7D73C /* ASDisplayNode.h */, 058D09D9195D050800B7D73C /* ASDisplayNode.mm */, @@ -1129,6 +1135,7 @@ 05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */, ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */, 058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */, + DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */, 058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */, 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */, 058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */, @@ -1267,6 +1274,7 @@ B350625C1B010F070018CF92 /* ASLog.h in Headers */, 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, + DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, 34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */, @@ -1355,6 +1363,7 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, + 6CC5F540055A48FCA8C12BF5 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1391,7 +1400,7 @@ 058D09A4195D04C000B7D73C /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0710; ORGANIZATIONNAME = Facebook; TargetAttributes = { 057D02BE1AC0A66700C7AC3C = { @@ -1484,6 +1493,21 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 6CC5F540055A48FCA8C12BF5 /* 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; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1541,6 +1565,7 @@ 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */, ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */, 0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */, + DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */, 058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */, 055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */, ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */, @@ -1657,6 +1682,7 @@ 34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */, 9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */, 9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */, + DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */, B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */, @@ -1771,6 +1797,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_TEST_COVERAGE_FILES = YES; diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme index df6da9c539..c40014ed91 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme @@ -1,6 +1,6 @@ + +typedef enum : NSUInteger { + ASButtonStateNormal, + ASButtonStateHighlighted, + ASButtonStateDisabled, +} ASButtonState; + +@interface ASButtonNode : ASControlNode + +@property (nonatomic, readonly) ASTextNode *titleNode; +@property (nonatomic, readonly) ASImageNode *imageNode; + +/** + Spacing between image and title. Defaults to 8.0. + */ +@property (nonatomic, assign) CGFloat contentSpacing; + +/** + Whether button should be laid out vertically (image on top of text) or horizontally (image to the left of text). + ASButton node does not yet support RTL but it should be fairly easy to implement. + Defaults to YES. + */ +@property (nonatomic, assign) BOOL laysOutHorizontally; + +- (NSAttributedString *)attributedTitleForState:(ASButtonState)state; +- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASButtonState)state; + +- (UIImage *)imageForState:(ASButtonState)state; +- (void)setImage:(UIImage *)image forState:(ASButtonState)state; + +@end diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm new file mode 100644 index 0000000000..1801a03527 --- /dev/null +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -0,0 +1,222 @@ +/* Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "ASButtonNode.h" + +#import + +@interface ASButtonNode () +{ + ASDN::RecursiveMutex _propertyLock; + + NSAttributedString *_normalAttributedTitle; + NSAttributedString *_highlightedAttributedTitle; + NSAttributedString *_disabledAttributedTitle; + + UIImage *_normalImage; + UIImage *_highlightedImage; + UIImage *_disabledImage; +} + +@end + +@implementation ASButtonNode + +@synthesize contentSpacing = _contentSpacing; +@synthesize laysOutHorizontally = _laysOutHorizontally; + +- (instancetype)init +{ + if (self = [super init]) { + _contentSpacing = 8.0; + _laysOutHorizontally = YES; + + _titleNode = [[ASTextNode alloc] init]; + _imageNode = [[ASImageNode alloc] init]; + + [self addSubnode:_titleNode]; + [self addSubnode:_imageNode]; + + [self addTarget:self action:@selector(controlEventUpdated:) forControlEvents:ASControlNodeEventAllEvents]; + } + return self; +} + +- (void)controlEventUpdated:(ASControlNode *)node +{ + [self updateImage]; + [self updateTitle]; +} + +- (void)updateImage +{ + ASDN::MutexLocker l(_propertyLock); + + UIImage *newImage; + if (self.enabled == NO && _disabledImage) { + newImage = _disabledImage; + } else if (self.highlighted && _highlightedImage) { + newImage = _highlightedImage; + } else { + newImage = _normalImage; + } + + if (newImage != self.imageNode.image) { + self.imageNode.image = newImage; + [self setNeedsLayout]; + } +} + +- (void)updateTitle +{ + ASDN::MutexLocker l(_propertyLock); + NSAttributedString *newTitle; + if (self.enabled == NO && _disabledAttributedTitle) { + newTitle = _disabledAttributedTitle; + } else if (self.highlighted && _highlightedAttributedTitle) { + newTitle = _highlightedAttributedTitle; + } else { + newTitle = _normalAttributedTitle; + } + + if (newTitle != self.titleNode.attributedString) { + self.titleNode.attributedString = newTitle; + [self setNeedsLayout]; + } +} + +- (CGFloat)contentSpacing +{ + ASDN::MutexLocker l(_propertyLock); + return _contentSpacing; +} + +- (void)setContentSpacing:(CGFloat)contentSpacing +{ + ASDN::MutexLocker l(_propertyLock); + if (contentSpacing == _contentSpacing) + return; + + _contentSpacing = contentSpacing; + [self setNeedsLayout]; +} + +- (BOOL)laysOutHorizontally +{ + ASDN::MutexLocker l(_propertyLock); + return _laysOutHorizontally; +} + +- (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally +{ + ASDN::MutexLocker l(_propertyLock); + if (laysOutHorizontally == _laysOutHorizontally) + return; + + _laysOutHorizontally = laysOutHorizontally; + [self setNeedsLayout]; +} + +- (NSAttributedString *)attributedTitleForState:(ASButtonState)state +{ + ASDN::MutexLocker l(_propertyLock); + switch (state) { + case ASButtonStateNormal: + return _normalAttributedTitle; + + case ASButtonStateHighlighted: + return _highlightedAttributedTitle; + + case ASButtonStateDisabled: + return _disabledAttributedTitle; + } +} + +- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASButtonState)state +{ + ASDN::MutexLocker l(_propertyLock); + switch (state) { + case ASButtonStateNormal: + _normalAttributedTitle = [title copy]; + break; + + case ASButtonStateHighlighted: + _highlightedAttributedTitle = [title copy]; + break; + + case ASButtonStateDisabled: + _disabledAttributedTitle = [title copy]; + break; + } + [self updateTitle]; +} + +- (UIImage *)imageForState:(ASButtonState)state +{ + ASDN::MutexLocker l(_propertyLock); + switch (state) { + case ASButtonStateNormal: + return _normalImage; + + case ASButtonStateHighlighted: + return _highlightedImage; + + case ASButtonStateDisabled: + return _disabledImage; + } +} + +- (void)setImage:(UIImage *)image forState:(ASButtonState)state +{ + ASDN::MutexLocker l(_propertyLock); + switch (state) { + case ASButtonStateNormal: + _normalImage = image; + break; + + case ASButtonStateHighlighted: + _highlightedImage = image; + break; + + case ASButtonStateDisabled: + _disabledImage = image; + break; + } + [self updateImage]; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; + stack.direction = self.laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; + stack.spacing = self.contentSpacing; + stack.justifyContent = ASStackLayoutJustifyContentCenter; + stack.alignItems = ASStackLayoutAlignItemsCenter; + + NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; + if (self.imageNode.image) { + [children addObject:self.imageNode]; + } + + if (self.titleNode.attributedString.length > 0) { + [children addObject:self.titleNode]; + } + + stack.children = children; + + return stack; +} + +- (void)layout +{ + [super layout]; + self.imageNode.hidden = self.imageNode.image == nil; + self.titleNode.hidden = self.titleNode.attributedString.length > 0 == NO; +} + +@end diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 2f07bcef50..0f78ac960d 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -96,6 +96,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation UILongPressGestureRecognizer *_longPressGestureRecognizer; } +@dynamic placeholderEnabled; #pragma mark - NSObject diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 2f4d77d15b..407d08b037 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -12,6 +12,7 @@ #import #import #import +#import #import diff --git a/examples/Multiplex/Sample.xcodeproj/project.pbxproj b/examples/Multiplex/Sample.xcodeproj/project.pbxproj index 1383d38552..7408b333ba 100644 --- a/examples/Multiplex/Sample.xcodeproj/project.pbxproj +++ b/examples/Multiplex/Sample.xcodeproj/project.pbxproj @@ -123,6 +123,7 @@ 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + 93B7780A33739EF25F20366B /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -179,6 +180,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 93B7780A33739EF25F20366B /* 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; + }; E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/examples/Multiplex/Sample/ScreenNode.h b/examples/Multiplex/Sample/ScreenNode.h index b1b5024ed6..9a818befa0 100644 --- a/examples/Multiplex/Sample/ScreenNode.h +++ b/examples/Multiplex/Sample/ScreenNode.h @@ -11,7 +11,7 @@ @interface ScreenNode : ASDisplayNode @property (nonatomic, strong) ASMultiplexImageNode *imageNode; -@property (nonatomic, strong) ASTextNode *textNode; +@property (nonatomic, strong) ASButtonNode *buttonNode; - (void)start; - (void)reload; diff --git a/examples/Multiplex/Sample/ScreenNode.m b/examples/Multiplex/Sample/ScreenNode.m index 632a50c4fb..b3e661079a 100644 --- a/examples/Multiplex/Sample/ScreenNode.m +++ b/examples/Multiplex/Sample/ScreenNode.m @@ -31,11 +31,13 @@ // load low-quality images before high-quality images _imageNode.downloadsIntermediateImages = YES; - // simple status label - _textNode = [[ASTextNode alloc] init]; + // simple status label. Synchronous to avoid flicker / placeholder state when updating. + _buttonNode = [[ASButtonNode alloc] init]; + [_buttonNode addTarget:self action:@selector(reload) forControlEvents:ASControlNodeEventTouchUpInside]; + _buttonNode.titleNode.displaysAsynchronously = NO; [self addSubnode:_imageNode]; - [self addSubnode:_textNode]; + [self addSubnode:_buttonNode]; return self; } @@ -43,11 +45,12 @@ - (void)start { [self setText:@"loading…"]; - _textNode.userInteractionEnabled = NO; + _buttonNode.userInteractionEnabled = NO; _imageNode.imageIdentifiers = @[ @"best", @"medium", @"worst" ]; // go! } -- (void)reload { +- (void)reload +{ [self start]; [_imageNode reloadImageIdentifierSources]; } @@ -57,18 +60,21 @@ NSDictionary *attributes = @{NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:22.0f]}; NSAttributedString *string = [[NSAttributedString alloc] initWithString:text attributes:attributes]; - _textNode.attributedString = string; + [_buttonNode setAttributedTitle:string forState:ASButtonStateNormal]; [self setNeedsLayout]; } - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { ASRatioLayoutSpec *imagePlaceholder = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1 child:_imageNode]; - ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical - spacing:10 - justifyContent:ASStackLayoutJustifyContentCenter - alignItems:ASStackLayoutAlignItemsCenter - children:@[imagePlaceholder, _textNode]]; + + ASStackLayoutSpec *verticalStack = [[ASStackLayoutSpec alloc] init]; + verticalStack.direction = ASStackLayoutDirectionVertical; + verticalStack.spacing = 10; + verticalStack.justifyContent = ASStackLayoutJustifyContentCenter; + verticalStack.alignItems = ASStackLayoutAlignItemsCenter; + verticalStack.children = @[imagePlaceholder, _buttonNode]; + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) child:verticalStack]; } @@ -98,8 +104,8 @@ [self setText:[NSString stringWithFormat:@"loaded '%@'", imageIdentifier]]; if ([imageIdentifier isEqualToString:@"best"]) { - [self setText:[_textNode.attributedString.string stringByAppendingString:@". tap to reload"]]; - _textNode.userInteractionEnabled = YES; + [self setText:[_buttonNode.titleNode.attributedString.string stringByAppendingString:@". tap to reload"]]; + _buttonNode.userInteractionEnabled = YES; } } diff --git a/examples/Multiplex/Sample/ViewController.m b/examples/Multiplex/Sample/ViewController.m index e694aa79bd..2b5471d6f5 100644 --- a/examples/Multiplex/Sample/ViewController.m +++ b/examples/Multiplex/Sample/ViewController.m @@ -12,7 +12,8 @@ #import "ViewController.h" #import "ScreenNode.h" -@interface ViewController() { +@interface ViewController() +{ ScreenNode *_screenNode; } @@ -28,10 +29,6 @@ _screenNode = node; - // tap to reload - UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reload:)]; - [_screenNode.textNode.view addGestureRecognizer:gr]; - return self; } @@ -42,10 +39,6 @@ [super viewWillAppear:animated]; } -- (void)reload:(id)sender { - [_screenNode reload]; -} - - (BOOL)prefersStatusBarHidden { return YES; From e4d25513846e640d5e86ee291d4c1b96d7d7e209 Mon Sep 17 00:00:00 2001 From: Samuel Hsiung Date: Thu, 29 Oct 2015 16:03:06 -0700 Subject: [PATCH 12/13] Make textContainerInset configurable for ASEditableTextNode placeholder and typed textViews Previously, it was only possible to configure the textContainerInset of the typed textView by accessing the textView property on didLoad. This would cause the placeholder and typed textViews to become desynced however, so in this commit we add the ability to configure both. --- AsyncDisplayKit/ASEditableTextNode.h | 5 +++++ AsyncDisplayKit/ASEditableTextNode.mm | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index 0581cfe638..ee20ce808a 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -59,6 +59,11 @@ //! @abstract The text input mode used by the receiver's keyboard, if it is visible. This value is undefined if the receiver is not the first responder. @property (nonatomic, readonly) UITextInputMode *textInputMode; +/* + @abstract The textContainerInset of both the placeholder and typed textView. This value defaults to UIEdgeInsetsZero. + */ +@property (nonatomic, readwrite) UIEdgeInsets textContainerInset; + /* @abstract The returnKeyType of the keyboard. This value defaults to UIReturnKeyDefault. */ diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 5b929ccfb3..b0beb67385 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -78,7 +78,8 @@ _textKitComponents.layoutManager.delegate = self; _wordKerner = [[ASTextNodeWordKerner alloc] init]; _returnKeyType = UIReturnKeyDefault; - + _textContainerInset = UIEdgeInsetsZero; + // Create the placeholder scaffolding. _placeholderTextKitComponents = [ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero]; _placeholderTextKitComponents.layoutManager.delegate = self; @@ -122,7 +123,7 @@ textView.backgroundColor = nil; textView.opaque = NO; } - textView.textContainerInset = UIEdgeInsetsZero; + textView.textContainerInset = self.textContainerInset; textView.clipsToBounds = NO; // We don't want selection handles cut off. }; @@ -175,6 +176,15 @@ _placeholderTextKitComponents.textView.backgroundColor = backgroundColor; } +- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset +{ + ASDN::MutexLocker l(_textKitLock); + + _textContainerInset = textContainerInset; + _textKitComponents.textView.textContainerInset = textContainerInset; + _placeholderTextKitComponents.textView.textContainerInset = textContainerInset; +} + - (void)setOpaque:(BOOL)opaque { [super setOpaque:opaque]; From e517f88e65debe63a059a9bac5e5fa8897ad171a Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 1 Nov 2015 12:51:46 -0800 Subject: [PATCH 13/13] Rename ASCellNode delegate to layoutDelegate to avoid common naming overlap --- AsyncDisplayKit/ASCellNode.h | 4 ++-- AsyncDisplayKit/ASCellNode.m | 4 ++-- AsyncDisplayKit/ASCollectionView.mm | 4 ++-- AsyncDisplayKit/ASTableView.mm | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 7d3bdfd583..a9c63b5827 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -12,7 +12,7 @@ typedef NSUInteger ASCellNodeAnimation; -@protocol ASCellNodeDelegate +@protocol ASCellNodeLayoutDelegate /** * Notifies the delegate that the specified cell node has done a relayout. @@ -72,7 +72,7 @@ typedef NSUInteger ASCellNodeAnimation; /* * A delegate to be notified (on main thread) after a relayout. */ -@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id layoutDelegate; /* * A constant that is passed to the delegate to indicate how a relayout is to be animated. diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 37df35e864..a56e9f3501 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -56,9 +56,9 @@ ASDisplayNodeAssertThreadAffinity(self); [super setNeedsLayout]; - if (_delegate != nil) { + if (_layoutDelegate != nil) { ASPerformBlockOnMainThread(^{ - [_delegate node:self didRelayoutWithSuggestedAnimation:_relayoutAnimation]; + [_layoutDelegate node:self didRelayoutWithSuggestedAnimation:_relayoutAnimation]; }); } } diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 7c5c667f2f..f7516a264a 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -130,7 +130,7 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { _ASCollectionViewProxy *_proxyDataSource; _ASCollectionViewProxy *_proxyDelegate; @@ -655,7 +655,7 @@ static BOOL _isInterceptedSelector(SEL sel) { ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); - node.delegate = self; + node.layoutDelegate = self; return node; } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index fa52ed0030..e8be092d97 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -151,7 +151,7 @@ static BOOL _isInterceptedSelector(SEL sel) #pragma mark - #pragma mark ASTableView -@interface ASTableView () { +@interface ASTableView () { _ASTableViewProxy *_proxyDataSource; _ASTableViewProxy *_proxyDelegate; @@ -820,7 +820,7 @@ static BOOL _isInterceptedSelector(SEL sel) { ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); - node.delegate = self; + node.layoutDelegate = self; return node; }