From 56768a837ab9e3ebee08345822f8bf17fab429e9 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sun, 2 Aug 2015 09:20:46 +0300 Subject: [PATCH 1/3] - Support internal relayout, that is a relayout caused by internal layout changes, like subnodes re-arrangement and/or subnodes' size change. The constrained size applied to root node is unchanged. - Update Kittens example to show how internal relayout is done. --- AsyncDisplayKit/ASDisplayNode.h | 13 ++++++++++- AsyncDisplayKit/ASDisplayNode.mm | 22 +++++++++++++++++++ .../Private/ASDisplayNode+UIViewBridge.mm | 1 + .../Private/ASDisplayNodeInternal.h | 1 + examples/Kittens/Sample/KittenNode.h | 2 ++ examples/Kittens/Sample/KittenNode.mm | 19 ++++++++++++++-- examples/Kittens/Sample/ViewController.m | 14 ++++++++++-- 7 files changed, 67 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index fa052b3db9..fadd242a59 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -506,7 +506,18 @@ 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 layout. Convenience for use whether view is created or not, or 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 redraw. + * In case of table or collection view, calling -beginUpdates and -endUpdates is enough. + */ +- (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..6b6196fd00 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -560,6 +560,28 @@ 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]; + self.frame = CGRectMake(0.0f, 0.0f, _layout.size.width, _layout.size.height); + } +} + // 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..2289fb7416 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 _isNodesSwapped; } @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:!_isNodesSwapped ? @[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 +{ + _isNodesSwapped = !_isNodesSwapped; + [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 From 0acfea234f09ca092bb408226524a731c0f15b52 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 3 Aug 2015 02:30:08 +0300 Subject: [PATCH 2/3] Update comments of setNeedsLayout and setNeedsDisplay of ASDisplayNode. --- AsyncDisplayKit/ASDisplayNode.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index fadd242a59..b4236cf32c 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -505,10 +505,13 @@ 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. +/** + * 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 view as needing layout. Convenience for use whether view is created or not, or from a background thread. + * 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. From 77b586a7e63937eca952393d091749e1daa665f3 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 3 Aug 2015 06:25:23 +0300 Subject: [PATCH 3/3] Improvements on internal relayout. --- AsyncDisplayKit/ASDisplayNode.h | 4 ++-- AsyncDisplayKit/ASDisplayNode.mm | 4 +++- examples/Kittens/Sample/KittenNode.mm | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index b4236cf32c..f748693040 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -517,8 +517,8 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); * 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 redraw. - * In case of table or collection view, calling -beginUpdates and -endUpdates is enough. + * (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; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 6b6196fd00..d9fe7520bd 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -578,7 +578,9 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)()) } else { // This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used. [self measureWithSizeRange:oldConstrainedSize]; - self.frame = CGRectMake(0.0f, 0.0f, _layout.size.width, _layout.size.height); + CGRect bounds = self.bounds; + bounds.size = CGSizeMake(_layout.size.width, _layout.size.height); + self.bounds = bounds; } } diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index 2289fb7416..de8d0384e4 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -30,7 +30,7 @@ static const CGFloat kInnerPadding = 10.0f; ASTextNode *_textNode; ASDisplayNode *_divider; BOOL _isImageEnlarged; - BOOL _isNodesSwapped; + BOOL _swappedTextAndImage; } @end @@ -148,7 +148,7 @@ static const CGFloat kInnerPadding = 10.0f; .direction = ASStackLayoutDirectionHorizontal, .spacing = kInnerPadding } - children:!_isNodesSwapped ? @[imagePlaceholder, _textNode] : @[_textNode, imagePlaceholder]]]; + children:!_swappedTextAndImage ? @[imagePlaceholder, _textNode] : @[_textNode, imagePlaceholder]]]; } // With box model, you don't need to override this method, unless you want to add custom logic. @@ -192,7 +192,7 @@ static const CGFloat kInnerPadding = 10.0f; - (void)toggleNodesSwap { - _isNodesSwapped = !_isNodesSwapped; + _swappedTextAndImage = !_swappedTextAndImage; [self setNeedsLayout]; }