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