diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js
index 922dd9607d4b88..ac50209fd50fcf 100644
--- a/Examples/UIExplorer/TextInputExample.js
+++ b/Examples/UIExplorer/TextInputExample.js
@@ -108,6 +108,14 @@ var styles = StyleSheet.create({
fontFamily: 'Cochin',
height: 60,
},
+ multielineWithoutHeight: {
+ borderWidth: 0.5,
+ borderColor: '#0f0f0f',
+ flex: 1,
+ fontSize: 13,
+ padding: 4,
+ marginBottom: 4,
+ },
multilineChild: {
width: 50,
height: 40,
@@ -386,6 +394,14 @@ exports.examples = [
style={styles.multiline}>
+
+
)
}
diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js
index c21184b7da5130..f3b53bb36f9fa5 100644
--- a/Libraries/Components/TextInput/TextInput.js
+++ b/Libraries/Components/TextInput/TextInput.js
@@ -41,10 +41,12 @@ var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
clearTextOnFocus: true,
color: true,
editable: true,
+ scrollEnabled: true,
fontFamily: true,
fontSize: true,
fontStyle: true,
fontWeight: true,
+ numberOfLines: true,
keyboardType: true,
returnKeyType: true,
enablesReturnKeyAutomatically: true,
@@ -68,6 +70,8 @@ var onlyMultiline = {
onSelectionChange: true,
onTextInput: true,
children: true,
+ scrollEnabled: true,
+ numberOfLines: true
};
var notMultiline = {
@@ -167,6 +171,12 @@ var TextInput = React.createClass({
* If false, text is not editable. Default value is true.
*/
editable: PropTypes.bool,
+ /**
+ * If false, text view is not scrollable. Default value is true.
+ * Please set to false if your're using the auto height calculation of
+ * TextView.
+ */
+ scrollEnabled: PropTypes.bool,
/**
* Determines which keyboard to open, e.g.`numeric`.
*/
@@ -484,6 +494,7 @@ var TextInput = React.createClass({
children={children}
mostRecentEventCounter={this.state.mostRecentEventCounter}
editable={this.props.editable}
+ scrollEnabled={this.props.scrollEnabled}
keyboardType={keyboardType}
returnKeyType={returnKeyType}
enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically}
@@ -498,6 +509,7 @@ var TextInput = React.createClass({
placeholder={this.props.placeholder}
placeholderTextColor={this.props.placeholderTextColor}
text={this.state.bufferedValue}
+ numberOfLines={this.props.numberOfLines}
autoCapitalize={autoCapitalize}
autoCorrect={this.props.autoCorrect}
clearButtonMode={clearButtonMode}
@@ -557,6 +569,10 @@ var TextInput = React.createClass({
_onChange: function(event: Event) {
if (this.props.controlled && event.nativeEvent.text !== this.props.value) {
this.refs.input.setNativeProps({text: this.props.value});
+ } else {
+ if (this.props.multiline) {
+ this.refs.input.setNativeProps({textUpdate: {text: event.nativeEvent.text}});
+ }
}
this.props.onChange && this.props.onChange(event);
this.props.onChangeText && this.props.onChangeText(event.nativeEvent.text);
diff --git a/Libraries/Text/NSAttributedString+EmptyStringWithAttributes.h b/Libraries/Text/NSAttributedString+EmptyStringWithAttributes.h
new file mode 100644
index 00000000000000..253d5b6b44a85d
--- /dev/null
+++ b/Libraries/Text/NSAttributedString+EmptyStringWithAttributes.h
@@ -0,0 +1,30 @@
+/**
+ * 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
+
+/**
+ * Problem: The NSAttributedString can not store Attributes if the String is empty, because every Attribute is associated with a certain NSRange in the string. That's why an empty String can not store any attributes, for there is not a single valid range. This results in two problems, when we're dealing with an empty string:
+ - RCTMeasure function in RCTShadowTextView can not calculate the height correctly, because the Attributes are not set for an empty string.
+ - The UITextView will not be displayed in the correct height: i.e. the cursor will always be the default height and not the size set by the Font size.
+
+ Solution: The NSAttributedString can never be empty if we want to store our Attributes in the String. That's why RCTShadowTextView will create a NSString only containing one letter if _text is empty before passing it to the RCTAttributedStringHanlder. Also it's sets the isEmptyStringWithAttributes variable to true, so other componenets may check if the value of the string is really meant to be displayed or just so we can store the Text Attributes somehow.
+
+ Problems Solved:
+ - RCTMeasure works correctly because we always calculate with a non empty string.
+ - UITextView works correctly because we can check for the isEmptyStingWithAttributes variable in RCTTextView and copy the attributes of the NSAttributedString into UITextViews typingAttributes variable.
+
+ Conclusion:
+ I am aware that this may not be the most elegant solution :/ . If every one comes up with a better idea, please contact me on twitter: @lukasreichart or open an issue on github.
+ */
+@interface NSAttributedString (EmptyStringWithAttributes)
+
+@property (nonatomic, assign) BOOL isEmptyStringWithAttributes;
+
+@end
diff --git a/Libraries/Text/NSAttributedString+EmptyStringWithAttributes.m b/Libraries/Text/NSAttributedString+EmptyStringWithAttributes.m
new file mode 100644
index 00000000000000..b11d447e04ab3d
--- /dev/null
+++ b/Libraries/Text/NSAttributedString+EmptyStringWithAttributes.m
@@ -0,0 +1,29 @@
+/**
+ * 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
+#import "NSAttributedString+EmptyStringWithAttributes.h"
+
+@implementation NSAttributedString (EmptyStringWithAttributes)
+
+- (BOOL)isEmptyStringWithAttributes
+{
+ NSNumber *value = objc_getAssociatedObject(self, @selector(isEmptyStringWithAttributes));
+ if (value) {
+ return [value boolValue];
+ }
+ return false;
+}
+
+- (void)setIsEmptyStringWithAttributes:(BOOL)isEmptyStringWithAttributes
+{
+ objc_setAssociatedObject(self, @selector(isEmptyStringWithAttributes), [NSNumber numberWithBool:isEmptyStringWithAttributes], OBJC_ASSOCIATION_ASSIGN);
+}
+
+@end
diff --git a/Libraries/Text/RCTAttributedStringHandler.h b/Libraries/Text/RCTAttributedStringHandler.h
new file mode 100644
index 00000000000000..d343b4a275110b
--- /dev/null
+++ b/Libraries/Text/RCTAttributedStringHandler.h
@@ -0,0 +1,38 @@
+/**
+ * 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
+#import
+
+#import "RCTShadowView.h"
+
+/**
+ * The RCTAttributedStringHandler class stores attributes that can be applied to a string.
+ * Using the attributedString function one can generate an NSAttributedString from a NSString applying those attributes.
+ */
+@interface RCTAttributedStringHandler : NSObject
+
+@property (nonatomic, assign) NSWritingDirection writingDirection;
+@property (nonatomic, strong) UIColor *textBackgroundColor;
+@property (nonatomic, strong) UIColor *textColor;
+@property (nonatomic, copy) NSString *fontFamily;
+@property (nonatomic, assign) CGFloat fontSize;
+@property (nonatomic, copy) NSString *fontWeight;
+@property (nonatomic, copy) NSString *fontStyle;
+@property (nonatomic, assign) BOOL isHighlighted;
+@property (nonatomic, assign) CGFloat lineHeight;
+@property (nonatomic, assign) NSTextAlignment textAlign;
+
+@property (nonatomic, strong, readonly) NSAttributedString *cachedAttributedString;
+
+-(instancetype)initWithShadowView:(RCTShadowView *)shadowView;
+
+- (NSAttributedString *)attributedString:(NSString *)stringToProcess;
+
+@end
diff --git a/Libraries/Text/RCTAttributedStringHandler.m b/Libraries/Text/RCTAttributedStringHandler.m
new file mode 100644
index 00000000000000..950b0fc3d1af8a
--- /dev/null
+++ b/Libraries/Text/RCTAttributedStringHandler.m
@@ -0,0 +1,139 @@
+/**
+ * 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 "RCTAttributedStringHandler.h"
+
+#import "RCTConvert.h"
+#import "RCTUtils.h"
+#import "RCTShadowView.h"
+
+@implementation RCTAttributedStringHandler {
+ UIFont *_font;
+ RCTShadowView *_shadowView;
+}
+
+-(instancetype)initWithShadowView:(RCTShadowView *)shadowView;
+{
+ if ((self = [super init])) {
+ _fontSize = NAN;
+ _isHighlighted = NO;
+ _shadowView = shadowView;
+ }
+
+ return self;
+}
+
+- (NSAttributedString *)attributedString:(NSString *)stringToProcess
+{
+ return [self _attributedString:stringToProcess
+ WithFontFamily:nil
+ fontSize:0
+ fontWeight:nil
+ fontStyle:nil ];
+}
+
+- (NSAttributedString *)_attributedString:(NSString *)stringToProcess
+ WithFontFamily:(NSString *)fontFamily
+ fontSize:(CGFloat)fontSize
+ fontWeight:(NSString *)fontWeight
+ fontStyle:(NSString *)fontStyle
+
+{
+ if (!stringToProcess) {
+ return [[NSAttributedString alloc]init];
+ }
+ if ( _fontSize && !isnan(_fontSize)) {
+ fontSize = _fontSize;
+ }
+ if (_fontWeight) {
+ fontWeight = _fontWeight;
+ }
+ if (_fontStyle) {
+ fontStyle = _fontStyle;
+ }
+ if (_fontFamily) {
+ fontFamily = _fontFamily;
+ }
+
+ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:stringToProcess];
+
+ if (_textColor) {
+ [self _addAttribute:NSForegroundColorAttributeName withValue:self.textColor toAttributedString:attributedString];
+ }
+ if (_isHighlighted) {
+ [self _addAttribute:@"IsHighlightedAttributeName" withValue:@YES toAttributedString:attributedString];
+ }
+ if (_textBackgroundColor) {
+ [self _addAttribute:NSBackgroundColorAttributeName withValue:self.textBackgroundColor toAttributedString:attributedString];
+ }
+
+ _font = [RCTConvert UIFont:nil withFamily:fontFamily size:@(fontSize) weight:fontWeight style:fontStyle];
+ [self _addAttribute:NSFontAttributeName withValue:_font toAttributedString:attributedString];
+ [self _addAttribute:@"IsHighlightedAttributeName" withValue:_shadowView.reactTag toAttributedString:attributedString];
+ [self _setParagraphStyleOnAttributedString:attributedString];
+
+ // create a non-mutable attributedString for use by the Text system which avoids copies down the line
+ _cachedAttributedString = [[NSAttributedString alloc] initWithAttributedString:attributedString];
+
+ return _cachedAttributedString;
+}
+
+- (void)_addAttribute:(NSString *)attribute withValue:(id)attributeValue toAttributedString:(NSMutableAttributedString *)attributedString
+{
+ [attributedString enumerateAttribute:attribute inRange:NSMakeRange(0, [attributedString length]) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
+ if (!value) {
+ [attributedString addAttribute:attribute value:attributeValue range:range];
+ }
+ }];
+}
+
+/*
+ * LineHeight works the same way line-height works in the web: if children and self have
+ * varying lineHeights, we simply take the max.
+ */
+- (void)_setParagraphStyleOnAttributedString:(NSMutableAttributedString *)attributedString
+{
+ // check if we have lineHeight set on self
+ __block BOOL hasParagraphStyle = NO;
+ if (_lineHeight || _textAlign) {
+ hasParagraphStyle = YES;
+ }
+
+ if (!_lineHeight) {
+ self.lineHeight = 0.0;
+ }
+
+ // check for lineHeight on each of our children, update the max as we go (in self.lineHeight)
+ [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:NSMakeRange(0, [attributedString length]) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
+ if (value) {
+ NSParagraphStyle *paragraphStyle = (NSParagraphStyle *)value;
+ if ([paragraphStyle maximumLineHeight] > _lineHeight) {
+ self.lineHeight = [paragraphStyle maximumLineHeight];
+ }
+ hasParagraphStyle = YES;
+ }
+ }];
+
+ self.textAlign = _textAlign ?: NSTextAlignmentNatural;
+ self.writingDirection = _writingDirection ?: NSWritingDirectionNatural;
+
+ // if we found anything, set it :D
+ if (hasParagraphStyle) {
+ NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
+ paragraphStyle.alignment = _textAlign;
+ paragraphStyle.baseWritingDirection = _writingDirection;
+ paragraphStyle.minimumLineHeight = _lineHeight;
+ paragraphStyle.maximumLineHeight = _lineHeight;
+ [attributedString addAttribute:NSParagraphStyleAttributeName
+ value:paragraphStyle
+ range:(NSRange){0, attributedString.length}];
+ }
+}
+
+@end
diff --git a/Libraries/Text/RCTShadowTextView.h b/Libraries/Text/RCTShadowTextView.h
new file mode 100644
index 00000000000000..8cc7c30448b76e
--- /dev/null
+++ b/Libraries/Text/RCTShadowTextView.h
@@ -0,0 +1,51 @@
+/**
+ * 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
+
+#import "RCTAttributedStringHandler.h"
+#import "RCTShadowView.h"
+
+@interface RCTShadowTextView : RCTShadowView
+
+// Not exposed to JS
+@property (nonatomic, copy, readonly) NSAttributedString *attributedString;
+@property (nonatomic, copy, readonly) NSAttributedString *attributedPlaceholderString;
+
+// Used to calculate the height of the UITextView
+@property (nonatomic, strong, readonly) NSLayoutManager *layoutManager;
+@property (nonatomic, strong, readonly) NSTextContainer *textContainer;
+
+
+// Exposed to JS
+// Update the text of the text field. ( Resets the current text field. )
+@property (nonatomic, copy) NSString *text;
+// Updates only the value of the shadow text updateTextView is set to false.
+// This is used to persist updates from TextInput.js back to the ShadowView wihtout reloading the UITextField.
+- (void)setText:(NSString *)text updateTextView:(BOOL)updateTextView;
+@property (nonatomic, copy) NSString *placeholder;
+
+// Styling Text and placeholder text.
+@property (nonatomic, strong) UIColor *textColor;
+@property (nonatomic, strong) UIColor *placeholderTextColor;
+
+@property (nonatomic, assign) NSWritingDirection writingDirection;
+@property (nonatomic, strong) UIColor *textBackgroundColor;
+@property (nonatomic, copy) NSString *fontFamily;
+@property (nonatomic, assign) CGFloat fontSize;
+@property (nonatomic, copy) NSString *fontWeight;
+@property (nonatomic, copy) NSString *fontStyle;
+@property (nonatomic, assign) BOOL isHighlighted;
+@property (nonatomic, assign) CGFloat lineHeight;
+@property (nonatomic, assign) NSTextAlignment textAlign;
+
+@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
+@property (nonatomic, assign) NSLineBreakMode truncationMode;
+
+@end
diff --git a/Libraries/Text/RCTShadowTextView.m b/Libraries/Text/RCTShadowTextView.m
new file mode 100644
index 00000000000000..deb5d63aed3afb
--- /dev/null
+++ b/Libraries/Text/RCTShadowTextView.m
@@ -0,0 +1,222 @@
+/**
+ * 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 "RCTShadowTextView.h"
+
+#import "RCTConvert.h"
+#import "RCTLog.h"
+#import "RCTUtils.h"
+#import "NSAttributedString+EmptyStringWithAttributes.h"
+
+static css_dim_t RCTMeasure(void *context, float width)
+{
+ RCTShadowTextView *shadowTextView = (__bridge RCTShadowTextView *)context;
+
+ NSAttributedString *attributedString = [shadowTextView attributedString];
+ if (attributedString.length == 0) {
+ // if the text is empty the height is defined by the placeholder string.
+ attributedString = [shadowTextView attributedPlaceholderString];
+ }
+ NSTextStorage *textStorage = [[NSTextStorage alloc]initWithAttributedString:attributedString];
+
+ NSTextStorage *previousTextStorage = shadowTextView.layoutManager.textStorage;
+ if (previousTextStorage) {
+ [previousTextStorage removeLayoutManager:shadowTextView.layoutManager];
+ }
+ [textStorage addLayoutManager:shadowTextView.layoutManager];
+
+ shadowTextView.textContainer.size = CGSizeMake(isnan(width) ? CGFLOAT_MAX :width, CGFLOAT_MAX);
+ [shadowTextView.layoutManager ensureLayoutForTextContainer:shadowTextView.textContainer];
+
+ CGSize computedSize = [shadowTextView.layoutManager usedRectForTextContainer:shadowTextView.textContainer].size;
+
+ [textStorage removeLayoutManager:shadowTextView.layoutManager];
+ if (previousTextStorage) {
+ [previousTextStorage addLayoutManager:shadowTextView.layoutManager];
+ }
+
+ css_dim_t result;
+ result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
+ result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(computedSize.height);
+ return result;
+}
+
+
+@implementation RCTShadowTextView {
+ NSLayoutManager *_layoutManager;
+ NSTextContainer *_textContainer;
+ UIFont *_font;
+
+ RCTAttributedStringHandler *_stringHandler;
+ RCTAttributedStringHandler *_placeholderStringHandler;
+ BOOL _textHasBeenSetOnce;
+}
+
+
+- (instancetype)init
+{
+ if ((self = [super init])) {
+ _textContainer = [[NSTextContainer alloc] init];
+ _textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
+ _textContainer.lineFragmentPadding = 0.0;
+
+ _layoutManager = [[NSLayoutManager alloc] init];
+ [_layoutManager addTextContainer:_textContainer];
+
+ _stringHandler = [[RCTAttributedStringHandler alloc] initWithShadowView:self];
+ _placeholderStringHandler = [[RCTAttributedStringHandler alloc] initWithShadowView:self];
+ _placeholderStringHandler.textColor = [UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:0.098/255.0 alpha:0.22];
+ _textHasBeenSetOnce = false;
+ }
+
+ return self;
+}
+
+- (NSAttributedString *)attributedString
+{
+ if ( !([self isTextDirty] || [self isLayoutDirty]) && _stringHandler.cachedAttributedString) {
+ return _stringHandler.cachedAttributedString;
+ }
+
+ // Never pass an empty string to the _stringHandler.
+ NSString *stringToProcess = _text;
+ BOOL isEmptyStringWithAttributes = false;
+ if ( !(_text && _text.length )) {
+ stringToProcess = @"A";
+ isEmptyStringWithAttributes = true;
+ }
+
+ [_stringHandler attributedString:stringToProcess];
+ [self dirtyLayout];
+
+ [_stringHandler.cachedAttributedString setIsEmptyStringWithAttributes:isEmptyStringWithAttributes];
+ return _stringHandler.cachedAttributedString;
+}
+
+- (NSAttributedString *)attributedPlaceholderString
+{
+ if (![self isTextDirty] && _placeholderStringHandler.cachedAttributedString) {
+ return _placeholderStringHandler.cachedAttributedString;
+ }
+ NSAttributedString *attributedString = [_placeholderStringHandler attributedString:_placeholder];
+ [self dirtyLayout];
+
+ return attributedString;
+}
+
+
+- (void)fillCSSNode:(css_node_t *)node
+{
+ [super fillCSSNode:node];
+ node->measure = RCTMeasure;
+}
+
+#define RCT_TEXT_PROPERTY(setProp, ivar, type) \
+- (void)set##setProp:(type)value; \
+{ \
+ivar=value; \
+[self dirtyText]; \
+}
+
+
+#define RCT_ATTR_STRING_PROPERTY(setProp, attrName, type) \
+- (void)set##setProp:(type)value; \
+{ \
+ _stringHandler.attrName = value;\
+ _placeholderStringHandler.attrName = value; \
+ [self dirtyText]; \
+} \
+- (type)attrName \
+{ \
+return _stringHandler.attrName; \
+}
+
+
+RCT_TEXT_PROPERTY(TextColor, _stringHandler.textColor, UIColor *);
+- (UIColor *)textColor
+{
+ return _stringHandler.textColor;
+}
+RCT_TEXT_PROPERTY(PlaceholderTextColor, _placeholderStringHandler.textColor, UIColor *);
+- (UIColor *)placeholderTextColor
+{
+ return _placeholderStringHandler.textColor;
+}
+
+RCT_ATTR_STRING_PROPERTY(TextBackgroundColor, textBackgroundColor, UIColor *);
+RCT_ATTR_STRING_PROPERTY(FontFamily, fontFamily, NSString *);
+RCT_ATTR_STRING_PROPERTY(FontSize, fontSize, CGFloat);
+RCT_ATTR_STRING_PROPERTY(FontWeight, fontWeight, NSString *);
+RCT_ATTR_STRING_PROPERTY(FontStyle, fontStyle, NSString *);
+RCT_ATTR_STRING_PROPERTY(LineHeight, lineHeight, CGFloat );
+RCT_ATTR_STRING_PROPERTY(TextAlign, textAlign, NSTextAlignment );
+RCT_ATTR_STRING_PROPERTY(IsHighlighted, isHighlighted, BOOL );
+RCT_ATTR_STRING_PROPERTY(WritingDirection, writingDirection, NSWritingDirection);
+
+
+- (void)setText:(NSString *)text
+{
+ if (![_text isEqualToString:text ]) {
+ _text = [text copy];
+ [self dirtyLayout];
+ [self dirtyText];
+ }
+}
+
+- (void)setPlaceholder:(NSString *)placeholder
+{
+ if (![_placeholder isEqualToString:placeholder]) {
+ _placeholder = [placeholder copy];
+ [self dirtyLayout];
+ [self dirtyText];
+ }
+}
+
+- (void)setText:(NSString *)text updateTextView:(BOOL)updateTextView
+{
+ if (!_textHasBeenSetOnce) {
+ updateTextView = true;
+ _textHasBeenSetOnce = true;
+ }
+ if (![_text isEqualToString:text ]) {
+ _text = [text copy];
+ [self dirtyLayout];
+ if (updateTextView) {
+ [self dirtyText];
+ }
+ }
+}
+
+- (void)setTruncationMode:(NSLineBreakMode)truncationMode
+{
+ _textContainer.lineBreakMode = truncationMode;
+}
+
+- (NSLineBreakMode)truncationMode
+{
+ return _textContainer.lineBreakMode;
+}
+
+- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
+{
+ if (maximumNumberOfLines < 1 ) {
+ maximumNumberOfLines = 1;
+ }
+
+ self.truncationMode = NSLineBreakByTruncatingTail;
+ _textContainer.maximumNumberOfLines = maximumNumberOfLines;
+}
+
+- (NSUInteger)maximumNumberOfLines
+{
+ return _textContainer.maximumNumberOfLines;
+}
+
+@end
+
diff --git a/Libraries/Text/RCTText.xcodeproj/project.pbxproj b/Libraries/Text/RCTText.xcodeproj/project.pbxproj
index 224c7e6b97fa7e..396c5d31b5a0f7 100644
--- a/Libraries/Text/RCTText.xcodeproj/project.pbxproj
+++ b/Libraries/Text/RCTText.xcodeproj/project.pbxproj
@@ -14,6 +14,9 @@
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CB1A9E6C5C00147676 /* RCTShadowText.m */; };
58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CD1A9E6C5C00147676 /* RCTTextManager.m */; };
58B512161A9E6EFF00147676 /* RCTText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512141A9E6EFF00147676 /* RCTText.m */; };
+ B87780921AFA36B50016FC2B /* RCTShadowTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = B87780911AFA36B50016FC2B /* RCTShadowTextView.m */; };
+ B87780951AFA37CA0016FC2B /* RCTAttributedStringHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = B87780941AFA37CA0016FC2B /* RCTAttributedStringHandler.m */; };
+ B8C3BF4C1B25860600BB4D5C /* NSAttributedString+EmptyStringWithAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = B8C3BF4B1B25860600BB4D5C /* NSAttributedString+EmptyStringWithAttributes.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -44,6 +47,12 @@
58B511CD1A9E6C5C00147676 /* RCTTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextManager.m; sourceTree = ""; };
58B512141A9E6EFF00147676 /* RCTText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTText.m; sourceTree = ""; };
58B512151A9E6EFF00147676 /* RCTText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTText.h; sourceTree = ""; };
+ B87780901AFA36B50016FC2B /* RCTShadowTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowTextView.h; sourceTree = ""; };
+ B87780911AFA36B50016FC2B /* RCTShadowTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowTextView.m; sourceTree = ""; };
+ B87780931AFA37CA0016FC2B /* RCTAttributedStringHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAttributedStringHandler.h; sourceTree = ""; };
+ B87780941AFA37CA0016FC2B /* RCTAttributedStringHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAttributedStringHandler.m; sourceTree = ""; };
+ B8C3BF4A1B25860600BB4D5C /* NSAttributedString+EmptyStringWithAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+EmptyStringWithAttributes.h"; sourceTree = ""; };
+ B8C3BF4B1B25860600BB4D5C /* NSAttributedString+EmptyStringWithAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+EmptyStringWithAttributes.m"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -60,6 +69,12 @@
58B511921A9E6C1200147676 = {
isa = PBXGroup;
children = (
+ B8C3BF4A1B25860600BB4D5C /* NSAttributedString+EmptyStringWithAttributes.h */,
+ B8C3BF4B1B25860600BB4D5C /* NSAttributedString+EmptyStringWithAttributes.m */,
+ B87780931AFA37CA0016FC2B /* RCTAttributedStringHandler.h */,
+ B87780941AFA37CA0016FC2B /* RCTAttributedStringHandler.m */,
+ B87780901AFA36B50016FC2B /* RCTShadowTextView.h */,
+ B87780911AFA36B50016FC2B /* RCTShadowTextView.m */,
58B511C61A9E6C5C00147676 /* RCTRawTextManager.h */,
58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */,
58B511C81A9E6C5C00147676 /* RCTShadowRawText.h */,
@@ -146,11 +161,14 @@
files = (
58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */,
131B6AC01AF0CD0600FFC3E0 /* RCTTextView.m in Sources */,
+ B87780951AFA37CA0016FC2B /* RCTAttributedStringHandler.m in Sources */,
58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */,
58B512161A9E6EFF00147676 /* RCTText.m in Sources */,
131B6AC11AF0CD0600FFC3E0 /* RCTTextViewManager.m in Sources */,
+ B87780921AFA36B50016FC2B /* RCTShadowTextView.m in Sources */,
58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */,
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */,
+ B8C3BF4C1B25860600BB4D5C /* NSAttributedString+EmptyStringWithAttributes.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m
index ef518d20483d70..e0bef6b249ac0b 100644
--- a/Libraries/Text/RCTTextManager.m
+++ b/Libraries/Text/RCTTextManager.m
@@ -16,6 +16,8 @@
#import "RCTShadowText.h"
#import "RCTSparseArray.h"
#import "RCTText.h"
+#import "RCTShadowTextView.h"
+#import "RCTTextView.h"
#import "UIView+React.h"
@implementation RCTTextManager
@@ -81,26 +83,7 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)
}
RCTSparseArray *reactTaggedAttributedStrings = [[RCTSparseArray alloc] init];
- NSMutableArray *queue = [NSMutableArray arrayWithObject:rootView];
- for (NSInteger i = 0; i < [queue count]; i++) {
- RCTShadowView *shadowView = queue[i];
- RCTAssert([shadowView isTextDirty], @"Don't process any nodes that don't have dirty text");
-
- if ([shadowView isKindOfClass:[RCTShadowText class]]) {
- RCTShadowText *shadowText = (RCTShadowText *)shadowView;
- reactTaggedAttributedStrings[shadowText.reactTag] = [shadowText attributedString];
- } else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) {
- RCTLogError(@"Raw text cannot be used outside of a tag. Not rendering string: '%@'", [(RCTShadowRawText *)shadowView text]);
- } else {
- for (RCTShadowView *child in [shadowView reactSubviews]) {
- if ([child isTextDirty]) {
- [queue addObject:child];
- }
- }
- }
-
- [shadowView setTextComputed];
- }
+ [self _processReactSubview:rootView toTaggedAttributedStrings:reactTaggedAttributedStrings];
[uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
[reactTaggedAttributedStrings enumerateObjectsUsingBlock:^(NSAttributedString *attributedString, NSNumber *reactTag, BOOL *stop) {
@@ -117,6 +100,36 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)
};
}
+- (BOOL)_processReactSubview:(RCTShadowView *)shadowView toTaggedAttributedStrings:(RCTSparseArray *)reactTaggedAttributedStrings
+{
+ BOOL textComputed = true;
+ RCTAssert([shadowView isTextDirty], @"Don't process any nodes that don't have dirty text");
+
+ if ([shadowView isKindOfClass:[RCTShadowText class]]) {
+ RCTShadowText *shadowText = (RCTShadowText *)shadowView;
+ reactTaggedAttributedStrings[shadowText.reactTag] = [shadowText attributedString];
+ } else if ([shadowView isKindOfClass:[RCTShadowTextView class]]) {
+ // not all text has been computed because RCTSahdowTextView has not been handled yet.
+ textComputed = false;
+ } else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) {
+ RCTLogError(@"Raw text cannot be used outside of a tag. Not rendering string: '%@'", [(RCTShadowRawText *)shadowView text]);
+ } else {
+ for (RCTShadowView *child in [shadowView reactSubviews]) {
+ if ([child isTextDirty]) {
+ BOOL textComputedResult = [self _processReactSubview:child toTaggedAttributedStrings:reactTaggedAttributedStrings];
+ if( !textComputedResult ) {
+ textComputed = false;
+ }
+ }
+ }
+ }
+ if (textComputed) {
+ [shadowView setTextComputed];
+ }
+
+ return textComputed;
+}
+
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h
index 19f2fea397b8c5..88f35f76591c6d 100644
--- a/Libraries/Text/RCTTextView.h
+++ b/Libraries/Text/RCTTextView.h
@@ -16,13 +16,18 @@
@interface RCTTextView : RCTView
+// exposed to JS
@property (nonatomic, assign) BOOL autoCorrect;
@property (nonatomic, assign) BOOL clearTextOnFocus;
@property (nonatomic, assign) BOOL selectTextOnFocus;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
-@property (nonatomic, strong) UIColor *placeholderTextColor;
-@property (nonatomic, assign) UIFont *font;
+@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
+@property (nonatomic, assign) NSLineBreakMode truncationMode;
+
+// Not exposed to JS
+@property (nonatomic, copy) NSAttributedString *attributedText;
+@property (nonatomic, copy) NSAttributedString *attributedPlaceholderText;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m
index c5947f317e0bd8..a92059305af2d3 100644
--- a/Libraries/Text/RCTTextView.m
+++ b/Libraries/Text/RCTTextView.m
@@ -8,6 +8,7 @@
*/
#import "RCTTextView.h"
+#import "NSAttributedString+EmptyStringWithAttributes.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
@@ -18,7 +19,7 @@ @implementation RCTTextView
{
RCTEventDispatcher *_eventDispatcher;
BOOL _jsRequestingFirstResponder;
- NSString *_placeholder;
+ NSAttributedString *_attributedPlacerholderText;
UITextView *_placeholderView;
UITextView *_textView;
}
@@ -28,9 +29,10 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
if ((self = [super initWithFrame:CGRectZero])) {
_contentInset = UIEdgeInsetsZero;
_eventDispatcher = eventDispatcher;
- _placeholderTextColor = [self defaultPlaceholderTextColor];
_textView = [[UITextView alloc] initWithFrame:self.bounds];
+ _textView.textContainer.lineFragmentPadding = 0.0;
+ _textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0 );
_textView.backgroundColor = [UIColor clearColor];
_textView.delegate = self;
[self addSubview:_textView];
@@ -39,15 +41,55 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
return self;
}
+- (NSAttributedString *)attributedText
+{
+ return [_textView.attributedText copy];
+}
+
+- (NSAttributedString *)attributedPlaceholderText
+{
+ return _attributedPlacerholderText;
+}
+
+- (void)setAttributedText:(NSAttributedString *)attributedText
+{
+ // save the cursors current location and disable scrolling -> otherwise UITextView will jump around.
+ BOOL oldScrollEnabled = _textView.scrollEnabled;
+ _textView.scrollEnabled = NO;
+ UITextRange *range = _textView.selectedTextRange;
+
+ // Check if we should really display the NSAttributedString's value or this is in fact an empty string.
+ if (attributedText.isEmptyStringWithAttributes) {
+ NSRange range = NSMakeRange(0, 1);
+ _textView.typingAttributes = [attributedText attributesAtIndex:0 effectiveRange:&range];
+ _textView.attributedText = [[NSAttributedString alloc]init];
+ } else {
+ _textView.attributedText = attributedText;
+ }
+
+ _textView.scrollEnabled = oldScrollEnabled;
+ _textView.selectedTextRange = range;//you keep before
+
+ [self updatePlaceholder];
+ [self setNeedsDisplay];
+}
+
+- (void)setAttributedPlaceholderText:(NSAttributedString *)attributedPlaceholderText
+{
+ _attributedPlacerholderText = attributedPlaceholderText;
+ [self updatePlaceholder];
+ [self setNeedsDisplay];
+}
+
- (void)updateFrames
{
// Adjust the insets so that they are as close as possible to single-line
// RCTTextField defaults
UIEdgeInsets adjustedInset = (UIEdgeInsets){
- _contentInset.top - 5, _contentInset.left - 4,
+ _contentInset.top, _contentInset.left,
_contentInset.bottom, _contentInset.right
};
-
+
[_textView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)];
[_placeholderView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)];
}
@@ -57,48 +99,19 @@ - (void)updatePlaceholder
[_placeholderView removeFromSuperview];
_placeholderView = nil;
- if (_placeholder) {
+ if (_attributedPlacerholderText) {
_placeholderView = [[UITextView alloc] initWithFrame:self.bounds];
+ _placeholderView.textContainer.lineFragmentPadding = 0.0;
+ _placeholderView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0 );
_placeholderView.backgroundColor = [UIColor clearColor];
_placeholderView.scrollEnabled = false;
- _placeholderView.attributedText =
- [[NSAttributedString alloc] initWithString:_placeholder attributes:@{
- NSFontAttributeName : (_textView.font ? _textView.font : [self defaultPlaceholderFont]),
- NSForegroundColorAttributeName : _placeholderTextColor
- }];
-
+ _placeholderView.attributedText = [self attributedPlaceholderText];
+
[self insertSubview:_placeholderView belowSubview:_textView];
[self _setPlaceholderVisibility];
}
}
-- (void)setFont:(UIFont *)font
-{
- _font = font;
- _textView.font = _font;
- [self updatePlaceholder];
-}
-
-- (void)setTextColor:(UIColor *)textColor
-{
- _textView.textColor = textColor;
-}
-
-- (void)setPlaceholder:(NSString *)placeholder
-{
- _placeholder = placeholder;
- [self updatePlaceholder];
-}
-
-- (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor
-{
- if (placeholderTextColor) {
- _placeholderTextColor = placeholderTextColor;
- } else {
- _placeholderTextColor = [self defaultPlaceholderTextColor];
- }
- [self updatePlaceholder];
-}
- (void)setContentInset:(UIEdgeInsets)contentInset
{
@@ -106,21 +119,18 @@ - (void)setContentInset:(UIEdgeInsets)contentInset
[self updateFrames];
}
-- (void)setText:(NSString *)text
-{
- if (![text isEqualToString:_textView.text]) {
- [_textView setText:text];
- [self _setPlaceholderVisibility];
- }
-}
-
- (void)_setPlaceholderVisibility
{
- if (_textView.text.length > 0) {
+ BOOL _placeholderViewWasHidden = _placeholderView.isHidden;
+ if (_textView.attributedText.length > 0) {
[_placeholderView setHidden:YES];
} else {
[_placeholderView setHidden:NO];
}
+
+ if (_placeholderViewWasHidden != _placeholderView.isHidden) {
+ [self setNeedsDisplay];
+ }
}
- (void)setAutoCorrect:(BOOL)autoCorrect
@@ -133,6 +143,27 @@ - (BOOL)autoCorrect
return _textView.autocorrectionType == UITextAutocorrectionTypeYes;
}
+- (void)setTruncationMode:(NSLineBreakMode)truncationMode
+{
+ _textView.textContainer.lineBreakMode = truncationMode;
+ if (_placeholderView) {
+ _textView.textContainer.lineBreakMode = truncationMode;
+ }
+}
+
+- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
+{
+ _textView.textContainer.maximumNumberOfLines = maximumNumberOfLines;
+ if (_placeholderView) {
+ _placeholderView.textContainer.maximumNumberOfLines = maximumNumberOfLines;
+ }
+}
+
+- (NSUInteger)maximumNumberOfLines
+{
+ return _textView.textContainer.maximumNumberOfLines;
+}
+
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
if (_selectTextOnFocus) {
@@ -146,8 +177,7 @@ - (BOOL)textViewShouldBeginEditing:(UITextView *)textView
- (void)textViewDidBeginEditing:(UITextView *)textView
{
if (_clearTextOnFocus) {
- [_textView setText:@""];
- _textView.text = @"";
+ _textView.attributedText = [_textView.attributedText attributedSubstringFromRange:NSMakeRange(0, 0)];
[self _setPlaceholderVisibility];
}
@@ -203,14 +233,5 @@ - (BOOL)canBecomeFirstResponder
return _jsRequestingFirstResponder;
}
-- (UIFont *)defaultPlaceholderFont
-{
- return [UIFont fontWithName:@"Helvetica" size:17];
-}
-
-- (UIColor *)defaultPlaceholderTextColor
-{
- return [UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:0.098/255.0 alpha:0.22];
-}
@end
diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m
index 570a511157bee5..d922bc22613534 100644
--- a/Libraries/Text/RCTTextViewManager.m
+++ b/Libraries/Text/RCTTextViewManager.m
@@ -14,6 +14,9 @@
#import "RCTShadowView.h"
#import "RCTSparseArray.h"
#import "RCTTextView.h"
+#import "RCTShadowText.h"
+
+#import "RCTShadowTextView.h"
@implementation RCTTextViewManager
@@ -24,41 +27,119 @@ - (UIView *)view
return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
+- (RCTShadowView *)shadowView
+{
+ return [[RCTShadowTextView alloc] init];
+}
+
+// Data string properties
+RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString)
+RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
+RCT_CUSTOM_SHADOW_PROPERTY(textUpdate, NSString, RCTShadowTextView)
+{
+ [view setText:json[@"text"] updateTextView:json[@"updateText"]? json[@"updateText"]: false ];
+}
+
+// UITextView specific properties
RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL)
-RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
-RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
-RCT_EXPORT_VIEW_PROPERTY(text, NSString)
+RCT_REMAP_VIEW_PROPERTY(scrollEnabled, textView.scrollEnabled, BOOL )
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
RCT_REMAP_VIEW_PROPERTY(keyboardType, textView.keyboardType, UIKeyboardType)
RCT_REMAP_VIEW_PROPERTY(returnKeyType, textView.returnKeyType, UIReturnKeyType)
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, textView.enablesReturnKeyAutomatically, BOOL)
-RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType)
-RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextView)
-{
- view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
-}
-RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, RCTTextView)
-{
- view.font = [RCTConvert UIFont:view.font withWeight:json]; // defaults to normal
-}
-RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, RCTTextView)
+
+// Shadow View properties
+RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
+RCT_REMAP_SHADOW_PROPERTY(color, textColor, UIColor )
+RCT_EXPORT_SHADOW_PROPERTY(placeholderTextColor, UIColor)
+RCT_EXPORT_SHADOW_PROPERTY(fontFamily, NSString)
+RCT_EXPORT_SHADOW_PROPERTY(fontSize, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString)
+RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString)
+RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
+RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
+RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textBackgroundColor, UIColor)
+RCT_REMAP_SHADOW_PROPERTY(numberOfLines, maximumNumberOfLines, NSInteger );
+
+- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
- view.font = [RCTConvert UIFont:view.font withStyle:json]; // defaults to normal
+ NSMutableArray *uiBlocks = [NSMutableArray new];
+
+ for (RCTShadowView *rootView in shadowViewRegistry.allObjects) {
+ if (![rootView isReactRootView]) {
+ // This isn't a root view
+ continue;
+ }
+
+ if (![rootView isTextDirty]) {
+ // No text processing to be done
+ continue;
+ }
+
+ RCTSparseArray *reactTaggedAttributedStrings = [[RCTSparseArray alloc] init];
+ [self _processReactSubview:rootView toTaggedAttributedStrings:reactTaggedAttributedStrings];
+
+ [uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
+ [reactTaggedAttributedStrings enumerateObjectsUsingBlock:^(NSDictionary *attributedStringDic, NSNumber *reactTag, BOOL *stop) {
+ RCTTextView *text = viewRegistry[reactTag];
+ text.attributedText = attributedStringDic[@"attributedText"];
+ text.attributedPlaceholderText = attributedStringDic[@"attributedPlaceholderText"];
+ }];
+ }];
+ }
+
+ return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
+ for (RCTViewManagerUIBlock shadowBlock in uiBlocks) {
+ shadowBlock(uiManager, viewRegistry);
+ }
+ };
}
-RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextView)
+
+- (BOOL)_processReactSubview:(RCTShadowView *)shadowView toTaggedAttributedStrings:(RCTSparseArray *)reactTaggedAttributedStrings
{
- view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName];
+ BOOL textComputed = true;
+ RCTAssert([shadowView isTextDirty], @"Don't process any nodes that don't have dirty text");
+
+ if ([shadowView isKindOfClass:[RCTShadowTextView class]]) {
+ RCTShadowTextView *shadowTextView = (RCTShadowTextView *)shadowView;
+ reactTaggedAttributedStrings[shadowTextView.reactTag] = @{
+ @"attributedText": [shadowTextView attributedString],
+ @"attributedPlaceholderText": [shadowTextView attributedPlaceholderString]
+ };
+
+ } else if ([shadowView isKindOfClass:[RCTShadowText class]]) {
+ // not all text has been computed because a RCTShadowText has not been handled yet.
+ textComputed = false;
+ } else {
+ for (RCTShadowView *child in [shadowView reactSubviews]) {
+ if ([child isTextDirty]) {
+ BOOL textComputedResult = [self _processReactSubview:child toTaggedAttributedStrings:reactTaggedAttributedStrings];
+ if( !textComputedResult ) {
+ textComputed = false;
+ }
+ }
+ }
+ }
+ if (textComputed) {
+ [shadowView setTextComputed];
+ }
+ return textComputed;
}
-- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
+- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowTextView *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
- ((RCTTextView *)viewRegistry[reactTag]).contentInset = padding;
+ RCTTextView *textView = viewRegistry[reactTag];
+ RCTShadowTextView *shadowTextView = (RCTShadowTextView *)shadowView;
+ textView.contentInset = padding;
+ textView.maximumNumberOfLines = shadowTextView.maximumNumberOfLines;
+ textView.truncationMode = shadowTextView.truncationMode;
};
}