diff --git a/Examples/UIExplorer/ViewExample.js b/Examples/UIExplorer/ViewExample.js
index c5cb7cae7..7dc42925b 100644
--- a/Examples/UIExplorer/ViewExample.js
+++ b/Examples/UIExplorer/ViewExample.js
@@ -1,4 +1,11 @@
/**
+ * Copyright (c) 2013-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.
+ *
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
@@ -30,7 +37,13 @@ var styles = StyleSheet.create({
backgroundColor: '#527FE4',
borderColor: '#000033',
borderWidth: 1,
- }
+ },
+ zIndex: {
+ justifyContent: 'space-around',
+ width: 100,
+ height: 50,
+ marginTop: -10,
+ },
});
var ViewBorderStyleExample = React.createClass({
@@ -74,6 +87,53 @@ var ViewBorderStyleExample = React.createClass({
}
});
+var ZIndexExample = React.createClass({
+ getInitialState() {
+ return {
+ flipped: false
+ };
+ },
+
+ render() {
+ const indices = this.state.flipped ? [-1, 0, 1, 2] : [2, 1, 0, -1];
+ return (
+
+
+ Tap to flip sorting order
+
+ ZIndex {indices[0]}
+
+
+ ZIndex {indices[1]}
+
+
+ ZIndex {indices[2]}
+
+
+ ZIndex {indices[3]}
+
+
+
+ );
+ },
+
+ _handlePress() {
+ this.setState({flipped: !this.state.flipped});
+ }
+});
+
exports.title = '';
exports.description = 'Basic building block of all UI, examples that ' +
'demonstrate some of the many styles available.';
@@ -188,5 +248,10 @@ exports.examples = [
);
},
+ }, {
+ title: 'ZIndex',
+ render: function() {
+ return ;
+ },
},
];
diff --git a/Libraries/StyleSheet/LayoutPropTypes.js b/Libraries/StyleSheet/LayoutPropTypes.js
index 95414b339..a07e16fe2 100644
--- a/Libraries/StyleSheet/LayoutPropTypes.js
+++ b/Libraries/StyleSheet/LayoutPropTypes.js
@@ -100,6 +100,9 @@ var LayoutPropTypes = {
// https://developer.mozilla.org/en-US/docs/Web/CSS/flex
flex: ReactPropTypes.number,
+
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/z-index
+ zIndex: ReactPropTypes.number,
};
module.exports = LayoutPropTypes;
diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m
index 4ab30f991..62539be9d 100644
--- a/React/Modules/RCTUIManager.m
+++ b/React/Modules/RCTUIManager.m
@@ -893,8 +893,6 @@ static void RCTSetChildren(NSNumber *containerTag,
[container insertReactSubview:view atIndex:index++];
}
}
-
- [container didUpdateReactSubviews];
}
RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerTag
@@ -965,8 +963,6 @@ RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerTag
[container insertReactSubview:destinationsToChildrenToAdd[reactIndex]
atIndex:reactIndex.integerValue];
}
-
- [container didUpdateReactSubviews];
}
RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag
diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h
index ed3e42988..d32ba1365 100644
--- a/React/Views/RCTShadowView.h
+++ b/React/Views/RCTShadowView.h
@@ -129,6 +129,11 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry
@property (nonatomic, assign) css_wrap_type_t flexWrap;
@property (nonatomic, assign) CGFloat flex;
+/**
+ * z-index, used to override sibling order in the view
+ */
+@property (nonatomic, assign) double zIndex;
+
/**
* 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 d257e9659..b518929dd 100644
--- a/React/Views/RCTShadowView.m
+++ b/React/Views/RCTShadowView.m
@@ -13,6 +13,7 @@
#import "RCTLog.h"
#import "RCTUtils.h"
#import "UIView+React.h"
+#import "UIView+Private.h"
typedef void (^RCTActionBlock)(RCTShadowView *shadowViewSelf, id value);
typedef void (^RCTResetActionBlock)(RCTShadowView *shadowViewSelf);
@@ -39,6 +40,7 @@ typedef NS_ENUM(unsigned int, meta_prop_t) {
BOOL _recomputePadding;
BOOL _recomputeMargin;
BOOL _recomputeBorder;
+ BOOL _didUpdateSubviews;
float _paddingMetaProps[META_PROP_COUNT];
float _marginMetaProps[META_PROP_COUNT];
float _borderMetaProps[META_PROP_COUNT];
@@ -179,6 +181,16 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
// dirtied, but really we should track which properties have changed and
// only update those.
+ if (_didUpdateSubviews) {
+ _didUpdateSubviews = NO;
+ [self didUpdateReactSubviews];
+ [applierBlocks addObject:^(NSDictionary *viewRegistry) {
+ UIView *view = viewRegistry[_reactTag];
+ [view clearSortedSubviews];
+ [view didUpdateReactSubviews];
+ }];
+ }
+
if (!_backgroundColor) {
UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp];
if (parentBackgroundColor) {
@@ -351,6 +363,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
[_reactSubviews insertObject:subview atIndex:atIndex];
_cssNode->children_count = (int)_reactSubviews.count;
subview->_superview = self;
+ _didUpdateSubviews = YES;
[self dirtyText];
[self dirtyLayout];
[self dirtyPropagation];
@@ -361,6 +374,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
[subview dirtyText];
[subview dirtyLayout];
[subview dirtyPropagation];
+ _didUpdateSubviews = YES;
subview->_superview = nil;
[_reactSubviews removeObject:subview];
_cssNode->children_count = (int)_reactSubviews.count;
@@ -596,6 +610,16 @@ RCT_STYLE_PROPERTY(FlexWrap, flexWrap, flex_wrap, css_wrap_type_t)
[self dirtyPropagation];
}
+- (void)setZIndex:(double)zIndex
+{
+ _zIndex = zIndex;
+ if (_superview) {
+ // Changing zIndex means the subview order of the parent needs updating
+ _superview->_didUpdateSubviews = YES;
+ [_superview dirtyPropagation];
+ }
+}
+
- (void)didUpdateReactSubviews
{
// Does nothing by default
diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h
index 7dc5bf318..ea17f568a 100644
--- a/React/Views/RCTView.h
+++ b/React/Views/RCTView.h
@@ -41,6 +41,13 @@
*/
+ (UIEdgeInsets)contentInsetsForView:(UIView *)curView;
+/**
+ * z-index, used to override sibling order in didUpdateReactSubviews. This is
+ * inherited from UIView+React, but we override it here to reduce the boxing
+ * and associated object overheads.
+ */
+@property (nonatomic, assign) double reactZIndex;
+
/**
* This is an optimization used to improve performance
* for large scrolling views with many subviews, such as a
diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m
index 4d1b362ae..ac8718eed 100644
--- a/React/Views/RCTView.m
+++ b/React/Views/RCTView.m
@@ -99,6 +99,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
UIColor *_backgroundColor;
}
+@synthesize reactZIndex = _reactZIndex;
+
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
@@ -274,7 +276,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
- (void)react_remountAllSubviews
{
if (_removeClippedSubviews) {
- for (UIView *view in self.reactSubviews) {
+ for (UIView *view in self.sortedReactSubviews) {
if (view.superview != self) {
[self addSubview:view];
[view react_remountAllSubviews];
@@ -313,7 +315,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
clipView = self;
// Mount / unmount views
- for (UIView *view in self.reactSubviews) {
+ for (UIView *view in self.sortedReactSubviews) {
if (!CGRectIsEmpty(CGRectIntersection(clipRect, view.frame))) {
// View is at least partially visible, so remount it if unmounted
diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m
index 899b3f031..c54e726c3 100644
--- a/React/Views/RCTViewManager.m
+++ b/React/Views/RCTViewManager.m
@@ -246,6 +246,8 @@ RCT_VIEW_BORDER_RADIUS_PROPERTY(TopRight)
RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomLeft)
RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomRight)
+RCT_REMAP_VIEW_PROPERTY(zIndex, reactZIndex, double)
+
#pragma mark - ShadowView properties
RCT_EXPORT_SHADOW_PROPERTY(backgroundColor, UIColor)
@@ -290,4 +292,6 @@ RCT_EXPORT_SHADOW_PROPERTY(position, css_position_type_t)
RCT_EXPORT_SHADOW_PROPERTY(onLayout, RCTDirectEventBlock)
+RCT_EXPORT_SHADOW_PROPERTY(zIndex, double)
+
@end
diff --git a/React/Views/UIView+Private.h b/React/Views/UIView+Private.h
index 14e6fcd2b..057c46293 100644
--- a/React/Views/UIView+Private.h
+++ b/React/Views/UIView+Private.h
@@ -9,10 +9,14 @@
#import
-@interface UIView (RCTViewUnmounting)
+@interface UIView (Private)
+// remove clipped subviews implementation
- (void)react_remountAllSubviews;
- (void)react_updateClippedSubviewsWithClipRect:(CGRect)clipRect relativeToView:(UIView *)clipView;
- (UIView *)react_findClipView;
+// zIndex sorting
+- (void)clearSortedSubviews;
+
@end
diff --git a/React/Views/UIView+React.h b/React/Views/UIView+React.h
index fa779e520..61484740b 100644
--- a/React/Views/UIView+React.h
+++ b/React/Views/UIView+React.h
@@ -25,9 +25,20 @@
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex NS_REQUIRES_SUPER;
- (void)removeReactSubview:(UIView *)subview NS_REQUIRES_SUPER;
+/**
+ * z-index, used to override sibling order in didUpdateReactSubviews.
+ */
+@property (nonatomic, assign) double reactZIndex;
+
+/**
+ * The reactSubviews array, sorted by zIndex. This value is cached and
+ * automatically recalculated if views are added or removed.
+ */
+@property (nonatomic, copy, readonly) NSArray *sortedReactSubviews;
+
/**
* Updates the subviews array based on the reactSubviews. Default behavior is
- * to insert the reactSubviews into the UIView.
+ * to insert the sortedReactSubviews into the UIView.
*/
- (void)didUpdateReactSubviews;
diff --git a/React/Views/UIView+React.m b/React/Views/UIView+React.m
index f25c71644..7004493d8 100644
--- a/React/Views/UIView+React.m
+++ b/React/Views/UIView+React.m
@@ -87,9 +87,51 @@
[subview removeFromSuperview];
}
+- (double)reactZIndex
+{
+ return [objc_getAssociatedObject(self, _cmd) doubleValue];
+}
+
+- (void)setReactZIndex:(double)reactZIndex
+{
+ objc_setAssociatedObject(self, @selector(reactZIndex), @(reactZIndex), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+- (NSArray *)sortedReactSubviews
+{
+ NSArray *subviews = objc_getAssociatedObject(self, _cmd);
+ if (!subviews) {
+ // Check if sorting is required - in most cases it won't be
+ BOOL sortingRequired = NO;
+ for (UIView *subview in self.reactSubviews) {
+ if (subview.reactZIndex != 0) {
+ sortingRequired = YES;
+ break;
+ }
+ }
+ subviews = sortingRequired ? [self.reactSubviews sortedArrayUsingComparator:^NSComparisonResult(UIView *a, UIView *b) {
+ if (a.reactZIndex > b.reactZIndex) {
+ return NSOrderedDescending;
+ } else {
+ // ensure sorting is stable by treating equal zIndex as ascending so
+ // that original order is preserved
+ return NSOrderedAscending;
+ }
+ }] : self.reactSubviews;
+ objc_setAssociatedObject(self, _cmd, subviews, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+ }
+ return subviews;
+}
+
+// private method, used to reset sort
+- (void)clearSortedSubviews
+{
+ objc_setAssociatedObject(self, @selector(sortedReactSubviews), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
- (void)didUpdateReactSubviews
{
- for (UIView *subview in self.reactSubviews) {
+ for (UIView *subview in self.sortedReactSubviews) {
[self addSubview:subview];
}
}