diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js
index e21b7c3bf..8c7b4ff86 100644
--- a/Examples/UIExplorer/TextExample.ios.js
+++ b/Examples/UIExplorer/TextExample.ios.js
@@ -422,12 +422,13 @@ exports.examples = [
);
},
}, {
- title: 'Inline images',
+ title: 'Inline views',
render: function() {
return (
- This text contains an inline image . Neat, huh?
+ This text contains an inline blue view and
+ an inline image . Neat, huh?
);
diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js
index 7c4966568..b2f4718f7 100644
--- a/Libraries/Image/Image.ios.js
+++ b/Libraries/Image/Image.ios.js
@@ -201,10 +201,6 @@ var Image = React.createClass({
validAttributes: ReactNativeViewAttributes.UIView
},
- contextTypes: {
- isInAParentText: React.PropTypes.bool
- },
-
render: function() {
var source = resolveAssetSource(this.props.source) || {};
var {width, height, uri} = source;
@@ -225,13 +221,6 @@ var Image = React.createClass({
console.warn('The component requires a `source` property rather than `src`.');
}
- if (this.context.isInAParentText) {
- RawImage = RCTVirtualImage;
- if (!width || !height) {
- console.warn('You must specify a width and height for the image %s', uri);
- }
- }
-
return (
-#import "RCTImageComponent.h"
#import "RCTResizeMode.h"
@class RCTBridge;
@class RCTImageSource;
-@interface RCTImageView : UIImageView
+@interface RCTImageView : UIImageView
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
diff --git a/Libraries/Image/RCTShadowVirtualImage.h b/Libraries/Image/RCTShadowVirtualImage.h
deleted file mode 100644
index b9623f736..000000000
--- a/Libraries/Image/RCTShadowVirtualImage.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * 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.
- */
-
-#import "RCTShadowView.h"
-#import "RCTImageComponent.h"
-#import "RCTImageSource.h"
-#import "RCTResizeMode.h"
-
-@class RCTBridge;
-
-/**
- * Shadow image component, used for embedding images in non-view contexts such
- * as text. This is NOT used for ordinary views.
- */
-@interface RCTShadowVirtualImage : RCTShadowView
-
-- (instancetype)initWithBridge:(RCTBridge *)bridge;
-
-@property (nonatomic, strong) RCTImageSource *source;
-@property (nonatomic, assign) RCTResizeMode resizeMode;
-
-@end
diff --git a/Libraries/Image/RCTShadowVirtualImage.m b/Libraries/Image/RCTShadowVirtualImage.m
deleted file mode 100644
index 757a48c24..000000000
--- a/Libraries/Image/RCTShadowVirtualImage.m
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * 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.
- */
-
-#import "RCTShadowVirtualImage.h"
-#import "RCTImageLoader.h"
-#import "RCTImageUtils.h"
-#import "RCTBridge.h"
-#import "RCTConvert.h"
-#import "RCTUIManager.h"
-#import "RCTUtils.h"
-
-@implementation RCTShadowVirtualImage
-{
- RCTBridge *_bridge;
- RCTImageLoaderCancellationBlock _cancellationBlock;
-}
-
-@synthesize image = _image;
-
-- (instancetype)initWithBridge:(RCTBridge *)bridge
-{
- if ((self = [super init])) {
- _bridge = bridge;
- }
- return self;
-}
-
-RCT_NOT_IMPLEMENTED(-(instancetype)init)
-
-- (void)didSetProps:(NSArray *)changedProps
-{
- [super didSetProps:changedProps];
-
- if (changedProps.count == 0) {
- // No need to reload image
- return;
- }
-
- // Cancel previous request
- if (_cancellationBlock) {
- _cancellationBlock();
- }
-
- CGSize imageSize = {
- RCTZeroIfNaN(self.width),
- RCTZeroIfNaN(self.height),
- };
-
- __weak RCTShadowVirtualImage *weakSelf = self;
- _cancellationBlock = [_bridge.imageLoader loadImageWithTag:_source.imageURL.absoluteString
- size:imageSize
- scale:RCTScreenScale()
- resizeMode:_resizeMode
- progressBlock:nil
- completionBlock:^(NSError *error, UIImage *image) {
-
- dispatch_async(_bridge.uiManager.methodQueue, ^{
- RCTShadowVirtualImage *strongSelf = weakSelf;
- if (![_source isEqual:strongSelf.source]) {
- // Bail out if source has changed since we started loading
- return;
- }
- strongSelf->_image = image;
- [strongSelf dirtyText];
- });
- }];
-}
-
-- (void)dealloc
-{
- if (_cancellationBlock) {
- _cancellationBlock();
- }
-}
-
-@end
diff --git a/Libraries/Image/RCTVirtualImageManager.h b/Libraries/Image/RCTVirtualImageManager.h
deleted file mode 100644
index b92896235..000000000
--- a/Libraries/Image/RCTVirtualImageManager.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * 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.
- */
-
-#import "RCTViewManager.h"
-
-@interface RCTVirtualImageManager : RCTViewManager
-
-@end
diff --git a/Libraries/Image/RCTVirtualImageManager.m b/Libraries/Image/RCTVirtualImageManager.m
deleted file mode 100644
index 6311010f4..000000000
--- a/Libraries/Image/RCTVirtualImageManager.m
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * 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.
- */
-
-#import "RCTVirtualImageManager.h"
-#import "RCTShadowVirtualImage.h"
-
-@implementation RCTVirtualImageManager
-
-RCT_EXPORT_MODULE()
-
-- (RCTShadowView *)shadowView
-{
- return [[RCTShadowVirtualImage alloc] initWithBridge:self.bridge];
-}
-
-RCT_EXPORT_SHADOW_PROPERTY(source, RCTImageSource)
-RCT_EXPORT_SHADOW_PROPERTY(resizeMode, UIViewContentMode)
-
-@end
diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m
index adf7be667..eced9cd0c 100644
--- a/Libraries/Text/RCTShadowText.m
+++ b/Libraries/Text/RCTShadowText.m
@@ -13,12 +13,12 @@
#import "RCTUIManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
-#import "RCTImageComponent.h"
#import "RCTLog.h"
#import "RCTShadowRawText.h"
#import "RCTText.h"
#import "RCTUtils.h"
+NSString *const RCTShadowViewAttributeName = @"RCTShadowViewAttributeName";
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
@@ -114,6 +114,45 @@ static css_dim_t RCTMeasure(void *context, float width, css_measure_mode_t width
[self dirtyPropagation];
}
+- (void)applyLayoutToChildren:(css_node_t *)node
+ viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
+ absolutePosition:(CGPoint)absolutePosition
+{
+ // Run layout on subviews.
+ NSTextStorage *textStorage = [self buildTextStorageForWidth:self.frame.size.width widthMode:CSS_MEASURE_MODE_EXACTLY];
+ NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
+ NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
+ NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
+ NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];
+ [layoutManager.textStorage enumerateAttribute:RCTShadowViewAttributeName inRange:characterRange options:0 usingBlock:^(RCTShadowView *child, NSRange range, BOOL *_) {
+ if (child) {
+ css_node_t *childNode = child.cssNode;
+ float width = childNode->style.dimensions[CSS_WIDTH];
+ float height = childNode->style.dimensions[CSS_HEIGHT];
+ if (isUndefined(width) || isUndefined(height)) {
+ RCTLogError(@"Views nested within a must have a width and height");
+ }
+ UIFont *font = [textStorage attribute:NSFontAttributeName atIndex:range.location effectiveRange:nil];
+ CGRect glyphRect = [layoutManager boundingRectForGlyphRange:range inTextContainer:textContainer];
+ CGRect childFrame = {{
+ RCTRoundPixelValue(glyphRect.origin.x),
+ RCTRoundPixelValue(glyphRect.origin.y + glyphRect.size.height - height + font.descender)
+ }, {
+ RCTRoundPixelValue(width),
+ RCTRoundPixelValue(height)
+ }};
+
+ NSRange truncatedGlyphRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location];
+ BOOL childIsTruncated = NSIntersectionRange(range, truncatedGlyphRange).length != 0;
+
+ [child collectUpdatedFrames:viewsWithNewFrame
+ withFrame:childFrame
+ hidden:childIsTruncated
+ absolutePosition:absolutePosition];
+ }
+ }];
+}
+
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width widthMode:(css_measure_mode_t)widthMode
{
if (_cachedTextStorage && width == _cachedTextStorageWidth && widthMode == _cachedTextStorageWidthMode) {
@@ -199,33 +238,48 @@ static css_dim_t RCTMeasure(void *context, float width, css_measure_mode_t width
_effectiveLetterSpacing = letterSpacing.doubleValue;
+ UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily
+ size:fontSize weight:fontWeight style:fontStyle
+ scaleMultiplier:_allowFontScaling ? _fontSizeMultiplier : 1.0];
+
+ CGFloat heightOfTallestSubview = 0.0;
NSMutableAttributedString *attributedString = [NSMutableAttributedString new];
for (RCTShadowView *child in [self reactSubviews]) {
if ([child isKindOfClass:[RCTShadowText class]]) {
RCTShadowText *shadowText = (RCTShadowText *)child;
[attributedString appendAttributedString:
- [shadowText _attributedStringWithFontFamily:fontFamily
- fontSize:fontSize
- fontWeight:fontWeight
- fontStyle:fontStyle
- letterSpacing:letterSpacing
- useBackgroundColor:YES
- foregroundColor:shadowText.color ?: foregroundColor
- backgroundColor:shadowText.backgroundColor ?: backgroundColor
- opacity:opacity * shadowText.opacity]];
+ [shadowText _attributedStringWithFontFamily:fontFamily
+ fontSize:fontSize
+ fontWeight:fontWeight
+ fontStyle:fontStyle
+ letterSpacing:letterSpacing
+ useBackgroundColor:YES
+ foregroundColor:shadowText.color ?: foregroundColor
+ backgroundColor:shadowText.backgroundColor ?: backgroundColor
+ opacity:opacity * shadowText.opacity]];
+ [child setTextComputed];
} else if ([child isKindOfClass:[RCTShadowRawText class]]) {
RCTShadowRawText *shadowRawText = (RCTShadowRawText *)child;
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:shadowRawText.text ?: @""]];
- } else if ([child conformsToProtocol:@protocol(RCTImageComponent)]) {
- NSTextAttachment *imageAttachment = [NSTextAttachment new];
- imageAttachment.image = ((id)child).image;
- imageAttachment.bounds = (CGRect){CGPointZero, {RCTZeroIfNaN(child.width), RCTZeroIfNaN(child.height)}};
- [attributedString appendAttributedString:[NSAttributedString attributedStringWithAttachment:imageAttachment]];
+ [child setTextComputed];
} else {
- RCTLogError(@" can't have any children except , or raw strings");
+ float width = child.cssNode->style.dimensions[CSS_WIDTH];
+ float height = child.cssNode->style.dimensions[CSS_HEIGHT];
+ if (isUndefined(width) || isUndefined(height)) {
+ RCTLogError(@"Views nested within a must have a width and height");
+ }
+ NSTextAttachment *attachment = [NSTextAttachment new];
+ attachment.bounds = (CGRect){CGPointZero, {width, height}};
+ NSMutableAttributedString *attachmentString = [NSMutableAttributedString new];
+ [attachmentString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
+ [attachmentString addAttribute:RCTShadowViewAttributeName value:child range:(NSRange){0, attachmentString.length}];
+ [attributedString appendAttributedString:attachmentString];
+ if (height > heightOfTallestSubview) {
+ heightOfTallestSubview = height;
+ }
+ // Don't call setTextComputed on this child. RCTTextManager takes care of
+ // processing inline UIViews.
}
-
- [child setTextComputed];
}
[self _addAttribute:NSForegroundColorAttributeName
@@ -241,13 +295,10 @@ static css_dim_t RCTMeasure(void *context, float width, css_measure_mode_t width
toAttributedString:attributedString];
}
- UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily
- size:fontSize weight:fontWeight style:fontStyle
- scaleMultiplier:_allowFontScaling ? _fontSizeMultiplier : 1.0];
[self _addAttribute:NSFontAttributeName withValue:font toAttributedString:attributedString];
[self _addAttribute:NSKernAttributeName withValue:letterSpacing toAttributedString:attributedString];
[self _addAttribute:RCTReactTagAttributeName withValue:self.reactTag toAttributedString:attributedString];
- [self _setParagraphStyleOnAttributedString:attributedString];
+ [self _setParagraphStyleOnAttributedString:attributedString heightOfTallestSubview:heightOfTallestSubview];
// create a non-mutable attributedString for use by the Text system which avoids copies down the line
_cachedAttributedString = [[NSAttributedString alloc] initWithAttributedString:attributedString];
@@ -270,6 +321,7 @@ static css_dim_t RCTMeasure(void *context, float width, css_measure_mode_t width
* varying lineHeights, we simply take the max.
*/
- (void)_setParagraphStyleOnAttributedString:(NSMutableAttributedString *)attributedString
+ heightOfTallestSubview:(CGFloat)heightOfTallestSubview
{
// check if we have lineHeight set on self
__block BOOL hasParagraphStyle = NO;
@@ -277,9 +329,7 @@ static css_dim_t RCTMeasure(void *context, float width, css_measure_mode_t width
hasParagraphStyle = YES;
}
- if (!_lineHeight) {
- self.lineHeight = 0.0;
- }
+ __block float newLineHeight = _lineHeight ?: 0.0;
CGFloat fontSizeMultiplier = _allowFontScaling ? _fontSizeMultiplier : 1.0;
@@ -288,15 +338,25 @@ static css_dim_t RCTMeasure(void *context, float width, css_measure_mode_t width
if (value) {
NSParagraphStyle *paragraphStyle = (NSParagraphStyle *)value;
CGFloat maximumLineHeight = round(paragraphStyle.maximumLineHeight / fontSizeMultiplier);
- if (maximumLineHeight > self.lineHeight) {
- self.lineHeight = maximumLineHeight;
+ if (maximumLineHeight > newLineHeight) {
+ newLineHeight = maximumLineHeight;
}
hasParagraphStyle = YES;
}
}];
- self.textAlign = _textAlign ?: NSTextAlignmentNatural;
- self.writingDirection = _writingDirection ?: NSWritingDirectionNatural;
+ if (self.lineHeight != newLineHeight) {
+ self.lineHeight = newLineHeight;
+ }
+
+ NSTextAlignment newTextAlign = _textAlign ?: NSTextAlignmentNatural;
+ if (self.textAlign != newTextAlign) {
+ self.textAlign = newTextAlign;
+ }
+ NSWritingDirection newWritingDirection = _writingDirection ?: NSWritingDirectionNatural;
+ if (self.writingDirection != newWritingDirection) {
+ self.writingDirection = newWritingDirection;
+ }
// if we found anything, set it :D
if (hasParagraphStyle) {
@@ -304,6 +364,9 @@ static css_dim_t RCTMeasure(void *context, float width, css_measure_mode_t width
paragraphStyle.alignment = _textAlign;
paragraphStyle.baseWritingDirection = _writingDirection;
CGFloat lineHeight = round(_lineHeight * fontSizeMultiplier);
+ if (heightOfTallestSubview > lineHeight) {
+ lineHeight = ceilf(heightOfTallestSubview);
+ }
paragraphStyle.minimumLineHeight = lineHeight;
paragraphStyle.maximumLineHeight = lineHeight;
[attributedString addAttribute:NSParagraphStyleAttributeName
diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m
index e57b93aa1..864fa096b 100644
--- a/Libraries/Text/RCTText.m
+++ b/Libraries/Text/RCTText.m
@@ -13,6 +13,17 @@
#import "RCTUtils.h"
#import "UIView+React.h"
+static void collectNonTextDescendants(RCTText *view, NSMutableArray *nonTextDescendants)
+{
+ for (UIView *child in view.reactSubviews) {
+ if ([child isKindOfClass:[RCTText class]]) {
+ collectNonTextDescendants((RCTText *)child, nonTextDescendants);
+ } else if (!CGRectEqualToRect(child.frame, CGRectZero)) {
+ [nonTextDescendants addObject:child];
+ }
+ }
+}
+
@implementation RCTText
{
NSTextStorage *_textStorage;
@@ -76,6 +87,21 @@
{
if (_textStorage != textStorage) {
_textStorage = textStorage;
+
+ NSMutableArray *nonTextDescendants = [NSMutableArray new];
+ collectNonTextDescendants(self, nonTextDescendants);
+ NSArray *subviews = self.subviews;
+ if (![subviews isEqualToArray:nonTextDescendants]) {
+ for (UIView *child in subviews) {
+ if (![nonTextDescendants containsObject:child]) {
+ [child removeFromSuperview];
+ }
+ }
+ for (UIView *child in nonTextDescendants) {
+ [self addSubview:child];
+ }
+ }
+
[self setNeedsDisplay];
}
}
diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m
index 92fa58949..9104ea72c 100644
--- a/Libraries/Text/RCTTextManager.m
+++ b/Libraries/Text/RCTTextManager.m
@@ -20,6 +20,18 @@
#import "RCTTextView.h"
#import "UIView+React.h"
+static void collectDirtyNonTextDescendants(RCTShadowText *shadowView, NSMutableArray *nonTextDescendants) {
+ for (RCTShadowView *child in shadowView.reactSubviews) {
+ if ([child isKindOfClass:[RCTShadowText class]]) {
+ collectDirtyNonTextDescendants((RCTShadowText *)child, nonTextDescendants);
+ } else if ([child isKindOfClass:[RCTShadowRawText class]]) {
+ // no-op
+ } else if ([child isTextDirty]) {
+ [nonTextDescendants addObject:child];
+ }
+ }
+}
+
@interface RCTShadowText (Private)
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width widthMode:(css_measure_mode_t)widthMode;
@@ -85,6 +97,7 @@ RCT_EXPORT_SHADOW_PROPERTY(textShadowColor, UIColor)
if ([shadowView isKindOfClass:[RCTShadowText class]]) {
((RCTShadowText *)shadowView).fontSizeMultiplier = self.bridge.accessibilityManager.multiplier;
[(RCTShadowText *)shadowView recomputeText];
+ collectDirtyNonTextDescendants((RCTShadowText *)shadowView, queue);
} else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) {
RCTLogError(@"Raw text cannot be used outside of a tag. Not rendering string: '%@'",
[(RCTShadowRawText *)shadowView text]);
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index c43d45fe5..f103966c3 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -217,7 +217,6 @@
13E067501A70F44B002CDEE1 /* RCTView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTView.m; sourceTree = ""; };
13E067531A70F44B002CDEE1 /* UIView+React.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+React.h"; sourceTree = ""; };
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = ""; };
- 13EF7F441BC69646003F47DD /* RCTImageComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTImageComponent.h; sourceTree = ""; };
13F17A831B8493E5007D4C75 /* RCTRedBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRedBox.h; sourceTree = ""; };
13F17A841B8493E5007D4C75 /* RCTRedBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRedBox.m; sourceTree = ""; };
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = ""; };
@@ -388,7 +387,6 @@
13AB90BF1B6FA36700713B4F /* RCTComponentData.h */,
13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */,
13AB90C01B6FA36700713B4F /* RCTComponentData.m */,
- 13EF7F441BC69646003F47DD /* RCTImageComponent.h */,
13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */,
13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */,
13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */,
diff --git a/React/Views/RCTImageComponent.h b/React/Views/RCTImageComponent.h
deleted file mode 100644
index a6916c9aa..000000000
--- a/React/Views/RCTImageComponent.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * 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.
- */
-
-#import
-
-/**
- * Generic interface for components that contain an image.
- */
-@protocol RCTImageComponent
-
-@property (nonatomic, strong, readonly) UIImage *image;
-
-@end
diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h
index ad09ae9f0..67955a9bc 100644
--- a/React/Views/RCTShadowView.h
+++ b/React/Views/RCTShadowView.h
@@ -140,12 +140,33 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry
parentProperties:(NSDictionary *)parentProperties NS_REQUIRES_SUPER;
/**
- * Recursively apply layout to children.
+ * Can be called by a parent on a child in order to calculate all views whose frame needs
+ * updating in that branch. Adds these frames to `viewsWithNewFrame`. Useful if layout
+ * enters a view where flex doesn't apply (e.g. Text) and then you want to resume flex
+ * layout on a subview.
+ */
+- (void)collectUpdatedFrames:(NSMutableSet *)viewsWithNewFrame
+ withFrame:(CGRect)frame
+ hidden:(BOOL)hidden
+ absolutePosition:(CGPoint)absolutePosition;
+
+/**
+ * Apply the CSS layout.
+ * This method also calls `applyLayoutToChildren:` internally. The functionality
+ * is split into two methods so subclasses can override `applyLayoutToChildren:`
+ * while using default implementation of `applyLayoutNode:`.
*/
- (void)applyLayoutNode:(css_node_t *)node
viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition NS_REQUIRES_SUPER;
+/**
+ * Enumerate the child nodes and tell them to apply layout.
+ */
+- (void)applyLayoutToChildren:(css_node_t *)node
+ viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
+ absolutePosition:(CGPoint)absolutePosition;
+
/**
* The following are implementation details exposed to subclasses. Do not call them directly
*/
diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m
index d55e80c62..83ce22a5b 100644
--- a/React/Views/RCTShadowView.m
+++ b/React/Views/RCTShadowView.m
@@ -157,6 +157,13 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
absolutePosition.x += node->layout.position[CSS_LEFT];
absolutePosition.y += node->layout.position[CSS_TOP];
+ [self applyLayoutToChildren:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
+}
+
+- (void)applyLayoutToChildren:(css_node_t *)node
+ viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
+ absolutePosition:(CGPoint)absolutePosition
+{
for (int i = 0; i < node->children_count; ++i) {
RCTShadowView *child = (RCTShadowView *)_reactSubviews[i];
[child applyLayoutNode:node->get_child(node->context, i)
@@ -209,6 +216,36 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
}
}
+- (void)collectUpdatedFrames:(NSMutableSet *)viewsWithNewFrame
+ withFrame:(CGRect)frame
+ hidden:(BOOL)hidden
+ absolutePosition:(CGPoint)absolutePosition
+{
+ if (_hidden != hidden) {
+ // The hidden state has changed. Even if the frame hasn't changed, add
+ // this ShadowView to viewsWithNewFrame so the UIManager will process
+ // this ShadowView's UIView and update its hidden state.
+ _hidden = hidden;
+ [viewsWithNewFrame addObject:self];
+ }
+
+ if (!CGRectEqualToRect(frame, _frame)) {
+ _cssNode->style.position_type = CSS_POSITION_ABSOLUTE;
+ _cssNode->style.dimensions[CSS_WIDTH] = frame.size.width;
+ _cssNode->style.dimensions[CSS_HEIGHT] = frame.size.height;
+ _cssNode->style.position[CSS_LEFT] = frame.origin.x;
+ _cssNode->style.position[CSS_TOP] = frame.origin.y;
+ // Our parent has asked us to change our cssNode->styles. Dirty the layout
+ // so that we can rerun layout on this node. The request came from our parent
+ // so there's no need to dirty our ancestors by calling dirtyLayout.
+ _layoutLifecycle = RCTUpdateLifecycleDirtied;
+ }
+
+ [self fillCSSNode:_cssNode];
+ layoutNode(_cssNode, frame.size.width, frame.size.height, CSS_DIRECTION_INHERIT);
+ [self applyLayoutNode:_cssNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
+}
+
- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor
{
CGPoint offset = CGPointZero;
@@ -459,6 +496,7 @@ RCT_BORDER_PROPERTY(Right, RIGHT)
{ \
_cssNode->style.dimensions[CSS_##cssProp] = value; \
[self dirtyLayout]; \
+ [self dirtyText]; \
} \
- (CGFloat)getProp \
{ \
diff --git a/docs/Text.md b/docs/Text.md
index 800137b03..48853606e 100644
--- a/docs/Text.md
+++ b/docs/Text.md
@@ -21,6 +21,20 @@ Behind the scenes, React Native converts this to a flat `NSAttributedString` or
9-17: bold, red
```
+## Nested Views (iOS Only)
+
+On iOS, you can nest views within your Text component. Here's an example:
+
+```javascript
+
+ There is a blue square
+
+ in between my text.
+
+```
+
+In order to use this feature, you must give the view a `width` and a `height`.
+
## Containers
The `` element is special relative to layout: everything inside is no longer using the flexbox layout but using text layout. This means that elements inside of a `` are no longer rectangles, but wrap when they see the end of the line.