Updates from Fri Feb 27

- [react-packager] transformModulePath option is not actually required | Amjad Masad
- Implement TextInput.clearButtonMode added by D1875684 on OSS fork + example | Tadeu Zagallo
- [ReactNative] Use local CocoaPod config for ReactNative modules | Spencer Ahrens
- [ReactNative] Pull out some OSS modules into separate libs | Spencer Ahrens
- Enqueue events at 60fps + profiling helpers | Tadeu Zagallo
This commit is contained in:
Christopher Chedeau
2015-03-02 10:52:16 -08:00
parent 258c6b1b54
commit 0b09ed0667
218 changed files with 14048 additions and 278 deletions

View File

@@ -0,0 +1,120 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule ExpandingText
*/
'use strict';
var React = require('React');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
var View = require('View');
var truncate = require('truncate');
var styles = StyleSheet.create({
boldText: {
fontWeight: 'bold',
},
});
/**
* <ExpandingText> - A react component for displaying text which supports truncating
* based on a set truncLength. In the following example, the text will truncate
* to show only the first 17 characters plus '...' with a See More button to
* expand the text to its full length
*
* renderText: function() {
* return <ExpandingText truncLength={20} text={EXAMPLE_TEXT} />;
* },
*
* More example code in `ExpandingTextExample.js`
*/
var ExpandingText = React.createClass({
PropTypes: {
/**
* Text to be displayed. Text will be truncated if the character length
* is greater than the truncLength property.
*/
text: React.PropTypes.string.isRequired,
/**
* The styles that will be applied to the text (both truncated and expanded).
*/
textStyle: Text.stylePropType,
/**
* The styles that will be applied to the See More button
*/
seeMoreStyle: Text.stylePropType,
/**
* The maximum character length for the text that will
* be displayed by default. Note that ... will be
* appended to the truncated text which is counted towards
* the total truncLength of the default displayed string
*/
truncLength: React.PropTypes.number
},
getDefaultProps: function() {
return {
truncLength: 130,
seeMoreText: 'See More',
seeMoreStyle: styles.boldText,
};
},
getInitialState: function() {
return {
truncated: true,
};
},
onTapSeeMore: function() {
this.setState({
truncated: !this.state.truncated,
});
},
isTruncated: function() {
return (
this.props.text.length > this.props.truncLength &&
this.state.truncated
);
},
getText: function() {
var text = this.props.text;
if (!this.isTruncated()) {
return text;
}
return truncate(text, this.props.truncLength) + ' ';
},
renderSeeMore: function() {
if (!this.isTruncated()) {
return null;
}
return (
<Text style={this.props.seeMoreStyle}>
{this.props.seeMoreText}
</Text>
);
},
render: function() {
return (
<TouchableWithoutFeedback onPress={this.onTapSeeMore}>
<View>
<Text style={this.props.textStyle}>
{this.getText()}
{this.renderSeeMore()}
</Text>
</View>
</TouchableWithoutFeedback>
);
}
});
module.exports = ExpandingText;

View File

@@ -0,0 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTViewManager.h"
@interface RCTRawTextManager : RCTViewManager
@end

View File

@@ -0,0 +1,20 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTRawTextManager.h"
#import "RCTShadowRawText.h"
@implementation RCTRawTextManager
- (UIView *)view
{
return [[UIView alloc] init];
}
- (RCTShadowView *)shadowView
{
return [[RCTShadowRawText alloc] init];
}
@end

View File

@@ -0,0 +1,9 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTShadowView.h"
@interface RCTShadowRawText : RCTShadowView
@property (nonatomic, copy) NSString *text;
@end

View File

@@ -0,0 +1,16 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTShadowRawText.h"
@implementation RCTShadowRawText
- (void)setText:(NSString *)text
{
if (_text != text) {
_text = [text copy];
[self dirtyLayout];
[self dirtyText];
}
}
@end

View File

@@ -0,0 +1,26 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTShadowView.h"
extern NSString *const RCTIsHighlightedAttributeName;
extern NSString *const RCTReactTagAttributeName;
@interface RCTShadowText : RCTShadowView
@property (nonatomic, assign) NSWritingDirection writingDirection;
@property (nonatomic, strong) UIColor *textBackgroundColor;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, strong) UIFont *font;
@property (nonatomic, copy) NSString *fontFamily;
@property (nonatomic, assign) CGFloat fontSize;
@property (nonatomic, copy) NSString *fontWeight;
@property (nonatomic, assign) BOOL isHighlighted;
@property (nonatomic, assign) CGFloat lineHeight;
@property (nonatomic, assign) NSInteger maxNumberOfLines;
@property (nonatomic, assign) CGSize shadowOffset;
@property (nonatomic, assign) NSTextAlignment textAlign;
@property (nonatomic, assign) NSLineBreakMode truncationMode;
- (NSAttributedString *)attributedString;
@end

View File

@@ -0,0 +1,199 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTShadowText.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTShadowRawText.h"
#import "RCTUtils.h"
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
static css_dim_t RCTMeasure(void *context, float width)
{
RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
CGSize computedSize = [[shadowText attributedString] boundingRectWithSize:(CGSize){isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX} options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
css_dim_t result;
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(computedSize.height);
return result;
}
@implementation RCTShadowText
{
NSAttributedString *_cachedAttributedString;
UIFont *_font;
}
- (instancetype)init
{
if ((self = [super init])) {
_fontSize = NAN;
_isHighlighted = NO;
}
return self;
}
- (NSAttributedString *)attributedString
{
return [self _attributedStringWithFontFamily:nil
fontSize:0
fontWeight:nil];
}
- (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily
fontSize:(CGFloat)fontSize
fontWeight:(NSString *)fontWeight
{
if (![self isTextDirty] && _cachedAttributedString) {
return _cachedAttributedString;
}
if (_fontSize && !isnan(_fontSize)) {
fontSize = _fontSize;
}
if (_fontWeight) {
fontWeight = _fontWeight;
}
if (_fontFamily) {
fontFamily = _fontFamily;
}
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
for (RCTShadowView *child in [self reactSubviews]) {
if ([child isKindOfClass:[RCTShadowText class]]) {
RCTShadowText *shadowText = (RCTShadowText *)child;
[attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight]];
} else if ([child isKindOfClass:[RCTShadowRawText class]]) {
RCTShadowRawText *shadowRawText = (RCTShadowRawText *)child;
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[shadowRawText text] ?: @""]];
} else {
RCTLogError(@"<Text> can't have any children except <Text> or raw strings");
}
[child setTextComputed];
}
if (_color) {
[self _addAttribute:NSForegroundColorAttributeName withValue:self.color toAttributedString:attributedString];
}
if (_isHighlighted) {
[self _addAttribute:RCTIsHighlightedAttributeName withValue:@YES toAttributedString:attributedString];
}
if (_textBackgroundColor) {
[self _addAttribute:NSBackgroundColorAttributeName withValue:self.textBackgroundColor toAttributedString:attributedString];
}
_font = [RCTConvert UIFont:nil withFamily:fontFamily size:@(fontSize) weight:fontWeight];
[self _addAttribute:NSFontAttributeName withValue:_font toAttributedString:attributedString];
[self _addAttribute:RCTReactTagAttributeName withValue:self.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];
[self dirtyLayout];
return _cachedAttributedString;
}
- (UIFont *)font
{
return _font ?: [RCTConvert UIFont:nil withFamily:_fontFamily size:@(_fontSize) weight:_fontWeight];
}
- (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;
}
}];
// TODO: umm, these can'e be null, so we're mapping left to natural - is that right?
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}];
}
}
- (void)fillCSSNode:(css_node_t *)node
{
[super fillCSSNode:node];
node->measure = RCTMeasure;
node->children_count = 0;
}
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
[self cssNode]->children_count = 0;
}
- (void)removeReactSubview:(RCTShadowView *)subview
{
[super removeReactSubview:subview];
[self cssNode]->children_count = 0;
}
#define RCT_TEXT_PROPERTY(setProp, ivar, type) \
- (void)set##setProp:(type)value; \
{ \
ivar = value; \
[self dirtyText]; \
}
RCT_TEXT_PROPERTY(TextBackgroundColor, _textBackgroundColor, UIColor *);
RCT_TEXT_PROPERTY(Color, _color, UIColor *);
RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *);
RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat);
RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *);
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat);
RCT_TEXT_PROPERTY(MaxNumberOfLines, _maxNumberOfLines, NSInteger);
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize);
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment);
RCT_TEXT_PROPERTY(TruncationMode, _truncationMode, NSLineBreakMode);
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL);
RCT_TEXT_PROPERTY(Font, _font, UIFont *);
@end

13
Libraries/Text/RCTText.h Normal file
View File

@@ -0,0 +1,13 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
@interface RCTText : UIView
@property (nonatomic, copy) NSAttributedString *attributedText;
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
@property (nonatomic, assign) NSUInteger numberOfLines;
- (NSNumber *)reactTagAtPoint:(CGPoint)point;
@end

100
Libraries/Text/RCTText.m Normal file
View File

@@ -0,0 +1,100 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTText.h"
#import "RCTShadowText.h"
#import "RCTUtils.h"
#import "UIView+ReactKit.h"
@implementation RCTText
{
NSLayoutManager *_layoutManager;
NSTextStorage *_textStorage;
NSTextContainer *_textContainer;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
_textContainer = [[NSTextContainer alloc] init];
_textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
_textContainer.lineFragmentPadding = 0.0;
_layoutManager = [[NSLayoutManager alloc] init];
[_layoutManager addTextContainer:_textContainer];
_textStorage = [[NSTextStorage alloc] init];
[_textStorage addLayoutManager:_layoutManager];
self.contentMode = UIViewContentModeRedraw;
}
return self;
}
- (NSAttributedString *)attributedText
{
return [_textStorage copy];
}
- (void)setAttributedText:(NSAttributedString *)attributedText
{
[_textStorage setAttributedString:attributedText];
[self setNeedsDisplay];
}
- (NSUInteger)numberOfLines
{
return _textContainer.maximumNumberOfLines;
}
- (void)setNumberOfLines:(NSUInteger)numberOfLines
{
_textContainer.maximumNumberOfLines = numberOfLines;
[self setNeedsDisplay];
}
- (NSLineBreakMode)lineBreakMode
{
return _textContainer.lineBreakMode;
}
- (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode
{
_textContainer.lineBreakMode = lineBreakMode;
[self setNeedsDisplay];
}
- (void)layoutSubviews
{
[super layoutSubviews];
// The header comment for `size` says that a height of 0.0 should be enough,
// but it isn't.
_textContainer.size = CGSizeMake(self.bounds.size.width, CGFLOAT_MAX);
}
- (void)drawRect:(CGRect)rect
{
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:CGPointZero];
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:CGPointZero];
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
CGFloat fraction;
NSUInteger characterIndex = [_layoutManager characterIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceBetweenInsertionPoints:&fraction];
NSNumber *reactTag = nil;
// If the point is not before (fraction == 0.0) the first character and not
// after (fraction == 1.0) the last character, then the attribute is valid.
if (_textStorage.length > 0 && (fraction > 0 || characterIndex > 0) && (fraction < 1 || characterIndex < _textStorage.length - 1)) {
reactTag = [_textStorage attribute:RCTReactTagAttributeName atIndex:characterIndex effectiveRange:NULL];
}
return reactTag ?: self.reactTag;
}
@end

View File

@@ -0,0 +1,28 @@
Pod::Spec.new do |spec|
spec.name = 'RCTText'
spec.version = '0.0.1'
spec.summary = 'Provides basic Text capabilities in ReactNative apps.'
spec.description = <<-DESC
Text can be rendered in ReactNative apps with the <Text> component using this module.
DESC
spec.homepage = 'https://facebook.github.io/react-native/'
spec.license = { :type => 'BSD' }
spec.author = 'Facebook'
spec.platform = :ios, '7.0'
spec.requires_arc = true
spec.source_files = '**/*.{h,m,c}'
spec.dependency "ReactKit", "~> 0.0.1"
# ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# Link your library with frameworks, or libraries. Libraries do not include
# the lib prefix of their name.
#
# s.framework = "SomeFramework"
# s.frameworks = "SomeFramework", "AnotherFramework"
# s.library = "iconv"
#spec.libraries = "RCTText", "ReactKit"
end

View File

@@ -0,0 +1,379 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
58B511A71A9E6C1300147676 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58B5119B1A9E6C1200147676 /* libRCTText.a */; };
58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */; };
58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511C91A9E6C5C00147676 /* RCTShadowRawText.m */; };
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 */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
58B511A81A9E6C1300147676 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 58B511931A9E6C1200147676 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 58B5119A1A9E6C1200147676;
remoteInfo = RCTText;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
58B511991A9E6C1200147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
58B5119B1A9E6C1200147676 /* libRCTText.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTText.a; sourceTree = BUILT_PRODUCTS_DIR; };
58B511A61A9E6C1300147676 /* RCTTextTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RCTTextTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
58B511C61A9E6C5C00147676 /* RCTRawTextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextManager.h; sourceTree = "<group>"; };
58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextManager.m; sourceTree = "<group>"; };
58B511C81A9E6C5C00147676 /* RCTShadowRawText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowRawText.h; sourceTree = "<group>"; };
58B511C91A9E6C5C00147676 /* RCTShadowRawText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowRawText.m; sourceTree = "<group>"; };
58B511CA1A9E6C5C00147676 /* RCTShadowText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowText.h; sourceTree = "<group>"; };
58B511CB1A9E6C5C00147676 /* RCTShadowText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowText.m; sourceTree = "<group>"; };
58B511CC1A9E6C5C00147676 /* RCTTextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextManager.h; sourceTree = "<group>"; };
58B511CD1A9E6C5C00147676 /* RCTTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextManager.m; sourceTree = "<group>"; };
58B512141A9E6EFF00147676 /* RCTText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTText.m; sourceTree = "<group>"; };
58B512151A9E6EFF00147676 /* RCTText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTText.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511981A9E6C1200147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511A31A9E6C1300147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
58B511A71A9E6C1300147676 /* libRCTText.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
58B511921A9E6C1200147676 = {
isa = PBXGroup;
children = (
58B511C61A9E6C5C00147676 /* RCTRawTextManager.h */,
58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */,
58B511C81A9E6C5C00147676 /* RCTShadowRawText.h */,
58B511C91A9E6C5C00147676 /* RCTShadowRawText.m */,
58B511CA1A9E6C5C00147676 /* RCTShadowText.h */,
58B511CB1A9E6C5C00147676 /* RCTShadowText.m */,
58B512151A9E6EFF00147676 /* RCTText.h */,
58B512141A9E6EFF00147676 /* RCTText.m */,
58B511CC1A9E6C5C00147676 /* RCTTextManager.h */,
58B511CD1A9E6C5C00147676 /* RCTTextManager.m */,
58B5119C1A9E6C1200147676 /* Products */,
);
sourceTree = "<group>";
};
58B5119C1A9E6C1200147676 /* Products */ = {
isa = PBXGroup;
children = (
58B5119B1A9E6C1200147676 /* libRCTText.a */,
58B511A61A9E6C1300147676 /* RCTTextTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B5119A1A9E6C1200147676 /* RCTText */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511AF1A9E6C1300147676 /* Build configuration list for PBXNativeTarget "RCTText" */;
buildPhases = (
58B511971A9E6C1200147676 /* Sources */,
58B511981A9E6C1200147676 /* Frameworks */,
58B511991A9E6C1200147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RCTText;
productName = RCTText;
productReference = 58B5119B1A9E6C1200147676 /* libRCTText.a */;
productType = "com.apple.product-type.library.static";
};
58B511A51A9E6C1300147676 /* RCTTextTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511B21A9E6C1300147676 /* Build configuration list for PBXNativeTarget "RCTTextTests" */;
buildPhases = (
58B511A21A9E6C1300147676 /* Sources */,
58B511A31A9E6C1300147676 /* Frameworks */,
58B511A41A9E6C1300147676 /* Resources */,
);
buildRules = (
);
dependencies = (
58B511A91A9E6C1300147676 /* PBXTargetDependency */,
);
name = RCTTextTests;
productName = RCTTextTests;
productReference = 58B511A61A9E6C1300147676 /* RCTTextTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511931A9E6C1200147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B5119A1A9E6C1200147676 = {
CreatedOnToolsVersion = 6.1.1;
};
58B511A51A9E6C1300147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511961A9E6C1200147676 /* Build configuration list for PBXProject "RCTText" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 58B511921A9E6C1200147676;
productRefGroup = 58B5119C1A9E6C1200147676 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
58B5119A1A9E6C1200147676 /* RCTText */,
58B511A51A9E6C1300147676 /* RCTTextTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
58B511A41A9E6C1300147676 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
58B511971A9E6C1200147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */,
58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */,
58B512161A9E6EFF00147676 /* RCTText.m in Sources */,
58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */,
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511A21A9E6C1300147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
58B511A91A9E6C1300147676 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 58B5119A1A9E6C1200147676 /* RCTText */;
targetProxy = 58B511A81A9E6C1300147676 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
58B511AD1A9E6C1300147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511AE1A9E6C1300147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511B01A9E6C1300147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../ReactKit/**",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511B11A9E6C1300147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../ReactKit/**",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
58B511B31A9E6C1300147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = RCTTextTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
58B511B41A9E6C1300147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
INFOPLIST_FILE = RCTTextTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511961A9E6C1200147676 /* Build configuration list for PBXProject "RCTText" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511AD1A9E6C1300147676 /* Debug */,
58B511AE1A9E6C1300147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511AF1A9E6C1300147676 /* Build configuration list for PBXNativeTarget "RCTText" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511B01A9E6C1300147676 /* Debug */,
58B511B11A9E6C1300147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511B21A9E6C1300147676 /* Build configuration list for PBXNativeTarget "RCTTextTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511B31A9E6C1300147676 /* Debug */,
58B511B41A9E6C1300147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511931A9E6C1200147676 /* Project object */;
}

View File

@@ -0,0 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTViewManager.h"
@interface RCTTextManager : RCTViewManager
@end

View File

@@ -0,0 +1,135 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTTextManager.h"
#import "RCTAssert.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTShadowRawText.h"
#import "RCTShadowText.h"
#import "RCTSparseArray.h"
#import "RCTText.h"
#import "UIView+ReactKit.h"
@implementation RCTTextManager
- (UIView *)view
{
return [[RCTText alloc] init];
}
- (RCTShadowView *)shadowView
{
return [[RCTShadowText alloc] init];
}
RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor)
- (void)set_textAlign:(id)json
forShadowView:(RCTShadowText *)shadowView
withDefaultView:(RCTShadowText *)defaultView
{
shadowView.textAlign = json ? [RCTConvert NSTextAlignment:json] : defaultView.textAlign;
}
- (void)set_numberOfLines:(id)json
forView:(RCTText *)view
withDefaultView:(RCTText *)defaultView
{
NSLineBreakMode truncationMode = NSLineBreakByClipping;
view.numberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.numberOfLines;
if (view.numberOfLines > 0) {
truncationMode = NSLineBreakByTruncatingTail;
}
view.lineBreakMode = truncationMode;
}
- (void)set_numberOfLines:(id)json
forShadowView:(RCTShadowText *)shadowView
withDefaultView:(RCTShadowText *)defaultView
{
NSLineBreakMode truncationMode = NSLineBreakByClipping;
shadowView.maxNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maxNumberOfLines;
if (shadowView.maxNumberOfLines > 0) {
truncationMode = NSLineBreakByTruncatingTail;
}
shadowView.truncationMode = truncationMode;
}
- (void)set_backgroundColor:(id)json
forShadowView:(RCTShadowText *)shadowView
withDefaultView:(RCTShadowText *)defaultView
{
shadowView.textBackgroundColor = json ? [RCTConvert UIColor:json] : defaultView.textBackgroundColor;
}
- (void)set_containerBackgroundColor:(id)json
forShadowView:(RCTShadowText *)shadowView
withDefaultView:(RCTShadowText *)defaultView
{
shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
// TODO: the purpose of this block is effectively just to copy properties from the shadow views
// to their equivalent UIViews. In this case, the property being copied is the attributed text,
// but the same principle could be used to copy any property. The implementation is really ugly tho
// because the RCTViewManager doesn't retain a reference to the views that it manages, so it basically
// has to search the entire view hierarchy for relevant views. Not awesome. This seems like something
// where we could introduce a generic solution - perhaps a method on RCTShadowView that is called after
// layout to copy its properties across?
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
NSMutableArray *uiBlocks = [NSMutableArray new];
// TODO: are modules global, or specific to a given rootView?
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;
}
// TODO: this is a slightly weird way to do this - a recursive approach would be cleaner
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 <Text> tag. Not rendering string: '%@'", [(RCTShadowRawText *)shadowView text]);
} else {
for (RCTShadowView *child in [shadowView reactSubviews]) {
if ([child isTextDirty]) {
[queue addObject:child];
}
}
}
[shadowView setTextComputed];
}
[uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
[reactTaggedAttributedStrings enumerateObjectsUsingBlock:^(NSAttributedString *attributedString, NSNumber *reactTag, BOOL *stop) {
RCTText *text = viewRegistry[reactTag];
text.attributedText = attributedString;
}];
}];
}
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
for (RCTViewManagerUIBlock shadowBlock in uiBlocks) {
shadowBlock(uiManager, viewRegistry);
}
};
}
@end

195
Libraries/Text/Text.js Normal file
View File

@@ -0,0 +1,195 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule Text
* @typechecks static-only
*/
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StyleSheetPropType = require('StyleSheetPropType');
var TextStylePropTypes = require('TextStylePropTypes');
var Touchable = require('Touchable');
var createReactIOSNativeComponentClass =
require('createReactIOSNativeComponentClass');
var merge = require('merge');
var stylePropType = StyleSheetPropType(TextStylePropTypes);
var viewConfig = {
validAttributes: merge(ReactIOSViewAttributes.UIView, {
isHighlighted: true,
numberOfLines: true,
}),
uiViewClassName: 'RCTText',
};
/**
* <Text> - A react component for displaying text which supports nesting,
* styling, and touch handling. In the following example, the nested title and
* body text will inherit the `fontFamily` from `styles.baseText`, but the title
* provides its own additional styles. The title and body will stack on top of
* each other on account of the literal newlines:
*
* renderText: function() {
* return (
* <Text style={styles.baseText}>
* <Text style={styles.titleText} onPress={this._onPressTitle}>
* {this.state.titleText + '\n\n'}
* </Text>
* <Text numberOfLines={5}>
* {this.state.bodyText}
* </Text>
* </Text>
* );
* },
* ...
* var styles = StyleSheet.create({
* baseText: {
* fontFamily: 'Cochin',
* },
* titleText: {
* fontSize: 20,
* fontWeight: 'bold',
* },
* };
*
* More example code in `TextExample.ios.js`
*/
var Text = React.createClass({
mixins: [Touchable.Mixin, NativeMethodsMixin],
statics: {
stylePropType: stylePropType,
},
propTypes: {
/**
* Used to truncate the text with an elipsis after computing the text
* layout, including line wrapping, such that the total number of lines does
* not exceed this number.
*/
numberOfLines: React.PropTypes.number,
/**
* This function is called on press. Text intrinsically supports press
* handling with a default highlight state (which can be disabled with
* `suppressHighlighting`).
*/
onPress: React.PropTypes.func,
/**
* When true, no visual change is made when text is pressed down. By
* default, a gray oval highlights the text on press down.
*/
suppressHighlighting: React.PropTypes.bool,
style: stylePropType,
},
viewConfig: viewConfig,
getInitialState: function() {
return merge(this.touchableGetInitialState(), {
isHighlighted: false,
});
},
onStartShouldSetResponder: function() {
var shouldSetFromProps = this.props.onStartShouldSetResponder &&
this.props.onStartShouldSetResponder();
return shouldSetFromProps || !!this.props.onPress;
},
/*
* Returns true to allow responder termination
*/
handleResponderTerminationRequest: function() {
// Allow touchable or props.onResponderTerminationRequest to deny
// the request
var allowTermination = this.touchableHandleResponderTerminationRequest();
if (allowTermination && this.props.onResponderTerminationRequest) {
allowTermination = this.props.onResponderTerminationRequest();
}
return allowTermination;
},
handleResponderGrant: function(e, dispatchID) {
this.touchableHandleResponderGrant(e, dispatchID);
this.props.onResponderGrant &&
this.props.onResponderGrant.apply(this, arguments);
},
handleResponderMove: function(e) {
this.touchableHandleResponderMove(e);
this.props.onResponderMove &&
this.props.onResponderMove.apply(this, arguments);
},
handleResponderRelease: function(e) {
this.touchableHandleResponderRelease(e);
this.props.onResponderRelease &&
this.props.onResponderRelease.apply(this, arguments);
},
handleResponderTerminate: function(e) {
this.touchableHandleResponderTerminate(e);
this.props.onResponderTerminate &&
this.props.onResponderTerminate.apply(this, arguments);
},
touchableHandleActivePressIn: function() {
if (this.props.suppressHighlighting || !this.props.onPress) {
return;
}
this.setState({
isHighlighted: true,
});
},
touchableHandleActivePressOut: function() {
if (this.props.suppressHighlighting || !this.props.onPress) {
return;
}
this.setState({
isHighlighted: false,
});
},
touchableHandlePress: function() {
this.props.onPress && this.props.onPress();
},
touchableGetPressRectOffset: function() {
return PRESS_RECT_OFFSET;
},
render: function() {
var props = {};
for (var key in this.props) {
props[key] = this.props[key];
}
props.ref = this.getNodeHandle();
// Text is accessible by default
if (props.accessible !== false) {
props.accessible = true;
}
props.isHighlighted = this.state.isHighlighted;
props.onStartShouldSetResponder = this.onStartShouldSetResponder;
props.onResponderTerminationRequest =
this.handleResponderTerminationRequest;
props.onResponderGrant = this.handleResponderGrant;
props.onResponderMove = this.handleResponderMove;
props.onResponderRelease = this.handleResponderRelease;
props.onResponderTerminate = this.handleResponderTerminate;
return <RKText {...props} />;
},
});
var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
var RKText = createReactIOSNativeComponentClass(viewConfig);
module.exports = Text;

View File

@@ -0,0 +1,46 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule TextStylePropTypes
*/
'use strict';
var ReactPropTypes = require('ReactPropTypes');
var ViewStylePropTypes = require('ViewStylePropTypes');
var merge = require('merge');
var TextStylePropTypes = merge(
ViewStylePropTypes, {
fontFamily: ReactPropTypes.string,
fontSize: ReactPropTypes.number,
fontWeight: ReactPropTypes.oneOf(['normal' /*default*/, 'bold']),
fontStyle: ReactPropTypes.oneOf(['normal', 'italic']),
lineHeight: ReactPropTypes.number,
color: ReactPropTypes.string,
containerBackgroundColor: ReactPropTypes.string,
textAlign: ReactPropTypes.oneOf(
['auto' /*default*/, 'left', 'right', 'center']
),
writingDirection: ReactPropTypes.oneOf(
['auto' /*default*/, 'ltr', 'rtl']
),
}
);
// Text doesn't support padding correctly (#4841912)
var unsupportedProps = Object.keys({
padding: null,
paddingTop: null,
paddingLeft: null,
paddingRight: null,
paddingBottom: null,
paddingVertical: null,
paddingHorizontal: null,
});
for (var key in unsupportedProps) {
delete TextStylePropTypes[key];
}
module.exports = TextStylePropTypes;