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](https://github.com/alloy/ReactNativeExperiments/issues/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 https://github.com/facebook/react-native/pull/6114 Differential Revision: D2994796 Pulled By: nicklockwood fb-gh-sync-id: 6dd3dd86a352ca7d31a0da38bc38a2859ed0a410 shipit-source-id: 6dd3dd86a352ca7d31a0da38bc38a2859ed0a410
This commit is contained in:
parent
b4dc5e3e81
commit
6bae7f93f5
|
@ -17,11 +17,23 @@
|
||||||
#import "RCTShadowView.h"
|
#import "RCTShadowView.h"
|
||||||
|
|
||||||
@interface RCTShadowViewTests : XCTestCase
|
@interface RCTShadowViewTests : XCTestCase
|
||||||
|
@property (nonatomic, strong) RCTShadowView *parentView;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation RCTShadowViewTests
|
@implementation RCTShadowViewTests
|
||||||
|
|
||||||
|
- (void)setUp
|
||||||
|
{
|
||||||
|
[super setUp];
|
||||||
|
|
||||||
|
self.parentView = [self _shadowViewWithStyle:^(css_style_t *style) {
|
||||||
|
style->flex_direction = CSS_FLEX_DIRECTION_COLUMN;
|
||||||
|
style->dimensions[0] = 440;
|
||||||
|
style->dimensions[1] = 440;
|
||||||
|
}];
|
||||||
|
self.parentView.reactTag = @1; // must be valid rootView tag
|
||||||
|
}
|
||||||
|
|
||||||
// Just a basic sanity test to ensure css-layout is applied correctly in the context of our shadow view hierarchy.
|
// Just a basic sanity test to ensure css-layout is applied correctly in the context of our shadow view hierarchy.
|
||||||
//
|
//
|
||||||
// ====================================
|
// ====================================
|
||||||
|
@ -69,33 +81,87 @@
|
||||||
style->flex = 1;
|
style->flex = 1;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
RCTShadowView *parentView = [self _shadowViewWithStyle:^(css_style_t *style) {
|
self.parentView.cssNode->style.padding[0] = 10;
|
||||||
style->flex_direction = CSS_FLEX_DIRECTION_COLUMN;
|
self.parentView.cssNode->style.padding[1] = 10;
|
||||||
style->padding[0] = 10;
|
self.parentView.cssNode->style.padding[2] = 10;
|
||||||
style->padding[1] = 10;
|
self.parentView.cssNode->style.padding[3] = 10;
|
||||||
style->padding[2] = 10;
|
|
||||||
style->padding[3] = 10;
|
|
||||||
style->dimensions[0] = 440;
|
|
||||||
style->dimensions[1] = 440;
|
|
||||||
}];
|
|
||||||
|
|
||||||
[parentView insertReactSubview:headerView atIndex:0];
|
[self.parentView insertReactSubview:headerView atIndex:0];
|
||||||
[parentView insertReactSubview:mainView atIndex:1];
|
[self.parentView insertReactSubview:mainView atIndex:1];
|
||||||
[parentView insertReactSubview:footerView atIndex:2];
|
[self.parentView insertReactSubview:footerView atIndex:2];
|
||||||
|
|
||||||
parentView.reactTag = @1; // must be valid rootView tag
|
[self.parentView collectRootUpdatedFrames];
|
||||||
[parentView collectRootUpdatedFrames];
|
|
||||||
|
|
||||||
XCTAssertTrue(CGRectEqualToRect([parentView measureLayoutRelativeToAncestor:parentView], CGRectMake(0, 0, 440, 440)));
|
XCTAssertTrue(CGRectEqualToRect([self.parentView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(0, 0, 440, 440)));
|
||||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
|
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([self.parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
|
||||||
|
|
||||||
XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 10, 420, 100)));
|
XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 10, 420, 100)));
|
||||||
XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 420, 200)));
|
XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 420, 200)));
|
||||||
XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 330, 420, 100)));
|
XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 330, 420, 100)));
|
||||||
|
|
||||||
XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 100, 200)));
|
XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 100, 200)));
|
||||||
XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:parentView], CGRectMake(120, 120, 200, 200)));
|
XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(120, 120, 200, 200)));
|
||||||
XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:parentView], CGRectMake(330, 120, 100, 200)));
|
XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(330, 120, 100, 200)));
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testAssignsSuggestedWidthDimension
|
||||||
|
{
|
||||||
|
[self _withShadowViewWithStyle:^(css_style_t *style) {
|
||||||
|
style->position[CSS_LEFT] = 0;
|
||||||
|
style->position[CSS_TOP] = 0;
|
||||||
|
style->dimensions[CSS_HEIGHT] = 10;
|
||||||
|
}
|
||||||
|
assertRelativeLayout:CGRectMake(0, 0, 3, 10)
|
||||||
|
withIntrinsicContentSize:CGSizeMake(3, UIViewNoIntrinsicMetric)];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testAssignsSuggestedHeightDimension
|
||||||
|
{
|
||||||
|
[self _withShadowViewWithStyle:^(css_style_t *style) {
|
||||||
|
style->position[CSS_LEFT] = 0;
|
||||||
|
style->position[CSS_TOP] = 0;
|
||||||
|
style->dimensions[CSS_WIDTH] = 10;
|
||||||
|
}
|
||||||
|
assertRelativeLayout:CGRectMake(0, 0, 10, 4)
|
||||||
|
withIntrinsicContentSize:CGSizeMake(UIViewNoIntrinsicMetric, 4)];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testDoesNotOverrideDimensionStyleWithSuggestedDimensions
|
||||||
|
{
|
||||||
|
[self _withShadowViewWithStyle:^(css_style_t *style) {
|
||||||
|
style->position[CSS_LEFT] = 0;
|
||||||
|
style->position[CSS_TOP] = 0;
|
||||||
|
style->dimensions[CSS_WIDTH] = 10;
|
||||||
|
style->dimensions[CSS_HEIGHT] = 10;
|
||||||
|
}
|
||||||
|
assertRelativeLayout:CGRectMake(0, 0, 10, 10)
|
||||||
|
withIntrinsicContentSize:CGSizeMake(3, 4)];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testDoesNotAssignSuggestedDimensionsWhenStyledWithFlexAttribute
|
||||||
|
{
|
||||||
|
float parentWidth = self.parentView.cssNode->style.dimensions[CSS_WIDTH];
|
||||||
|
float parentHeight = self.parentView.cssNode->style.dimensions[CSS_HEIGHT];
|
||||||
|
[self _withShadowViewWithStyle:^(css_style_t *style) {
|
||||||
|
style->flex = 1;
|
||||||
|
}
|
||||||
|
assertRelativeLayout:CGRectMake(0, 0, parentWidth, parentHeight)
|
||||||
|
withIntrinsicContentSize:CGSizeMake(3, 4)];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_withShadowViewWithStyle:(void(^)(css_style_t *style))styleBlock
|
||||||
|
assertRelativeLayout:(CGRect)expectedRect
|
||||||
|
withIntrinsicContentSize:(CGSize)contentSize
|
||||||
|
{
|
||||||
|
RCTShadowView *view = [self _shadowViewWithStyle:styleBlock];
|
||||||
|
[self.parentView insertReactSubview:view atIndex:0];
|
||||||
|
view.intrinsicContentSize = contentSize;
|
||||||
|
[self.parentView collectRootUpdatedFrames];
|
||||||
|
CGRect actualRect = [view measureLayoutRelativeToAncestor:self.parentView];
|
||||||
|
XCTAssertTrue(CGRectEqualToRect(expectedRect, actualRect),
|
||||||
|
@"Expected layout to be %@, got %@",
|
||||||
|
NSStringFromCGRect(expectedRect),
|
||||||
|
NSStringFromCGRect(actualRect));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (RCTShadowView *)_shadowViewWithStyle:(void(^)(css_style_t *style))styleBlock
|
- (RCTShadowView *)_shadowViewWithStyle:(void(^)(css_style_t *style))styleBlock
|
||||||
|
|
|
@ -60,6 +60,12 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
|
||||||
*/
|
*/
|
||||||
- (void)setFrame:(CGRect)frame forView:(UIView *)view;
|
- (void)setFrame:(CGRect)frame forView:(UIView *)view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the natural size of a view, which is used when no explicit size is set.
|
||||||
|
* Use UIViewNoIntrinsicMetric to ignore a dimension.
|
||||||
|
*/
|
||||||
|
- (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the background color of a root view. This is usually triggered by
|
* Update the background color of a root view. This is usually triggered by
|
||||||
* manually setting the background color of the root view with native code.
|
* manually setting the background color of the root view with native code.
|
||||||
|
|
|
@ -399,6 +399,21 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view
|
||||||
|
{
|
||||||
|
RCTAssertMainThread();
|
||||||
|
|
||||||
|
NSNumber *reactTag = view.reactTag;
|
||||||
|
dispatch_async(_shadowQueue, ^{
|
||||||
|
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
||||||
|
RCTAssert(shadowView != nil, @"Could not locate root view with tag #%@", reactTag);
|
||||||
|
|
||||||
|
shadowView.intrinsicContentSize = size;
|
||||||
|
|
||||||
|
[self batchDidComplete];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setBackgroundColor:(UIColor *)color forRootView:(UIView *)rootView
|
- (void)setBackgroundColor:(UIColor *)color forRootView:(UIView *)rootView
|
||||||
{
|
{
|
||||||
RCTAssertMainThread();
|
RCTAssertMainThread();
|
||||||
|
|
|
@ -68,6 +68,12 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
|
||||||
- (void)setTopLeft:(CGPoint)topLeft;
|
- (void)setTopLeft:(CGPoint)topLeft;
|
||||||
- (void)setSize:(CGSize)size;
|
- (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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Size flexibility type used to find size constraints.
|
* Size flexibility type used to find size constraints.
|
||||||
* Default to RCTRootViewSizeFlexibilityNone
|
* Default to RCTRootViewSizeFlexibilityNone
|
||||||
|
|
|
@ -535,6 +535,29 @@ RCT_POSITION_PROPERTY(Left, left, LEFT)
|
||||||
[self dirtyLayout];
|
[self dirtyLayout];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline BOOL
|
||||||
|
RCTAssignSuggestedDimension(css_node_t *css_node, int dimension, CGFloat amount)
|
||||||
|
{
|
||||||
|
if (amount != UIViewNoIntrinsicMetric
|
||||||
|
&& isnan(css_node->style.dimensions[dimension])) {
|
||||||
|
css_node->style.dimensions[dimension] = amount;
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setIntrinsicContentSize:(CGSize)size
|
||||||
|
{
|
||||||
|
if (_cssNode->style.flex == 0) {
|
||||||
|
BOOL dirty = NO;
|
||||||
|
dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_HEIGHT, size.height);
|
||||||
|
dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_WIDTH, size.width);
|
||||||
|
if (dirty) {
|
||||||
|
[self dirtyLayout];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setTopLeft:(CGPoint)topLeft
|
- (void)setTopLeft:(CGPoint)topLeft
|
||||||
{
|
{
|
||||||
_cssNode->style.position[CSS_LEFT] = topLeft.x;
|
_cssNode->style.position[CSS_LEFT] = topLeft.x;
|
||||||
|
|
|
@ -10,16 +10,9 @@ if [ -z "$1" ]
|
||||||
exit 255
|
exit 255
|
||||||
fi
|
fi
|
||||||
|
|
||||||
xctool \
|
|
||||||
-project IntegrationTests/IntegrationTests.xcodeproj \
|
|
||||||
-scheme IntegrationTests \
|
|
||||||
-sdk iphonesimulator8.1 \
|
|
||||||
-destination "platform=iOS Simulator,OS=${1},name=iPhone 5" \
|
|
||||||
build test
|
|
||||||
|
|
||||||
xctool \
|
xctool \
|
||||||
-project Examples/UIExplorer/UIExplorer.xcodeproj \
|
-project Examples/UIExplorer/UIExplorer.xcodeproj \
|
||||||
-scheme UIExplorer \
|
-scheme UIExplorer \
|
||||||
-sdk iphonesimulator8.1 \
|
-sdk iphonesimulator${1} \
|
||||||
-destination "platform=iOS Simulator,OS=${1},name=iPhone 5" \
|
-destination "platform=iOS Simulator,OS=${1},name=iPhone 5" \
|
||||||
build test
|
build test
|
||||||
|
|
Loading…
Reference in New Issue