diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index fa052b3db9..f748693040 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -505,8 +505,22 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); */ @interface ASDisplayNode (UIViewBridge) -- (void)setNeedsDisplay; // Marks the view as needing display. Convenience for use whether view is created or not, or from a background thread. -- (void)setNeedsLayout; // Marks the view as needing layout. Convenience for use whether view is created or not, or from a background thread. +/** + * Marks the view as needing display. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread. + */ +- (void)setNeedsDisplay; + +/** + * 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 of the root node that is attached to a container view + * (table or collection view, for example), the container view must be notified to relayout. + * For ASTableView and ASCollectionView, an empty batch editing transaction must be triggered to animate to new row / item sizes. + */ +- (void)setNeedsLayout; @property (atomic, retain) id contents; // default=nil @property (atomic, assign) BOOL clipsToBounds; // default==NO diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 43dbc037fa..d9fe7520bd 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -560,6 +560,30 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)()) [[self asyncLayer] displayImmediately]; } +- (void)__setNeedsLayout +{ + ASDisplayNodeAssertThreadAffinity(self); + ASDN::MutexLocker l(_propertyLock); + + if (!_flags.isMeasured) { + return; + } + + ASSizeRange oldConstrainedSize = _constrainedSize; + [self invalidateCalculatedLayout]; + + if (_supernode) { + // Cause supernode's layout to be invalidated + [_supernode setNeedsLayout]; + } else { + // This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used. + [self measureWithSizeRange:oldConstrainedSize]; + CGRect bounds = self.bounds; + bounds.size = CGSizeMake(_layout.size.width, _layout.size.height); + self.bounds = bounds; + } +} + // These private methods ensure that subclasses are not required to call super in order for _renderingSubnodes to be properly managed. - (void)__layout diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 2f2994fd98..ef013a97c2 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -174,6 +174,7 @@ - (void)setNeedsLayout { _bridge_prologue; + [self __setNeedsLayout]; _messageToViewOrLayer(setNeedsLayout); } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 39ce70105b..b59c34d754 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -121,6 +121,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { // Core implementation of -measureWithSizeRange:. Must be called with _propertyLock held. - (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize; +- (void)__setNeedsLayout; - (void)__layout; - (void)__setSupernode:(ASDisplayNode *)supernode; diff --git a/examples/Kittens/Sample/KittenNode.h b/examples/Kittens/Sample/KittenNode.h index 162cb22a32..3cc23d5a44 100644 --- a/examples/Kittens/Sample/KittenNode.h +++ b/examples/Kittens/Sample/KittenNode.h @@ -19,4 +19,6 @@ - (instancetype)initWithKittenOfSize:(CGSize)size; +- (void)toggleImageEnlargement; + @end diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index 7e321712cc..de8d0384e4 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -29,6 +29,8 @@ static const CGFloat kInnerPadding = 10.0f; ASNetworkImageNode *_imageNode; ASTextNode *_textNode; ASDisplayNode *_divider; + BOOL _isImageEnlarged; + BOOL _swappedTextAndImage; } @end @@ -84,6 +86,7 @@ static const CGFloat kInnerPadding = 10.0f; (NSInteger)roundl(_kittenSize.width), (NSInteger)roundl(_kittenSize.height)]]; // _imageNode.contentMode = UIViewContentModeCenter; + [_imageNode addTarget:self action:@selector(toggleNodesSwap) forControlEvents:ASControlNodeEventTouchUpInside]; [self addSubnode:_imageNode]; // lorem ipsum text, plus some nice styling @@ -132,7 +135,7 @@ static const CGFloat kInnerPadding = 10.0f; - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { ASRatioLayoutSpec *imagePlaceholder = [ASRatioLayoutSpec newWithRatio:1.0 child:_imageNode]; - imagePlaceholder.flexBasis = ASRelativeDimensionMakeWithPoints(kImageSize); + imagePlaceholder.flexBasis = ASRelativeDimensionMakeWithPoints(kImageSize * (!_isImageEnlarged ? 1 : 2)); _textNode.flexShrink = YES; @@ -145,7 +148,7 @@ static const CGFloat kInnerPadding = 10.0f; .direction = ASStackLayoutDirectionHorizontal, .spacing = kInnerPadding } - children:@[imagePlaceholder, _textNode]]]; + children:!_swappedTextAndImage ? @[imagePlaceholder, _textNode] : @[_textNode, imagePlaceholder]]]; } // With box model, you don't need to override this method, unless you want to add custom logic. @@ -181,4 +184,16 @@ static const CGFloat kInnerPadding = 10.0f; } #endif +- (void)toggleImageEnlargement +{ + _isImageEnlarged = !_isImageEnlarged; + [self setNeedsLayout]; +} + +- (void)toggleNodesSwap +{ + _swappedTextAndImage = !_swappedTextAndImage; + [self setNeedsLayout]; +} + @end diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index b3293c13fc..12e5123079 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -102,6 +102,16 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell #pragma mark - #pragma mark ASTableView. +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [_tableView deselectRowAtIndexPath:indexPath animated:YES]; + [_tableView beginUpdates]; + // Assume only kitten nodes are selectable (see -tableView:shouldHighlightRowAtIndexPath:). + KittenNode *node = (KittenNode *)[_tableView nodeForRowAtIndexPath:indexPath]; + [node toggleImageEnlargement]; + [_tableView endUpdates]; +} + - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath { // special-case the first row @@ -123,8 +133,8 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath { - // disable row selection - return NO; + // Enable selection for kitten nodes + return indexPath.section != 0 || indexPath.row != 0; } - (void)tableViewLockDataSource:(ASTableView *)tableView