From d1990f8fc49aaab8c94f5bbb2441c0216c65eb4c Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Tue, 31 Jan 2017 16:47:11 -0800 Subject: [PATCH] Better (right) implementation of `intrinsicContentSize` Reviewed By: emilsjolander Differential Revision: D4486767 fbshipit-source-id: d37ea11f9f48425d4d99c29e8bfb6c8ed2353f04 --- .../UIExplorerUnitTests/RCTShadowViewTests.m | 17 +++-- React/Modules/RCTUIManager.m | 9 +-- React/Views/RCTShadowView.h | 13 ++-- React/Views/RCTShadowView.m | 67 ++++++++++++++----- 4 files changed, 71 insertions(+), 35 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m index 9f78489c3..a384142cb 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m @@ -28,11 +28,10 @@ { [super setUp]; - self.parentView = [self _shadowViewWithConfig:^(YGNodeRef node) { - YGNodeStyleSetFlexDirection(node, YGFlexDirectionColumn); - YGNodeStyleSetWidth(node, 440); - YGNodeStyleSetHeight(node, 440); - }]; + self.parentView = [RCTRootShadowView new]; + YGNodeStyleSetFlexDirection(self.parentView.cssNode, YGFlexDirectionColumn); + YGNodeStyleSetWidth(self.parentView.cssNode, 440); + YGNodeStyleSetHeight(self.parentView.cssNode, 440); self.parentView.reactTag = @1; // must be valid rootView tag } @@ -132,6 +131,7 @@ - (void)testAssignsSuggestedWidthDimension { [self _withShadowViewWithStyle:^(YGNodeRef node) { + YGNodeStyleSetPositionType(node, YGPositionTypeAbsolute); YGNodeStyleSetPosition(node, YGEdgeLeft, 0); YGNodeStyleSetPosition(node, YGEdgeTop, 0); YGNodeStyleSetHeight(node, 10); @@ -143,6 +143,7 @@ - (void)testAssignsSuggestedHeightDimension { [self _withShadowViewWithStyle:^(YGNodeRef node) { + YGNodeStyleSetPositionType(node, YGPositionTypeAbsolute); YGNodeStyleSetPosition(node, YGEdgeLeft, 0); YGNodeStyleSetPosition(node, YGEdgeTop, 0); YGNodeStyleSetWidth(node, 10); @@ -154,6 +155,7 @@ - (void)testDoesNotOverrideDimensionStyleWithSuggestedDimensions { [self _withShadowViewWithStyle:^(YGNodeRef node) { + YGNodeStyleSetPositionType(node, YGPositionTypeAbsolute); YGNodeStyleSetPosition(node, YGEdgeLeft, 0); YGNodeStyleSetPosition(node, YGEdgeTop, 0); YGNodeStyleSetWidth(node, 10); @@ -189,11 +191,12 @@ NSStringFromCGRect(actualRect)); } -- (RCTRootShadowView *)_shadowViewWithConfig:(void(^)(YGNodeRef node))configBlock +- (RCTShadowView *)_shadowViewWithConfig:(void(^)(YGNodeRef node))configBlock { - RCTRootShadowView *shadowView = [RCTRootShadowView new]; + RCTShadowView *shadowView = [RCTShadowView new]; configBlock(shadowView.cssNode); return shadowView; } + @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 377754049..acde98a24 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -473,11 +473,12 @@ dispatch_queue_t RCTGetUIManagerQueue(void) NSNumber *reactTag = view.reactTag; dispatch_async(RCTGetUIManagerQueue(), ^{ RCTShadowView *shadowView = self->_shadowViewRegistry[reactTag]; - RCTAssert(shadowView != nil, @"Could not locate root view with tag #%@", reactTag); + RCTAssert(shadowView != nil, @"Could not locate view with tag #%@", reactTag); - shadowView.intrinsicContentSize = size; - - [self setNeedsLayout]; + if (!CGSizeEqualToSize(shadowView.intrinsicContentSize, size)) { + shadowView.intrinsicContentSize = size; + [self setNeedsLayout]; + } }); } diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index b1322cbab..1944bc55a 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -81,15 +81,16 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry @property (nonatomic, assign) CGRect frame; +/** + * Represents the natural size of the view, which is used when explicit size is not set or is ambiguous. + * Defaults to `{UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric}`. + */ +@property (nonatomic, assign) CGSize intrinsicContentSize; + + - (void)setTopLeft:(CGPoint)topLeft; - (void)setSize:(CGSize)size; -/** - * Set the natural size of the view, which is used when no explicit size is set. - * Use UIViewNoIntrinsicMetric to ignore a dimension. - */ -- (void)setIntrinsicContentSize:(CGSize)size; - /** * Border. Defaults to { 0, 0, 0, 0 }. */ diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index 76bbff128..5f8140c37 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -301,6 +301,8 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], _borderMetaProps[ii] = YGValueUndefined; } + _intrinsicContentSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric); + _newView = YES; _propagationLifecycle = RCTUpdateLifecycleUninitialized; _textLifecycle = RCTUpdateLifecycleUninitialized; @@ -556,30 +558,59 @@ RCT_POSITION_PROPERTY(Left, left, YGEdgeStart) } } -static inline void RCTAssignSuggestedDimension(YGNodeRef cssNode, YGDimension dimension, CGFloat amount) +static inline YGSize RCTShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode) { - if (amount != UIViewNoIntrinsicMetric) { - switch (dimension) { - case YGDimensionWidth: - if (YGNodeStyleGetWidth(cssNode).unit == YGUnitUndefined) { - YGNodeStyleSetWidth(cssNode, amount); - } - break; - case YGDimensionHeight: - if (YGNodeStyleGetHeight(cssNode).unit == YGUnitUndefined) { - YGNodeStyleSetHeight(cssNode, amount); - } - break; - } + RCTShadowView *shadowView = (__bridge RCTShadowView *)YGNodeGetContext(node); + + CGSize intrinsicContentSize = shadowView->_intrinsicContentSize; + // Replace `UIViewNoIntrinsicMetric` (which equals `-1`) with zero. + intrinsicContentSize.width = MAX(0, intrinsicContentSize.width); + intrinsicContentSize.height = MAX(0, intrinsicContentSize.height); + + YGSize result; + + switch (widthMode) { + case YGMeasureModeUndefined: + result.width = intrinsicContentSize.width; + break; + case YGMeasureModeExactly: + result.width = width; + break; + case YGMeasureModeAtMost: + result.width = MIN(width, intrinsicContentSize.width); + break; } + + switch (heightMode) { + case YGMeasureModeUndefined: + result.height = intrinsicContentSize.height; + break; + case YGMeasureModeExactly: + result.height = height; + break; + case YGMeasureModeAtMost: + result.height = MIN(height, intrinsicContentSize.height); + break; + } + + return result; } -- (void)setIntrinsicContentSize:(CGSize)size +- (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize { - if (YGNodeStyleGetFlexGrow(_cssNode) == 0 && YGNodeStyleGetFlexShrink(_cssNode) == 0) { - RCTAssignSuggestedDimension(_cssNode, YGDimensionHeight, size.height); - RCTAssignSuggestedDimension(_cssNode, YGDimensionWidth, size.width); + if (CGSizeEqualToSize(_intrinsicContentSize, intrinsicContentSize)) { + return; } + + _intrinsicContentSize = intrinsicContentSize; + + if (CGSizeEqualToSize(_intrinsicContentSize, CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric))) { + YGNodeSetMeasureFunc(_cssNode, NULL); + } else { + YGNodeSetMeasureFunc(_cssNode, RCTShadowViewMeasure); + } + + YGNodeMarkDirty(_cssNode); } - (void)setTopLeft:(CGPoint)topLeft