diff --git a/Examples/UIExplorer/BoxShadowExample.js b/Examples/UIExplorer/BoxShadowExample.js
new file mode 100644
index 000000000..12568c1b6
--- /dev/null
+++ b/Examples/UIExplorer/BoxShadowExample.js
@@ -0,0 +1,85 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+ Image,
+ StyleSheet,
+ View
+} = React;
+
+var styles = StyleSheet.create({
+ box: {
+ width: 100,
+ height: 100,
+ borderWidth: 2,
+ },
+ shadow1: {
+ shadowOpacity: 0.5,
+ shadowRadius: 3,
+ shadowOffset: {width: 2, height: 2},
+ },
+ shadow2: {
+ shadowOpacity: 1.0,
+ shadowColor: 'red',
+ shadowRadius: 0,
+ shadowOffset: {width: 3, height: 3},
+ },
+});
+
+exports.title = 'Box Shadow';
+exports.description = 'Demonstrates some of the shadow styles available to Views.';
+exports.examples = [
+ {
+ title: 'Basic shadow',
+ description: 'shadowOpacity: 0.5, shadowOffset: {2, 2}',
+ render() {
+ return ;
+ }
+ },
+ {
+ title: 'Colored shadow',
+ description: 'shadowColor: \'red\', shadowRadius: 0',
+ render() {
+ return ;
+ }
+ },
+ {
+ title: 'Shaped shadow',
+ description: 'borderRadius: 50',
+ render() {
+ return ;
+ }
+ },
+ {
+ title: 'Image shadow',
+ description: 'Image shadows are derived exactly from the pixels.',
+ render() {
+ return ;
+ }
+ },
+ {
+ title: 'Child shadow',
+ description: 'For views without an opaque background color, shadow will be derived from the subviews.',
+ render() {
+ return
+
+ ;
+ }
+ },
+];
diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js
index 0c1e9e2e7..92f0f0bad 100644
--- a/Examples/UIExplorer/UIExplorerList.ios.js
+++ b/Examples/UIExplorer/UIExplorerList.ios.js
@@ -66,6 +66,7 @@ var APIS = [
require('./AppStateIOSExample'),
require('./AsyncStorageExample'),
require('./BorderExample'),
+ require('./BoxShadowExample'),
require('./CameraRollExample'),
require('./ClipboardExample'),
require('./GeolocationExample'),
diff --git a/Libraries/Components/View/ShadowPropTypesIOS.js b/Libraries/Components/View/ShadowPropTypesIOS.js
new file mode 100644
index 000000000..c770ca9ed
--- /dev/null
+++ b/Libraries/Components/View/ShadowPropTypesIOS.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ShadowPropTypesIOS
+ * @flow
+ */
+'use strict';
+
+var ColorPropType = require('ColorPropType');
+var ReactPropTypes = require('ReactPropTypes');
+
+var ShadowPropTypesIOS = {
+ /**
+ * Sets the drop shadow color
+ * @platform ios
+ */
+ shadowColor: ColorPropType,
+ /**
+ * Sets the drop shadow offset
+ * @platform ios
+ */
+ shadowOffset: ReactPropTypes.shape(
+ {width: ReactPropTypes.number, height: ReactPropTypes.number}
+ ),
+ /**
+ * Sets the drop shadow opacity (multiplied by the color's alpha component)
+ * @platform ios
+ */
+ shadowOpacity: ReactPropTypes.number,
+ /**
+ * Sets the drop shadow blur radius
+ * @platform ios
+ */
+ shadowRadius: ReactPropTypes.number,
+};
+
+module.exports = ShadowPropTypesIOS;
diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js
index cb193be1e..619df34c8 100644
--- a/Libraries/Components/View/ViewStylePropTypes.js
+++ b/Libraries/Components/View/ViewStylePropTypes.js
@@ -14,6 +14,7 @@
var LayoutPropTypes = require('LayoutPropTypes');
var ReactPropTypes = require('ReactPropTypes');
var ColorPropType = require('ColorPropType');
+var ShadowPropTypesIOS = require('ShadowPropTypesIOS');
var TransformPropTypes = require('TransformPropTypes');
/**
@@ -21,6 +22,7 @@ var TransformPropTypes = require('TransformPropTypes');
*/
var ViewStylePropTypes = {
...LayoutPropTypes,
+ ...ShadowPropTypesIOS,
...TransformPropTypes,
backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']),
backgroundColor: ColorPropType,
@@ -42,12 +44,6 @@ var ViewStylePropTypes = {
borderLeftWidth: ReactPropTypes.number,
opacity: ReactPropTypes.number,
overflow: ReactPropTypes.oneOf(['visible', 'hidden']),
- shadowColor: ColorPropType,
- shadowOffset: ReactPropTypes.shape(
- {width: ReactPropTypes.number, height: ReactPropTypes.number}
- ),
- shadowOpacity: ReactPropTypes.number,
- shadowRadius: ReactPropTypes.number,
/**
* (Android-only) Sets the elevation of a view, using Android's underlying
* [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation).
diff --git a/Libraries/Image/ImageStylePropTypes.js b/Libraries/Image/ImageStylePropTypes.js
index ec806b1b8..5e99a54b0 100644
--- a/Libraries/Image/ImageStylePropTypes.js
+++ b/Libraries/Image/ImageStylePropTypes.js
@@ -15,10 +15,12 @@ var ImageResizeMode = require('ImageResizeMode');
var LayoutPropTypes = require('LayoutPropTypes');
var ReactPropTypes = require('ReactPropTypes');
var ColorPropType = require('ColorPropType');
+var ShadowPropTypesIOS = require('ShadowPropTypesIOS');
var TransformPropTypes = require('TransformPropTypes');
var ImageStylePropTypes = {
...LayoutPropTypes,
+ ...ShadowPropTypesIOS,
...TransformPropTypes,
resizeMode: ReactPropTypes.oneOf(Object.keys(ImageResizeMode)),
backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']),
diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m
index 4d4380d42..eebbc9c32 100644
--- a/Libraries/Text/RCTShadowText.m
+++ b/Libraries/Text/RCTShadowText.m
@@ -373,7 +373,6 @@ RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL)
RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat)
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat)
RCT_TEXT_PROPERTY(NumberOfLines, _numberOfLines, NSUInteger)
-RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize)
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment)
RCT_TEXT_PROPERTY(TextDecorationColor, _textDecorationColor, UIColor *);
RCT_TEXT_PROPERTY(TextDecorationLine, _textDecorationLine, RCTTextDecorationLineType);
diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m
index 7a28ab928..2a7e4d98b 100644
--- a/Libraries/Text/RCTTextManager.m
+++ b/Libraries/Text/RCTTextManager.m
@@ -51,7 +51,6 @@ RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
-RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize)
RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
RCT_EXPORT_SHADOW_PROPERTY(textDecorationStyle, NSUnderlineStyle)
RCT_EXPORT_SHADOW_PROPERTY(textDecorationColor, UIColor)
diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m
index 7c773691e..010c62c49 100644
--- a/React/Views/RCTView.m
+++ b/React/Views/RCTView.m
@@ -515,8 +515,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
// If frame is zero, or below the threshold where the border radii can
// be rendered as a stretchable image, we'll need to re-render.
// TODO: detect up-front if re-rendering is necessary
+ CGSize oldSize = self.bounds.size;
[super reactSetFrame:frame];
- [self.layer setNeedsDisplay];
+ if (!CGSizeEqualToSize(self.bounds.size, oldSize)) {
+ [self.layer setNeedsDisplay];
+ }
}
- (void)displayLayer:(CALayer *)layer
@@ -525,6 +528,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
return;
}
+ RCTUpdateShadowPathForView(self);
+
const RCTCornerRadii cornerRadii = [self cornerRadii];
const UIEdgeInsets borderInsets = [self bordersAsInsets];
const RCTBorderColors borderColors = [self borderColors];
@@ -608,6 +613,44 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
[self updateClippingForLayer:layer];
}
+static BOOL RCTLayerHasShadow(CALayer *layer)
+{
+ return layer.shadowOpacity * CGColorGetAlpha(layer.shadowColor) > 0;
+}
+
+- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
+{
+ // Inherit background color if a shadow has been set, as an optimization
+ if (RCTLayerHasShadow(self.layer)) {
+ self.backgroundColor = inheritedBackgroundColor;
+ }
+}
+
+static void RCTUpdateShadowPathForView(RCTView *view)
+{
+ if (RCTLayerHasShadow(view.layer)) {
+ if (CGColorGetAlpha(view.backgroundColor.CGColor) > 0.999) {
+
+ // If view has a solid background color, calculate shadow path from border
+ const RCTCornerRadii cornerRadii = [view cornerRadii];
+ const RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero);
+ CGPathRef shadowPath = RCTPathCreateWithRoundedRect(view.bounds, cornerInsets, NULL);
+ view.layer.shadowPath = shadowPath;
+ CGPathRelease(shadowPath);
+
+ } else {
+
+ // Can't accurately calculate box shadow, so fall back to pixel-based shadow
+ view.layer.shadowPath = nil;
+
+ RCTLogWarn(@"View #%@ of type %@ has a shadow set but cannot calculate "
+ "shadow efficiently. Consider setting a background color to "
+ "fix this, or apply the shadow to a more specific component.",
+ view.reactTag, [view class]);
+ }
+ }
+}
+
- (void)updateClippingForLayer:(CALayer *)layer
{
CALayer *mask = nil;
@@ -691,14 +734,14 @@ setBorderRadius(BottomRight)
#pragma mark - Border Style
-#define setBorderStyle(side) \
+#define setBorderStyle(side) \
- (void)setBorder##side##Style:(RCTBorderStyle)style \
- { \
- if (_border##side##Style == style) { \
- return; \
- } \
- _border##side##Style = style; \
- [self.layer setNeedsDisplay]; \
+ { \
+ if (_border##side##Style == style) { \
+ return; \
+ } \
+ _border##side##Style = style; \
+ [self.layer setNeedsDisplay]; \
}
setBorderStyle()