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"
|
||||
|
||||
@interface RCTShadowViewTests : XCTestCase
|
||||
|
||||
@property (nonatomic, strong) RCTShadowView *parentView;
|
||||
@end
|
||||
|
||||
@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.
|
||||
//
|
||||
// ====================================
|
||||
|
@ -69,33 +81,87 @@
|
|||
style->flex = 1;
|
||||
}];
|
||||
|
||||
RCTShadowView *parentView = [self _shadowViewWithStyle:^(css_style_t *style) {
|
||||
style->flex_direction = CSS_FLEX_DIRECTION_COLUMN;
|
||||
style->padding[0] = 10;
|
||||
style->padding[1] = 10;
|
||||
style->padding[2] = 10;
|
||||
style->padding[3] = 10;
|
||||
style->dimensions[0] = 440;
|
||||
style->dimensions[1] = 440;
|
||||
}];
|
||||
self.parentView.cssNode->style.padding[0] = 10;
|
||||
self.parentView.cssNode->style.padding[1] = 10;
|
||||
self.parentView.cssNode->style.padding[2] = 10;
|
||||
self.parentView.cssNode->style.padding[3] = 10;
|
||||
|
||||
[parentView insertReactSubview:headerView atIndex:0];
|
||||
[parentView insertReactSubview:mainView atIndex:1];
|
||||
[parentView insertReactSubview:footerView atIndex:2];
|
||||
[self.parentView insertReactSubview:headerView atIndex:0];
|
||||
[self.parentView insertReactSubview:mainView atIndex:1];
|
||||
[self.parentView insertReactSubview:footerView atIndex:2];
|
||||
|
||||
parentView.reactTag = @1; // must be valid rootView tag
|
||||
[parentView collectRootUpdatedFrames];
|
||||
[self.parentView collectRootUpdatedFrames];
|
||||
|
||||
XCTAssertTrue(CGRectEqualToRect([parentView measureLayoutRelativeToAncestor:parentView], CGRectMake(0, 0, 440, 440)));
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
|
||||
XCTAssertTrue(CGRectEqualToRect([self.parentView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(0, 0, 440, 440)));
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([self.parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
|
||||
|
||||
XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 10, 420, 100)));
|
||||
XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 420, 200)));
|
||||
XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 330, 420, 100)));
|
||||
XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 10, 420, 100)));
|
||||
XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 420, 200)));
|
||||
XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 330, 420, 100)));
|
||||
|
||||
XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 100, 200)));
|
||||
XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:parentView], CGRectMake(120, 120, 200, 200)));
|
||||
XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:parentView], CGRectMake(330, 120, 100, 200)));
|
||||
XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 100, 200)));
|
||||
XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(120, 120, 200, 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
|
||||
|
|
|
@ -60,6 +60,12 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
|
|||
*/
|
||||
- (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
|
||||
* 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
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
|
|
@ -68,6 +68,12 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
|
|||
- (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;
|
||||
|
||||
/**
|
||||
* Size flexibility type used to find size constraints.
|
||||
* Default to RCTRootViewSizeFlexibilityNone
|
||||
|
|
|
@ -535,6 +535,29 @@ RCT_POSITION_PROPERTY(Left, left, LEFT)
|
|||
[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
|
||||
{
|
||||
_cssNode->style.position[CSS_LEFT] = topLeft.x;
|
||||
|
|
|
@ -10,16 +10,9 @@ if [ -z "$1" ]
|
|||
exit 255
|
||||
fi
|
||||
|
||||
xctool \
|
||||
-project IntegrationTests/IntegrationTests.xcodeproj \
|
||||
-scheme IntegrationTests \
|
||||
-sdk iphonesimulator8.1 \
|
||||
-destination "platform=iOS Simulator,OS=${1},name=iPhone 5" \
|
||||
build test
|
||||
|
||||
xctool \
|
||||
-project Examples/UIExplorer/UIExplorer.xcodeproj \
|
||||
-scheme UIExplorer \
|
||||
-sdk iphonesimulator8.1 \
|
||||
-sdk iphonesimulator${1} \
|
||||
-destination "platform=iOS Simulator,OS=${1},name=iPhone 5" \
|
||||
build test
|
||||
|
|
Loading…
Reference in New Issue