From cd31f884ddd630e4b1ad5454e95d653027d2f228 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 29 Oct 2015 15:58:43 +0200 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 fb18e7635b46368084418f3eb0cb7eea54859431 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 29 Oct 2015 21:26:06 +0200 Subject: [PATCH 5/5] 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