From 1b84fb919761981a94e94bc513960a4911585c06 Mon Sep 17 00:00:00 2001 From: Param Aggarwal Date: Mon, 27 Jul 2015 10:21:37 +0530 Subject: [PATCH 1/2] aspectRatio support on RCTView --- Libraries/Components/View/View.js | 15 ++++++++ Libraries/Image/Image.ios.js | 13 +++++++ .../ReactNative/ReactNativeViewAttributes.js | 6 ++++ React/Views/RCTShadowView.h | 6 ++++ React/Views/RCTShadowView.m | 35 +++++++++++++++++++ React/Views/RCTViewManager.m | 10 ++++++ 6 files changed, 85 insertions(+) diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 707d97b7ead7ca..7e6f11f33cfb0e 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -182,6 +182,21 @@ var View = React.createClass({ ]), style: stylePropType, + /** + * Makes the view resize in a fixed `aspectRatio` as the prop. It can be + * a number or a rectangle. As the width changes due to flex layout, + * the height will change in proportion with same aspect ratio. Use this + * to wrap images and other components, where you would like the aspect + * ratio to be maintained. + */ + aspectRatio: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + width: PropTypes.number, + height: PropTypes.number, + }), + ]), + /** * This is a special performance property exposed by RCTView and is useful * for scrolling content when there are many subviews, most of which are diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index e1fc6df2f16432..1bc237bd7c9a90 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -93,6 +93,19 @@ var Image = React.createClass({ * image dimensions. */ resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']), + /** + * Makes the image resize in a fixed `aspectRatio` as the prop. It can be + * a number or a rectangle. As the width changes due to flex layout, + * the height will change in proportion with same aspect ratio. Use this + * to wrap images where you would like the aspect ratio to be maintained. + */ + aspectRatio: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + width: PropTypes.number, + height: PropTypes.number, + }), + ]), /** * A unique identifier for this element to be used in UI Automation * testing scripts. diff --git a/Libraries/ReactNative/ReactNativeViewAttributes.js b/Libraries/ReactNative/ReactNativeViewAttributes.js index 50b839e1db7441..fc72d41d3194d0 100644 --- a/Libraries/ReactNative/ReactNativeViewAttributes.js +++ b/Libraries/ReactNative/ReactNativeViewAttributes.js @@ -35,6 +35,12 @@ ReactNativeViewAttributes.RCTView = merge( // many subviews that extend outside its bound. The subviews must also have // overflow: hidden, as should the containing view (or one of its superviews). removeClippedSubviews: true, + + // This property makes the view resize in the same aspect ratio as the + // `aspectRatio` rectangle. As the width changes due to flex layout, + // the height will change in proportion with same aspect ratio. + aspectRatio: true, + }); module.exports = ReactNativeViewAttributes; diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index 1c44033f6df59f..e01b748bf59b03 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -111,6 +111,12 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry); @property (nonatomic, assign) css_wrap_type_t flexWrap; @property (nonatomic, assign) CGFloat flex; +/** + * Makes the box rigid, and sizes itself in flex with the + * defined aspect ratio of the CGSize rectangle. + */ +@property (nonatomic, assign) CGSize aspectRatio; + /** * Calculate property changes that need to be propagated to the view. * The applierBlocks set contains RCTApplierBlock functions that must be applied diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index 9d56bb90624a8f..eb776da764fcb8 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -86,11 +86,46 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st : 0; } +/* + * Sets the measure function on the cssNode, when the + * aspectRatio rectangle is set. This helps calculate + * the height of the node based on width of parent and + * maintains the aspect ratio of the node. + */ +static css_dim_t RCTAspectRatioMeasure(void *context, float width) +{ + RCTShadowView *shadowView = (__bridge RCTShadowView *)context; + if (isnan(width)) { + width = shadowView.aspectRatio.width; + } + + CGFloat computedHeight = 0.0f; + if (shadowView.aspectRatio.width > 0) { + computedHeight = width * (shadowView.aspectRatio.height / shadowView.aspectRatio.width); + } + + css_dim_t result; + result.dimensions[CSS_WIDTH] = width; + result.dimensions[CSS_HEIGHT] = computedHeight; + return result; +} + - (void)fillCSSNode:(css_node_t *)node { node->children_count = (int)_reactSubviews.count; } +// Aspect Ratio +- (void)setAspectRatio:(CGSize)aspectRatio { + _aspectRatio = aspectRatio; + if (aspectRatio.width && aspectRatio.height) { + self.cssNode->measure = RCTAspectRatioMeasure; + } else { + self.cssNode->measure = nil; + } + [self dirtyLayout]; +} + // The absolute stuff is so that we can take into account our absolute position when rounding in order to // snap to the pixel grid. For example, say you have the following structure: // diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 1c6c2f17d84200..cfb6b5742687b0 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -275,4 +275,14 @@ - (RCTViewEventHandler)eventHandlerWithName:(NSString *)eventName json:(id)json RCT_REMAP_SHADOW_PROPERTY(onLayout, hasOnLayout, BOOL) +RCT_CUSTOM_SHADOW_PROPERTY(aspectRatio, CGSize, RCTShadowView) { + if ([view respondsToSelector:@selector(setAspectRatio:)]) { + if ([json isKindOfClass:NSDictionary.class]) { + [view setAspectRatio:[RCTConvert CGSize:json]]; + } else { + [view setAspectRatio:CGSizeMake([RCTConvert CGFloat:json], 1.0f)]; + } + } +} + @end From d72ecd760f16ce8faf3bb43bd8578882feb42b9b Mon Sep 17 00:00:00 2001 From: Param Aggarwal Date: Tue, 28 Jul 2015 11:15:55 +0530 Subject: [PATCH 2/2] handle the case where json is nil --- React/Views/RCTShadowView.m | 7 +++---- React/Views/RCTViewManager.m | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index eb776da764fcb8..675d6bcd9a98dd 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -87,10 +87,9 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st } /* - * Sets the measure function on the cssNode, when the - * aspectRatio rectangle is set. This helps calculate - * the height of the node based on width of parent and - * maintains the aspect ratio of the node. + * Sets the measure function on the cssNode, when the aspectRatio rectangle is + * set. This helps calculate the height of the node based on width of the parent + * and maintains the aspect ratio of the node. */ static css_dim_t RCTAspectRatioMeasure(void *context, float width) { diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index cfb6b5742687b0..de8a0002c8ff53 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -279,8 +279,10 @@ - (RCTViewEventHandler)eventHandlerWithName:(NSString *)eventName json:(id)json if ([view respondsToSelector:@selector(setAspectRatio:)]) { if ([json isKindOfClass:NSDictionary.class]) { [view setAspectRatio:[RCTConvert CGSize:json]]; - } else { + } else if (json) { [view setAspectRatio:CGSizeMake([RCTConvert CGFloat:json], 1.0f)]; + } else { + [view setAspectRatio:defaultView.aspectRatio]; } } }