Skip to content

Commit 6bae7f9

Browse files
alloyFacebook Github Bot 5
authored and
Facebook Github Bot 5
committed
Add suggested ‘view size’ powers to -[RCTShadowView setFrame:]
Summary:The UICollectionView example is actually my use-case, which is discussed in a bit more detail [here](alloy/ReactNativeExperiments#2). ---- This is useful when wrapping native iOS components that determine their own suggested size and which would be too hard/unnecessary to replicate in the shadow view. For instance a `UICollectionView` that after layout will update its `contentSize`, which could be used to suggest a size to the shadow view. The reason for adding it to -[RCTShadowView setFrame:] is mainly so it can be used via the existing -[RCTUIManager setFrame:forView:] API and because it might not be a feature you want to expose too prominently. An origin of `{ NAN, NAN }` is used as a sentinel to indicate that the frame should be used as a size suggestion. The size portion of the rect may contain a `NAN` to skip that dimension or a suggested value for the dimension which will be used if no explicit styling has been assigned. Examples: * Without any expl Closes facebook#6114 Differential Revision: D2994796 Pulled By: nicklockwood fb-gh-sync-id: 6dd3dd86a352ca7d31a0da38bc38a2859ed0a410 shipit-source-id: 6dd3dd86a352ca7d31a0da38bc38a2859ed0a410
1 parent b4dc5e3 commit 6bae7f9

File tree

6 files changed

+140
-31
lines changed

6 files changed

+140
-31
lines changed

Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m

+89-23
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,23 @@
1717
#import "RCTShadowView.h"
1818

1919
@interface RCTShadowViewTests : XCTestCase
20-
20+
@property (nonatomic, strong) RCTShadowView *parentView;
2121
@end
2222

2323
@implementation RCTShadowViewTests
2424

25+
- (void)setUp
26+
{
27+
[super setUp];
28+
29+
self.parentView = [self _shadowViewWithStyle:^(css_style_t *style) {
30+
style->flex_direction = CSS_FLEX_DIRECTION_COLUMN;
31+
style->dimensions[0] = 440;
32+
style->dimensions[1] = 440;
33+
}];
34+
self.parentView.reactTag = @1; // must be valid rootView tag
35+
}
36+
2537
// Just a basic sanity test to ensure css-layout is applied correctly in the context of our shadow view hierarchy.
2638
//
2739
// ====================================
@@ -69,33 +81,87 @@ - (void)testApplyingLayoutRecursivelyToShadowView
6981
style->flex = 1;
7082
}];
7183

72-
RCTShadowView *parentView = [self _shadowViewWithStyle:^(css_style_t *style) {
73-
style->flex_direction = CSS_FLEX_DIRECTION_COLUMN;
74-
style->padding[0] = 10;
75-
style->padding[1] = 10;
76-
style->padding[2] = 10;
77-
style->padding[3] = 10;
78-
style->dimensions[0] = 440;
79-
style->dimensions[1] = 440;
80-
}];
84+
self.parentView.cssNode->style.padding[0] = 10;
85+
self.parentView.cssNode->style.padding[1] = 10;
86+
self.parentView.cssNode->style.padding[2] = 10;
87+
self.parentView.cssNode->style.padding[3] = 10;
88+
89+
[self.parentView insertReactSubview:headerView atIndex:0];
90+
[self.parentView insertReactSubview:mainView atIndex:1];
91+
[self.parentView insertReactSubview:footerView atIndex:2];
8192

82-
[parentView insertReactSubview:headerView atIndex:0];
83-
[parentView insertReactSubview:mainView atIndex:1];
84-
[parentView insertReactSubview:footerView atIndex:2];
93+
[self.parentView collectRootUpdatedFrames];
8594

86-
parentView.reactTag = @1; // must be valid rootView tag
87-
[parentView collectRootUpdatedFrames];
95+
XCTAssertTrue(CGRectEqualToRect([self.parentView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(0, 0, 440, 440)));
96+
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([self.parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
8897

89-
XCTAssertTrue(CGRectEqualToRect([parentView measureLayoutRelativeToAncestor:parentView], CGRectMake(0, 0, 440, 440)));
90-
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
98+
XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 10, 420, 100)));
99+
XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 420, 200)));
100+
XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 330, 420, 100)));
91101

92-
XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 10, 420, 100)));
93-
XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 420, 200)));
94-
XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 330, 420, 100)));
102+
XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 100, 200)));
103+
XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(120, 120, 200, 200)));
104+
XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(330, 120, 100, 200)));
105+
}
95106

96-
XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 100, 200)));
97-
XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:parentView], CGRectMake(120, 120, 200, 200)));
98-
XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:parentView], CGRectMake(330, 120, 100, 200)));
107+
- (void)testAssignsSuggestedWidthDimension
108+
{
109+
[self _withShadowViewWithStyle:^(css_style_t *style) {
110+
style->position[CSS_LEFT] = 0;
111+
style->position[CSS_TOP] = 0;
112+
style->dimensions[CSS_HEIGHT] = 10;
113+
}
114+
assertRelativeLayout:CGRectMake(0, 0, 3, 10)
115+
withIntrinsicContentSize:CGSizeMake(3, UIViewNoIntrinsicMetric)];
116+
}
117+
118+
- (void)testAssignsSuggestedHeightDimension
119+
{
120+
[self _withShadowViewWithStyle:^(css_style_t *style) {
121+
style->position[CSS_LEFT] = 0;
122+
style->position[CSS_TOP] = 0;
123+
style->dimensions[CSS_WIDTH] = 10;
124+
}
125+
assertRelativeLayout:CGRectMake(0, 0, 10, 4)
126+
withIntrinsicContentSize:CGSizeMake(UIViewNoIntrinsicMetric, 4)];
127+
}
128+
129+
- (void)testDoesNotOverrideDimensionStyleWithSuggestedDimensions
130+
{
131+
[self _withShadowViewWithStyle:^(css_style_t *style) {
132+
style->position[CSS_LEFT] = 0;
133+
style->position[CSS_TOP] = 0;
134+
style->dimensions[CSS_WIDTH] = 10;
135+
style->dimensions[CSS_HEIGHT] = 10;
136+
}
137+
assertRelativeLayout:CGRectMake(0, 0, 10, 10)
138+
withIntrinsicContentSize:CGSizeMake(3, 4)];
139+
}
140+
141+
- (void)testDoesNotAssignSuggestedDimensionsWhenStyledWithFlexAttribute
142+
{
143+
float parentWidth = self.parentView.cssNode->style.dimensions[CSS_WIDTH];
144+
float parentHeight = self.parentView.cssNode->style.dimensions[CSS_HEIGHT];
145+
[self _withShadowViewWithStyle:^(css_style_t *style) {
146+
style->flex = 1;
147+
}
148+
assertRelativeLayout:CGRectMake(0, 0, parentWidth, parentHeight)
149+
withIntrinsicContentSize:CGSizeMake(3, 4)];
150+
}
151+
152+
- (void)_withShadowViewWithStyle:(void(^)(css_style_t *style))styleBlock
153+
assertRelativeLayout:(CGRect)expectedRect
154+
withIntrinsicContentSize:(CGSize)contentSize
155+
{
156+
RCTShadowView *view = [self _shadowViewWithStyle:styleBlock];
157+
[self.parentView insertReactSubview:view atIndex:0];
158+
view.intrinsicContentSize = contentSize;
159+
[self.parentView collectRootUpdatedFrames];
160+
CGRect actualRect = [view measureLayoutRelativeToAncestor:self.parentView];
161+
XCTAssertTrue(CGRectEqualToRect(expectedRect, actualRect),
162+
@"Expected layout to be %@, got %@",
163+
NSStringFromCGRect(expectedRect),
164+
NSStringFromCGRect(actualRect));
99165
}
100166

101167
- (RCTShadowView *)_shadowViewWithStyle:(void(^)(css_style_t *style))styleBlock

React/Modules/RCTUIManager.h

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
6060
*/
6161
- (void)setFrame:(CGRect)frame forView:(UIView *)view;
6262

63+
/**
64+
* Set the natural size of a view, which is used when no explicit size is set.
65+
* Use UIViewNoIntrinsicMetric to ignore a dimension.
66+
*/
67+
- (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view;
68+
6369
/**
6470
* Update the background color of a root view. This is usually triggered by
6571
* manually setting the background color of the root view with native code.

React/Modules/RCTUIManager.m

+15
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,21 @@ - (void)setFrame:(CGRect)frame forView:(UIView *)view
399399
});
400400
}
401401

402+
- (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view
403+
{
404+
RCTAssertMainThread();
405+
406+
NSNumber *reactTag = view.reactTag;
407+
dispatch_async(_shadowQueue, ^{
408+
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
409+
RCTAssert(shadowView != nil, @"Could not locate root view with tag #%@", reactTag);
410+
411+
shadowView.intrinsicContentSize = size;
412+
413+
[self batchDidComplete];
414+
});
415+
}
416+
402417
- (void)setBackgroundColor:(UIColor *)color forRootView:(UIView *)rootView
403418
{
404419
RCTAssertMainThread();

React/Views/RCTShadowView.h

+6
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
6868
- (void)setTopLeft:(CGPoint)topLeft;
6969
- (void)setSize:(CGSize)size;
7070

71+
/**
72+
* Set the natural size of the view, which is used when no explicit size is set.
73+
* Use UIViewNoIntrinsicMetric to ignore a dimension.
74+
*/
75+
- (void)setIntrinsicContentSize:(CGSize)size;
76+
7177
/**
7278
* Size flexibility type used to find size constraints.
7379
* Default to RCTRootViewSizeFlexibilityNone

React/Views/RCTShadowView.m

+23
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,29 @@ - (void)setFrame:(CGRect)frame
535535
[self dirtyLayout];
536536
}
537537

538+
static inline BOOL
539+
RCTAssignSuggestedDimension(css_node_t *css_node, int dimension, CGFloat amount)
540+
{
541+
if (amount != UIViewNoIntrinsicMetric
542+
&& isnan(css_node->style.dimensions[dimension])) {
543+
css_node->style.dimensions[dimension] = amount;
544+
return YES;
545+
}
546+
return NO;
547+
}
548+
549+
- (void)setIntrinsicContentSize:(CGSize)size
550+
{
551+
if (_cssNode->style.flex == 0) {
552+
BOOL dirty = NO;
553+
dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_HEIGHT, size.height);
554+
dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_WIDTH, size.width);
555+
if (dirty) {
556+
[self dirtyLayout];
557+
}
558+
}
559+
}
560+
538561
- (void)setTopLeft:(CGPoint)topLeft
539562
{
540563
_cssNode->style.position[CSS_LEFT] = topLeft.x;

runXcodeTests.sh

+1-8
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,9 @@ if [ -z "$1" ]
1010
exit 255
1111
fi
1212

13-
xctool \
14-
-project IntegrationTests/IntegrationTests.xcodeproj \
15-
-scheme IntegrationTests \
16-
-sdk iphonesimulator8.1 \
17-
-destination "platform=iOS Simulator,OS=${1},name=iPhone 5" \
18-
build test
19-
2013
xctool \
2114
-project Examples/UIExplorer/UIExplorer.xcodeproj \
2215
-scheme UIExplorer \
23-
-sdk iphonesimulator8.1 \
16+
-sdk iphonesimulator${1} \
2417
-destination "platform=iOS Simulator,OS=${1},name=iPhone 5" \
2518
build test

0 commit comments

Comments
 (0)