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.