From 0f8eac4757475ed6ecc301e0bbd7d8cb70114333 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 11 Nov 2016 15:51:14 -0800 Subject: [PATCH] Some other commit --- AsyncDisplayKit/ASButtonNode.mm | 5 + AsyncDisplayKit/ASCellNode.mm | 26 ++-- AsyncDisplayKit/ASDisplayNode.h | 18 +++ AsyncDisplayKit/ASDisplayNode.mm | 117 +++++++++--------- AsyncDisplayKit/ASTableView.mm | 14 +-- .../Details/UIView+ASConvenience.h | 1 + AsyncDisplayKit/Details/_ASDisplayLayer.mm | 6 + .../Private/ASDisplayNode+UIViewBridge.mm | 28 +++++ .../Private/ASDisplayNodeInternal.h | 2 + AsyncDisplayKit/Private/_ASPendingState.mm | 13 ++ examples/ASDKgram/Sample/CommentsNode.m | 2 +- examples/ASDKgram/Sample/PhotoCellNode.m | 4 +- .../Sample/DetailViewController.m | 52 +++++++- .../Sample/SampleSizingNode.h | 6 +- .../Sample/SampleSizingNode.m | 62 +++++++--- examples/SocialAppLayout/Sample/PostNode.m | 2 +- 16 files changed, 258 insertions(+), 100 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index fda284ad70..0520e66ac3 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -172,6 +172,7 @@ if ((_imageNode != nil || newImage != nil) && newImage != self.imageNode.image) { _imageNode.image = newImage; [self setNeedsLayout]; + [self invalidateSize]; } } @@ -195,6 +196,7 @@ _titleNode.attributedText = newTitle; self.accessibilityLabel = _titleNode.accessibilityLabel; [self setNeedsLayout]; + [self invalidateSize]; } } @@ -218,6 +220,7 @@ if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) { _backgroundImageNode.image = newImage; [self setNeedsLayout]; + [self invalidateSize]; } } @@ -235,6 +238,7 @@ _contentSpacing = contentSpacing; [self setNeedsLayout]; + [self invalidateSize]; } - (BOOL)laysOutHorizontally @@ -251,6 +255,7 @@ _laysOutHorizontally = laysOutHorizontally; [self setNeedsLayout]; + [self invalidateSize]; } - (ASVerticalAlignment)contentVerticalAlignment diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index cad63a8230..77270c3736 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -29,7 +29,7 @@ #pragma mark - #pragma mark ASCellNode -@interface ASCellNode () +@interface ASCellNode () { ASDisplayNodeViewControllerBlock _viewControllerBlock; ASDisplayNodeDidLoadBlock _viewControllerDidLoadBlock; @@ -58,6 +58,10 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init // Use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; + + // ASCellNode acts as sizing container for the node + self.sizingDelegate = self; + return self; } @@ -117,15 +121,19 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init _viewControllerNode.frame = self.bounds; } -- (void)__setNeedsLayout +- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode { - CGSize oldSize = self.calculatedSize; - [super __setNeedsLayout]; - - //Adding this lock because lock used to be held when this method was called. Not sure if it's necessary for - //didRelayoutFromOldSize:toNewSize: - ASDN::MutexLocker l(__instanceLock__); - [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; + ASDN::MutexLocker l(__instanceLock__); + + CGSize oldSize = self.calculatedSize; + ASLayout *layout = [self layoutThatFits:self.constrainedSizeForCalculatedLayout]; + + // TODO: Needs proper adjustment + CGRect f = self.frame; + f.size = layout.size; + self.frame = f; + + [self didRelayoutFromOldSize:oldSize toNewSize:layout.size]; } - (void)transitionLayoutWithAnimation:(BOOL)animated diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index bbc1453a84..cb1594049a 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -26,6 +26,19 @@ NS_ASSUME_NONNULL_BEGIN @class ASDisplayNode; +// TODO: Extract to ASDisplayNode+Layout.h +// ASDisplayNodeSizingDelegate / ASDisplayNodeSizingHandlers +@protocol ASDisplayNodeSizingDelegate +@required +/** + Called after the display node state update happened (layout invlidation) that could lead to a + + The delegate can use this callback to appropriately resize the node frame to fit the new + node size. The node will not resize itself. + */ +- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode; +@end + /** * UIView creation block. Used to create the backing view of a new display node. */ @@ -254,6 +267,9 @@ extern NSInteger const ASDefaultDrawingPriority; /** @name Managing dimensions */ +@property (nonatomic, readwrite, weak, nullable) id sizingDelegate; +- (void)invalidateSize; + - (CGSize)sizeThatFits:(CGSize)size; /** @@ -648,6 +664,8 @@ extern NSInteger const ASDefaultDrawingPriority; */ - (void)setNeedsLayout; +- (void)layoutIfNeeded; + @property (nonatomic, strong, nullable) id contents; // default=nil @property (nonatomic, assign) BOOL clipsToBounds; // default==NO @property (nonatomic, getter=isOpaque) BOOL opaque; // default==YES diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index a008bd66bd..dbe83499b8 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -703,6 +703,33 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma mark - Layout +- (void)invalidateSize +{ + ASDisplayNodeAssertThreadAffinity(self); + + __instanceLock__.lock(); + + [self invalidateCalculatedLayout]; + + // This is the root node. Let the delegate know that the size changed + // If someone calls `invalidateBlaBla TBD` we have to inform the sizing delegate of the root node to be able + // to let them now that a size change happened and it needs to calculate a new layout / size for this node hierarchy + if ([self.sizingDelegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { + [self.sizingDelegate displayNodeDidInvalidateSize:self]; + } + + if (_supernode) { + ASDisplayNode *supernode = _supernode; + __instanceLock__.unlock(); + // Cause supernode's layout to be invalidated + // We need to release the lock to prevent a deadlock + [supernode invalidateSize]; + return; + } + + __instanceLock__.unlock(); +} + - (CGSize)sizeThatFits:(CGSize)size { return [self layoutThatFits:ASSizeRangeMake(CGSizeZero, size)].size; @@ -716,20 +743,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma clang diagnostic pop } -- (ASLayout *)cacheLayoutThatFits:(ASSizeRange)constrainedSize -{ - return [self cacheLayoutThatFits:constrainedSize parentSize:constrainedSize.max]; -} - - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize { ASDN::MutexLocker l(__instanceLock__); - return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; -} - -- (ASLayout *)cacheLayoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize -{ if ([self shouldCalculateLayoutWithConstrainedSize:constrainedSize parentSize:parentSize] == NO) { ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _layout should not be nil! %@", self); return _calculatedDisplayNodeLayout->layout ? : [ASLayout layoutWithLayoutElement:self size:{0, 0}]; @@ -1402,51 +1419,41 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self displayImmediately]; } -//Calling this with the lock held can lead to deadlocks. Always call *unlocked* - (void)__setNeedsLayout +{ + ASDN::MutexLocker l(__instanceLock__); + + // This will cause the next call to -layoutThatFits:parentSize: to compute a new layout instead of returning + // the cached layout in case the constrained or parent size did not change + _calculatedDisplayNodeLayout->invalidate(); +} + +/** + * Returns a BOOL indicating whether the layer has been marked as needing a layout update. + */ +- (BOOL)__needsLayout +{ + ASDN::MutexLocker l(__instanceLock__); + return _calculatedDisplayNodeLayout->isDirty(); +} + +/** + * The node's supernodes are traversed until a ancestor node is found that does not require layout. Then layout + * is performed on the entire node-tree beneath that ancestor + */ +- (void)__layoutIfNeeded { ASDisplayNodeAssertThreadAffinity(self); + __instanceLock__.lock(); + ASDisplayNode *supernode = _supernode; + __instanceLock__.unlock(); - /*__instanceLock__.lock(); - - if (_calculatedDisplayNodeLayout->layout == nil) { - // Can't proceed without a layout as no constrained size would be available. If not layout exists at this moment - // no measurement pass did happen just bail out for now - __instanceLock__.unlock(); - return; - }*/ - - [self invalidateCalculatedLayout]; - - /*if (_supernode) { - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - // Cause supernode's layout to be invalidated - // We need to release the lock to prevent a deadlock - [supernode setNeedsLayout]; - return; + if ([supernode __needsLayout]) { + [supernode __layoutIfNeeded]; + } else { + // Layout all subviews starting from the first node that needs layout + [self __layout]; } - - // This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used. - [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize];*/ - - /*CGRect oldBounds = self.bounds; - CGSize oldSize = oldBounds.size; - CGSize newSize = _calculatedDisplayNodeLayout->layout.size; - - if (! CGSizeEqualToSize(oldSize, newSize)) { - self.bounds = (CGRect){ oldBounds.origin, newSize }; - - // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint - // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. - CGPoint anchorPoint = self.anchorPoint; - CGPoint oldPosition = self.position; - CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; - CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; - self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); - }*/ - - //__instanceLock__.unlock(); } - (void)__setNeedsDisplay @@ -1512,10 +1519,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // for the node using a size range equal to whatever bounds were provided to the node if (CGRectEqualToRect(bounds, CGRectZero)) { LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); - } else { - if (CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) { - [self cacheLayoutThatFits:ASSizeRangeMake(bounds.size)]; - } + } else if (hasDirtyLayout || CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) { + [self layoutThatFits:ASSizeRangeMake(bounds.size)]; } } @@ -3054,9 +3059,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) { ASDisplayNodeAssertMainThread(); - /*if (_calculatedDisplayNodeLayout->isDirty()) { + if (_calculatedDisplayNodeLayout->isDirty()) { return; - }*/ + } [self __layoutSublayouts]; } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 7cb7de99ce..ec1d8a3019 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -1509,6 +1509,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - _ASTableViewCellDelegate +#pragma mark - _ASTableViewCellDelegate + - (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell { ASCellNode *node = tableViewCell.node; @@ -1517,15 +1519,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width; -// ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; + ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; // Table view cells should always fill its content view width. // Normally the content view width equals to the constrained size width (which equals to the table view width). // If there is a mismatch between these values, for example after the table view entered or left editing mode, // content view width is preferred and used to re-measure the cell node. - //if (contentViewWidth != constrainedSize.max.width) { - //constrainedSize.min.width = contentViewWidth; - //constrainedSize.max.width = contentViewWidth; + if (contentViewWidth != constrainedSize.max.width) { + constrainedSize.min.width = contentViewWidth; + constrainedSize.max.width = contentViewWidth; // Re-measurement is done on main to ensure thread affinity. In the worst case, this is as fast as UIKit's implementation. // @@ -1534,8 +1536,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Also, in many cases, some nodes may not need to be re-measured at all, such as when user enters and then immediately leaves editing mode. // To avoid premature optimization and making such assumption, as well as to keep ASTableView simple, re-measurement is strictly done on main. CGSize oldSize = node.bounds.size; - ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeMake(contentViewWidth, 0.0), - CGSizeMake(contentViewWidth, CGFLOAT_MAX)); const CGSize calculatedSize = [node layoutThatFits:constrainedSize].size; node.frame = { .size = calculatedSize }; @@ -1544,7 +1544,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [self beginUpdates]; [self endUpdates]; } - //} + } } #pragma mark - ASCellNodeDelegate diff --git a/AsyncDisplayKit/Details/UIView+ASConvenience.h b/AsyncDisplayKit/Details/UIView+ASConvenience.h index 24a9021cfd..9c918689ec 100644 --- a/AsyncDisplayKit/Details/UIView+ASConvenience.h +++ b/AsyncDisplayKit/Details/UIView+ASConvenience.h @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)setNeedsDisplay; - (void)setNeedsLayout; +- (void)layoutIfNeeded; @end diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index a1d74c15fd..dd7f70ee8f 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -122,6 +122,12 @@ ASDisplayNodeAssertMainThread(); [super setNeedsLayout]; } + +- (void)layoutIfNeeded +{ + ASDisplayNodeAssertMainThread(); + [super layoutIfNeeded]; +} #endif - (void)layoutSublayers diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index f4e67b7f9a..efa1c50d06 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -354,6 +354,34 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo } } +- (void)layoutIfNeeded +{ + _bridge_prologue_write; + BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + if (shouldApply) { + // The node is loaded and we're on main. + // Quite the opposite of setNeedsDisplay, we must call __layoutIfNeeded before messaging + // the view or layer to ensure that measurement and implicitly added subnodes have been handled. + + // Calling __layoutIfNeeded while holding the property lock can cause deadlocks + _bridge_prologue_write_unlock; + [self __layoutIfNeeded]; + _bridge_prologue_write; + _messageToViewOrLayer(layoutIfNeeded); + } else if (__loaded(self)) { + // The node is loaded but we're not on main. + // We will call [self __setNeedsLayout] when we apply + // the pending state. We need to call it on main if the node is loaded + // to support automatic subnode management. + [ASDisplayNodeGetPendingState(self) layoutIfNeeded]; + } else { + // The node is not loaded and we're not on main. + _bridge_prologue_write_unlock; + [self __layoutIfNeeded]; + _bridge_prologue_write; + } +} + - (BOOL)isOpaque { _bridge_prologue_read; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 8aa400cb54..c26eb1c014 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -205,6 +205,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo */ - (void)__setNeedsLayout; +- (void)__layoutIfNeeded; + /** Invoked after a call to setNeedsDisplay to the underlying view */ diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index c01470e258..b9e1abcb46 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -24,6 +24,7 @@ typedef struct { // Properties int needsDisplay:1; int needsLayout:1; + int layoutIfNeeded:1; // Flags indicating that a given property should be applied to the view at creation int setClipsToBounds:1; @@ -272,6 +273,11 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; _flags.needsLayout = YES; } +- (void)layoutIfNeeded +{ + _flags.layoutIfNeeded = YES; +} + - (void)setClipsToBounds:(BOOL)flag { clipsToBounds = flag; @@ -764,6 +770,9 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; if (flags.needsLayout) [layer setNeedsLayout]; + if (flags.layoutIfNeeded) + [layer layoutIfNeeded]; + if (flags.setAsyncTransactionContainer) layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -891,6 +900,9 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; if (flags.needsLayout) [view setNeedsLayout]; + + if (flags.layoutIfNeeded) + [view layoutIfNeeded]; if (flags.setAsyncTransactionContainer) view.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -1105,6 +1117,7 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; || flags.setEdgeAntialiasingMask || flags.needsDisplay || flags.needsLayout + || flags.layoutIfNeeded || flags.setAsyncTransactionContainer || flags.setOpaque || flags.setIsAccessibilityElement diff --git a/examples/ASDKgram/Sample/CommentsNode.m b/examples/ASDKgram/Sample/CommentsNode.m index 67859bd37f..29cea828e3 100644 --- a/examples/ASDKgram/Sample/CommentsNode.m +++ b/examples/ASDKgram/Sample/CommentsNode.m @@ -79,7 +79,7 @@ labelsIndex++; } - [self setNeedsLayout]; + [self invalidateSize]; } } diff --git a/examples/ASDKgram/Sample/PhotoCellNode.m b/examples/ASDKgram/Sample/PhotoCellNode.m index 176cf4b608..89bd91e5ac 100644 --- a/examples/ASDKgram/Sample/PhotoCellNode.m +++ b/examples/ASDKgram/Sample/PhotoCellNode.m @@ -81,7 +81,7 @@ // where as local variable will never change if (locationModel == _photoModel.location) { _photoLocationLabel.attributedText = [photo locationAttributedStringWithFontSize:FONT_SIZE]; - [self setNeedsLayout]; + [self invalidateSize]; } }]; @@ -224,7 +224,7 @@ if (photo.commentFeed.numberOfItemsInFeed > 0) { [_photoCommentsView updateWithCommentFeedModel:photo.commentFeed]; - [self setNeedsLayout]; + [self invalidateSize]; } } diff --git a/examples/ASViewController/Sample/DetailViewController.m b/examples/ASViewController/Sample/DetailViewController.m index 685be7b108..e246ae56ab 100644 --- a/examples/ASViewController/Sample/DetailViewController.m +++ b/examples/ASViewController/Sample/DetailViewController.m @@ -21,9 +21,12 @@ #import "DetailRootNode.h" #import "SampleSizingNode.h" -@interface DetailViewController () +@interface DetailViewController () @property (strong, nonatomic) SampleSizingNode *sizingNode; +@property (strong, nonatomic) ASNetworkImageNode *imageNode; +@property (strong, nonatomic) ASButtonNode *buttonNode; + @end @implementation DetailViewController @@ -35,9 +38,22 @@ self = [super initWithNode:node]; // Set the sizing delegate of the root node to the container - self.sizingNode = [SampleSizingNode new]; - self.sizingNode.autoresizingMask = UIViewAutoresizingNone; - self.sizingNode.delegate = self; + _sizingNode = [SampleSizingNode new]; + _sizingNode.autoresizingMask = UIViewAutoresizingNone; + _sizingNode.sizingDelegate = self; + + _imageNode = [ASNetworkImageNode new]; + _imageNode.needsDisplayOnBoundsChange = YES; + _imageNode.backgroundColor = [UIColor brownColor]; + _imageNode.style.preferredSize = CGSizeMake(100, 100); + _imageNode.URL = [NSURL URLWithString:@"http://www.classicwings-bavaria.com/bavarian-pictures/chitty-chitty-bang-bang-castle.jpg"]; + + _buttonNode = [ASButtonNode new]; + _buttonNode.backgroundColor = [UIColor yellowColor]; + [_buttonNode setTitle:@"Some Title" withFont:nil withColor:nil forState:ASControlStateNormal]; + [_buttonNode setTitle:@"Some Bla" withFont:nil withColor:[UIColor orangeColor] forState:ASControlStateHighlighted]; + [_buttonNode addTarget:self action:@selector(buttonAction:) forControlEvents:ASControlNodeEventTouchUpInside]; + _buttonNode.sizingDelegate = self; return self; } @@ -47,6 +63,8 @@ [super viewDidLoad]; [self.view addSubnode:self.sizingNode]; + [self.view addSubnode:self.imageNode]; + [self.view addSubnode:self.buttonNode]; } - (void)viewWillAppear:(BOOL)animated @@ -54,7 +72,18 @@ [super viewWillAppear:animated]; // Initial size of sizing node - self.sizingNode.frame = CGRectMake(100, 100, 50, 50); + //self.sizingNode.frame = CGRectMake(100, 100, 50, 50); + + self.buttonNode.frame = CGRectMake(100, 100, 200, 10); + [self displayNodeDidInvalidateSize:self.buttonNode]; + + // Initial size for image node +// self.imageNode.frame = CGRectMake(50, 70, 100, 100); +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +// self.imageNode.frame = CGRectMake(50, 70, 70, 50); +// //[self.imageNode setNeedsLayout]; +// //[self.imageNode setNeedsDisplay]; +// }); // Start some timer to chang ethe size randomly [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) { @@ -66,6 +95,7 @@ { [super viewDidLayoutSubviews]; + // Update the sizing node layout [self updateNodeLayout]; } @@ -75,6 +105,12 @@ // to get the new size from the display node and update the frame based on the returned size - (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode { + if (displayNode == self.buttonNode) { + CGSize s = [self.buttonNode sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; + self.buttonNode.frame = CGRectMake(100, 100, s.width, s.height); + return; + } + [self updateNodeLayout]; /*dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @@ -118,6 +154,12 @@ } +#pragma mark - + +- (void)buttonAction:(id)sender +{ + NSLog(@"Button Sender"); +} #pragma mark - Rotation diff --git a/examples/ASViewController/Sample/SampleSizingNode.h b/examples/ASViewController/Sample/SampleSizingNode.h index d02b99b8db..57ecdb7df0 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.h +++ b/examples/ASViewController/Sample/SampleSizingNode.h @@ -8,11 +8,7 @@ #import -// ASDisplayNodeSizingDelegate / ASDisplayNodeSizingHandlers -@interface ASDisplayNodeSizingDelegate : NSObject -- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode; -@end @interface SampleSizingNode : ASDisplayNode -@property (nonatomic, weak) id delegate; + @end diff --git a/examples/ASViewController/Sample/SampleSizingNode.m b/examples/ASViewController/Sample/SampleSizingNode.m index e59a855758..a1767b12a9 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.m +++ b/examples/ASViewController/Sample/SampleSizingNode.m @@ -13,6 +13,7 @@ @property (nonatomic, assign) NSInteger state; @property (nonatomic, strong) ASTextNode *textNode; +@property (nonatomic, strong) ASNetworkImageNode *imageNode; @end @implementation SampleSizingNode @@ -28,14 +29,33 @@ _textNode = [ASTextNode new]; _textNode.backgroundColor = [UIColor blueColor]; - //_textNode.autoresizingMask = UIViewAutoresizingNone; + + _imageNode = [ASNetworkImageNode new]; + _imageNode.URL = [NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Mont_Blanc_oct_2004.JPG/280px-Mont_Blanc_oct_2004.JPG"]; + _imageNode.backgroundColor = [UIColor brownColor]; + _imageNode.needsDisplayOnBoundsChange = YES; + _imageNode.style.height = ASDimensionMakeWithFraction(1.0); + _imageNode.style.width = ASDimensionMake(30.0); + _subnode = [ASDisplayNode new]; _subnode.backgroundColor = [UIColor redColor]; _subnode.automaticallyManagesSubnodes = YES; + + __weak __typeof(self) weakSelf = self; _subnode.layoutSpecBlock = ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_textNode]; + ASInsetLayoutSpec *textInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:weakSelf.textNode]; + textInsetSpec.style.flexShrink = 1.0; + + return [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:@[weakSelf.imageNode, textInsetSpec]]; + //return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithSizing:ASAbsoluteLayoutSpecSizingSizeToFit children:@[_imageNode, insetSpec]]; }; _state = 0; @@ -47,8 +67,11 @@ { [super didLoad]; - [self stateChanged]; + // Initial state + self.state = 0; + + // Simulate a state change of the node dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.state = 1; }); @@ -59,24 +82,35 @@ - (void)setState:(NSInteger)state { _state = state; - [self stateChanged]; -} - -- (void)stateChanged -{ - NSString *text = self.state == 0 ? @"Bla Bla" : @"Bla Blaa sd fkj as;l dkf"; + + // Set text based on state + NSString *text = state == 0 ? @"Bla Bla" : @"Bla Blaa sd fkj as;l dkf"; self.textNode.attributedText = [[NSAttributedString alloc] initWithString:text]; + //self.imageNode.style.width = state == 0 ? ASDimensionMake(30.0) : ASDimensionMake(50.0); + + // Let the root node know there can be a size change happened. If we will not call this the text node will just + // use it's own bounds and layout again in the next layout pass what can result in truncation + [self invalidateCalculatedLayoutSizingBlaBla]; +} + +// Invalidates the current layout and bubbles up the setNeedsLayout call to the root node. The root node will inform +// the sizing delegate that a size change did happen and it's up to it to decide if the bounds of the root node should +// change based on this request or not. If no change happened the layout will happen in all subnodes based on the current +// set bounds +- (void)invalidateCalculatedLayoutSizingBlaBla +{ // Invalidate the layout for now and bubble it up until the root node to let the size provider know that // that a size change could have happened // --> Do we even need to invalidate the layout? [self setNeedsLayout]; - // If someone calls `setNeedsLayout` we have to inform the sizing delegate of the root node to be able - // to let them now that a size change happened - if ([self.delegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { - [self.delegate performSelector:@selector(displayNodeDidInvalidateSize:) withObject:self]; - } + // If someone calls `invalidateBlaBla TBD` we have to inform the sizing delegate of the root node to be able + // to let them now that a size change happened and it needs to calculate a new layout / size for this node hierarchy + /*if ([self.sizingDelegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { + [self.sizingDelegate performSelector:@selector(displayNodeDidInvalidateSize:) withObject:self]; + }*/ + [self invalidateSize]; } diff --git a/examples/SocialAppLayout/Sample/PostNode.m b/examples/SocialAppLayout/Sample/PostNode.m index 3206b00d99..b4abad7a64 100644 --- a/examples/SocialAppLayout/Sample/PostNode.m +++ b/examples/SocialAppLayout/Sample/PostNode.m @@ -339,7 +339,7 @@ - (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image { - [self setNeedsLayout]; + [self invalidateSize]; } @end