Re-render views when direction changes

Summary:
This is required for D5874536, wherein I'll be introducing direction-aware props for borders.

When a view's border changes due to a direction update, only the frames of its children update. Therefore, only the children `UIView`s get a chance to be re-rendered. This is incorrect because the view that's had its borders changed also needs to re-render. So, I keep a track of the layout direction in a property on all shadow views. Then, when I update that prop within `applyLayoutNode`, I push shadow views into the `viewsWithNewFrames` set.

Reviewed By: mmmulani

Differential Revision: D5944488

fbshipit-source-id: 3f23e9973f3555612920703cdb6cec38e6360d2d
This commit is contained in:
Ramanpreet Nara 2017-10-02 11:03:00 -07:00 committed by Facebook Github Bot
parent 53a339a459
commit 9bbc70c442
6 changed files with 631 additions and 443 deletions

View File

@ -38,7 +38,7 @@ static CGFloat const kAutoSizeGranularity = 0.001f;
CGFloat _cachedTextStorageWidthMode; CGFloat _cachedTextStorageWidthMode;
NSAttributedString *_cachedAttributedString; NSAttributedString *_cachedAttributedString;
CGFloat _effectiveLetterSpacing; CGFloat _effectiveLetterSpacing;
UIUserInterfaceLayoutDirection _cachedEffectiveLayoutDirection; UIUserInterfaceLayoutDirection _cachedLayoutDirection;
} }
static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode) static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
@ -72,7 +72,7 @@ static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, f
_fontSizeMultiplier = 1.0; _fontSizeMultiplier = 1.0;
_textAlign = NSTextAlignmentNatural; _textAlign = NSTextAlignmentNatural;
_writingDirection = NSWritingDirectionNatural; _writingDirection = NSWritingDirectionNatural;
_cachedEffectiveLayoutDirection = UIUserInterfaceLayoutDirectionLeftToRight; _cachedLayoutDirection = UIUserInterfaceLayoutDirectionLeftToRight;
YGNodeSetMeasureFunc(self.yogaNode, RCTMeasure); YGNodeSetMeasureFunc(self.yogaNode, RCTMeasure);
@ -198,7 +198,7 @@ static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, f
_cachedTextStorage && _cachedTextStorage &&
(width == _cachedTextStorageWidth || (isnan(width) && isnan(_cachedTextStorageWidth))) && (width == _cachedTextStorageWidth || (isnan(width) && isnan(_cachedTextStorageWidth))) &&
widthMode == _cachedTextStorageWidthMode && widthMode == _cachedTextStorageWidthMode &&
_cachedEffectiveLayoutDirection == self.effectiveLayoutDirection _cachedLayoutDirection == self.layoutDirection
) { ) {
return _cachedTextStorage; return _cachedTextStorage;
} }
@ -272,12 +272,12 @@ static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, f
if ( if (
![self isTextDirty] && ![self isTextDirty] &&
_cachedAttributedString && _cachedAttributedString &&
_cachedEffectiveLayoutDirection == self.effectiveLayoutDirection _cachedLayoutDirection == self.layoutDirection
) { ) {
return _cachedAttributedString; return _cachedAttributedString;
} }
_cachedEffectiveLayoutDirection = self.effectiveLayoutDirection; _cachedLayoutDirection = self.layoutDirection;
if (_fontSize && !isnan(_fontSize)) { if (_fontSize && !isnan(_fontSize)) {
fontSize = @(_fontSize); fontSize = @(_fontSize);
@ -419,7 +419,7 @@ static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, f
// Text alignment // Text alignment
NSTextAlignment textAlign = _textAlign; NSTextAlignment textAlign = _textAlign;
if (textAlign == NSTextAlignmentRight || textAlign == NSTextAlignmentLeft) { if (textAlign == NSTextAlignmentRight || textAlign == NSTextAlignmentLeft) {
if (_cachedEffectiveLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { if (_cachedLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
if (textAlign == NSTextAlignmentRight) { if (textAlign == NSTextAlignmentRight) {
textAlign = NSTextAlignmentLeft; textAlign = NSTextAlignmentLeft;
} else { } else {

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @flow * @flow
* @format
* @providesModule TextExample * @providesModule TextExample
*/ */
'use strict'; 'use strict';
@ -15,13 +16,56 @@ const Platform = require('Platform');
var React = require('react'); var React = require('react');
var createReactClass = require('create-react-class'); var createReactClass = require('create-react-class');
var ReactNative = require('react-native'); var ReactNative = require('react-native');
var { var {Image, Text, View, LayoutAnimation, Button} = ReactNative;
Image,
StyleSheet, type TextAlignExampleRTLState = {|
Text, isRTL: boolean,
View, |};
LayoutAnimation,
} = ReactNative; class TextAlignRTLExample extends React.Component<*, TextAlignExampleRTLState> {
constructor(...args: Array<*>) {
super(...args);
this.state = {
isRTL: false,
};
}
render() {
const {isRTL} = this.state;
const toggleRTL = () => this.setState({isRTL: !isRTL});
return (
<View style={{direction: isRTL ? 'rtl' : 'ltr'}}>
<Text>auto (default) - english LTR</Text>
<Text>
\u0623\u062D\u0628 \u0627\u0644\u0644\u063A\u0629
\u0627\u0644\u0639\u0631\u0628\u064A\u0629 auto (default) - arabic RTL
</Text>
<Text style={{textAlign: 'left'}}>
left left left left left left left left left left left left left left
left
</Text>
<Text style={{textAlign: 'center'}}>
center center center center center center center center center center
center
</Text>
<Text style={{textAlign: 'right'}}>
right right right right right right right right right right right
right right
</Text>
<Text style={{textAlign: 'justify'}}>
justify: this text component{"'"}s contents are laid out with
"textAlign: justify" and as you can see all of the lines except the
last one span the available width of the parent container.
</Text>
<Button
onPress={toggleRTL}
title={`Switch to ${isRTL ? 'LTR' : 'RTL'}`}
/>
</View>
);
}
}
class Entity extends React.Component<$FlowFixMeProps> { class Entity extends React.Component<$FlowFixMeProps> {
render() { render() {
@ -38,25 +82,31 @@ class AttributeToggler extends React.Component<{}, $FlowFixMeState> {
toggleWeight = () => { toggleWeight = () => {
this.setState({ this.setState({
fontWeight: this.state.fontWeight === 'bold' ? 'normal' : 'bold' fontWeight: this.state.fontWeight === 'bold' ? 'normal' : 'bold',
}); });
}; };
increaseSize = () => { increaseSize = () => {
this.setState({ this.setState({
fontSize: this.state.fontSize + 1 fontSize: this.state.fontSize + 1,
}); });
}; };
render() { render() {
var curStyle = {fontWeight: this.state.fontWeight, fontSize: this.state.fontSize}; var curStyle = {
fontWeight: this.state.fontWeight,
fontSize: this.state.fontSize,
};
return ( return (
<View> <View>
<Text style={curStyle}> <Text style={curStyle}>
Tap the controls below to change attributes. Tap the controls below to change attributes.
</Text> </Text>
<Text> <Text>
<Text>See how it will even work on <Text style={curStyle}>this nested text</Text></Text> <Text>
See how it will even work on{' '}
<Text style={curStyle}>this nested text</Text>
</Text>
</Text> </Text>
<Text <Text
style={{backgroundColor: '#ffaaaa', marginTop: 5}} style={{backgroundColor: '#ffaaaa', marginTop: 5}}
@ -76,7 +126,7 @@ class AttributeToggler extends React.Component<{}, $FlowFixMeState> {
var AdjustingFontSize = createReactClass({ var AdjustingFontSize = createReactClass({
displayName: 'AdjustingFontSize', displayName: 'AdjustingFontSize',
getInitialState: function() { getInitialState: function() {
return {dynamicText:'', shouldRender: true,}; return {dynamicText: '', shouldRender: true};
}, },
reset: function() { reset: function() {
LayoutAnimation.easeInEaseOut(); LayoutAnimation.easeInEaseOut();
@ -93,37 +143,58 @@ var AdjustingFontSize = createReactClass({
}, },
addText: function() { addText: function() {
this.setState({ this.setState({
dynamicText: this.state.dynamicText + (Math.floor((Math.random() * 10) % 2) ? ' foo' : ' bar'), dynamicText:
this.state.dynamicText +
(Math.floor((Math.random() * 10) % 2) ? ' foo' : ' bar'),
}); });
}, },
removeText: function() { removeText: function() {
this.setState({ this.setState({
dynamicText: this.state.dynamicText.slice(0, this.state.dynamicText.length - 4), dynamicText: this.state.dynamicText.slice(
0,
this.state.dynamicText.length - 4,
),
}); });
}, },
render: function() { render: function() {
if (!this.state.shouldRender) { if (!this.state.shouldRender) {
return (<View/>); return <View />;
} }
return ( return (
<View> <View>
<Text ellipsizeMode="tail" numberOfLines={1} style={{fontSize: 36, marginVertical: 6}}> <Text
ellipsizeMode="tail"
numberOfLines={1}
style={{fontSize: 36, marginVertical: 6}}>
Truncated text is baaaaad. Truncated text is baaaaad.
</Text> </Text>
<Text numberOfLines={1} adjustsFontSizeToFit={true} style={{fontSize: 40, marginVertical:6}}> <Text
numberOfLines={1}
adjustsFontSizeToFit={true}
style={{fontSize: 40, marginVertical: 6}}>
Shrinking to fit available space is much better! Shrinking to fit available space is much better!
</Text> </Text>
<Text adjustsFontSizeToFit={true} numberOfLines={1} style={{fontSize:30, marginVertical:6}}> <Text
adjustsFontSizeToFit={true}
numberOfLines={1}
style={{fontSize: 30, marginVertical: 6}}>
{'Add text to me to watch me shrink!' + ' ' + this.state.dynamicText} {'Add text to me to watch me shrink!' + ' ' + this.state.dynamicText}
</Text> </Text>
<Text adjustsFontSizeToFit={true} numberOfLines={4} style={{fontSize:20, marginVertical:6}}> <Text
{'Multiline text component shrinking is supported, watch as this reeeeaaaally loooooong teeeeeeext grooooows and then shriiiinks as you add text to me! ioahsdia soady auydoa aoisyd aosdy ' + ' ' + this.state.dynamicText} adjustsFontSizeToFit={true}
numberOfLines={4}
style={{fontSize: 20, marginVertical: 6}}>
{'Multiline text component shrinking is supported, watch as this reeeeaaaally loooooong teeeeeeext grooooows and then shriiiinks as you add text to me! ioahsdia soady auydoa aoisyd aosdy ' +
' ' +
this.state.dynamicText}
</Text> </Text>
<Text adjustsFontSizeToFit={true} numberOfLines={1} style={{marginVertical:6}}> <Text
adjustsFontSizeToFit={true}
numberOfLines={1}
style={{marginVertical: 6}}>
<Text style={{fontSize: 14}}> <Text style={{fontSize: 14}}>
{'Differently sized nested elements will shrink together. '} {'Differently sized nested elements will shrink together. '}
</Text> </Text>
@ -132,26 +203,26 @@ var AdjustingFontSize = createReactClass({
</Text> </Text>
</Text> </Text>
<View style={{flexDirection:'row', justifyContent:'space-around', marginTop: 5, marginVertical:6}}> <View
<Text style={{
style={{backgroundColor: '#ffaaaa'}} flexDirection: 'row',
onPress={this.reset}> justifyContent: 'space-around',
marginTop: 5,
marginVertical: 6,
}}>
<Text style={{backgroundColor: '#ffaaaa'}} onPress={this.reset}>
Reset Reset
</Text> </Text>
<Text <Text style={{backgroundColor: '#aaaaff'}} onPress={this.removeText}>
style={{backgroundColor: '#aaaaff'}}
onPress={this.removeText}>
Remove Text Remove Text
</Text> </Text>
<Text <Text style={{backgroundColor: '#aaffaa'}} onPress={this.addText}>
style={{backgroundColor: '#aaffaa'}}
onPress={this.addText}>
Add Text Add Text
</Text> </Text>
</View> </View>
</View> </View>
); );
} },
}); });
exports.title = '<Text>'; exports.title = '<Text>';
@ -163,12 +234,13 @@ exports.examples = [
render: function() { render: function() {
return ( return (
<Text> <Text>
The text should wrap if it goes on multiple lines. See, this is going to The text should wrap if it goes on multiple lines. See, this is going
the next line. to the next line.
</Text> </Text>
); );
}, },
}, { },
{
title: 'Padding', title: 'Padding',
render: function() { render: function() {
return ( return (
@ -177,61 +249,63 @@ exports.examples = [
</Text> </Text>
); );
}, },
}, { },
{
title: 'Font Family', title: 'Font Family',
render: function() { render: function() {
return ( return (
<View> <View>
<Text style={{fontFamily: (Platform.isTVOS ? 'Times' : 'Cochin')}}> <Text style={{fontFamily: Platform.isTVOS ? 'Times' : 'Cochin'}}>
Cochin Cochin
</Text> </Text>
<Text style={{fontFamily: (Platform.isTVOS ? 'Times' : 'Cochin'), fontWeight: 'bold'}}> <Text
style={{
fontFamily: Platform.isTVOS ? 'Times' : 'Cochin',
fontWeight: 'bold',
}}>
Cochin bold Cochin bold
</Text> </Text>
<Text style={{fontFamily: 'Helvetica'}}> <Text style={{fontFamily: 'Helvetica'}}>Helvetica</Text>
Helvetica
</Text>
<Text style={{fontFamily: 'Helvetica', fontWeight: 'bold'}}> <Text style={{fontFamily: 'Helvetica', fontWeight: 'bold'}}>
Helvetica bold Helvetica bold
</Text> </Text>
<Text style={{fontFamily: (Platform.isTVOS ? 'Courier' : 'Verdana')}}> <Text style={{fontFamily: Platform.isTVOS ? 'Courier' : 'Verdana'}}>
Verdana Verdana
</Text> </Text>
<Text style={{fontFamily: (Platform.isTVOS ? 'Courier' : 'Verdana'), fontWeight: 'bold'}}> <Text
style={{
fontFamily: Platform.isTVOS ? 'Courier' : 'Verdana',
fontWeight: 'bold',
}}>
Verdana bold Verdana bold
</Text> </Text>
</View> </View>
); );
}, },
}, { },
{
title: 'Font Size', title: 'Font Size',
render: function() { render: function() {
return ( return (
<View> <View>
<Text style={{fontSize: 23}}> <Text style={{fontSize: 23}}>Size 23</Text>
Size 23 <Text style={{fontSize: 8}}>Size 8</Text>
</Text>
<Text style={{fontSize: 8}}>
Size 8
</Text>
</View> </View>
); );
}, },
}, { },
{
title: 'Color', title: 'Color',
render: function() { render: function() {
return ( return (
<View> <View>
<Text style={{color: 'red'}}> <Text style={{color: 'red'}}>Red color</Text>
Red color <Text style={{color: 'blue'}}>Blue color</Text>
</Text>
<Text style={{color: 'blue'}}>
Blue color
</Text>
</View> </View>
); );
}, },
}, { },
{
title: 'Font Weight', title: 'Font Weight',
render: function() { render: function() {
return ( return (
@ -254,61 +328,97 @@ exports.examples = [
</View> </View>
); );
}, },
}, { },
{
title: 'Font Style', title: 'Font Style',
render: function() { render: function() {
return ( return (
<View> <View>
<Text style={{fontStyle: 'normal'}}> <Text style={{fontStyle: 'normal'}}>Normal text</Text>
Normal text <Text style={{fontStyle: 'italic'}}>Italic text</Text>
</Text>
<Text style={{fontStyle: 'italic'}}>
Italic text
</Text>
</View> </View>
); );
}, },
}, { },
{
title: 'Selectable', title: 'Selectable',
render: function() { render: function() {
return ( return (
<View> <View>
<Text selectable={true}> <Text selectable={true}>
This text is <Text style={{fontWeight: 'bold'}}>selectable</Text> if you click-and-hold. This text is <Text style={{fontWeight: 'bold'}}>selectable</Text> if
you click-and-hold.
</Text> </Text>
</View> </View>
); );
}, },
}, { },
{
title: 'Text Decoration', title: 'Text Decoration',
render: function() { render: function() {
return ( return (
<View> <View>
<Text style={{textDecorationLine: 'underline', textDecorationStyle: 'solid'}}> <Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'solid',
}}>
Solid underline Solid underline
</Text> </Text>
<Text style={{textDecorationLine: 'underline', textDecorationStyle: 'double', textDecorationColor: '#ff0000'}}> <Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'double',
textDecorationColor: '#ff0000',
}}>
Double underline with custom color Double underline with custom color
</Text> </Text>
<Text style={{textDecorationLine: 'underline', textDecorationStyle: 'dashed', textDecorationColor: '#9CDC40'}}> <Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'dashed',
textDecorationColor: '#9CDC40',
}}>
Dashed underline with custom color Dashed underline with custom color
</Text> </Text>
<Text style={{textDecorationLine: 'underline', textDecorationStyle: 'dotted', textDecorationColor: 'blue'}}> <Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'dotted',
textDecorationColor: 'blue',
}}>
Dotted underline with custom color Dotted underline with custom color
</Text> </Text>
<Text style={{textDecorationLine: 'none'}}> <Text style={{textDecorationLine: 'none'}}>None textDecoration</Text>
None textDecoration <Text
</Text> style={{
<Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'solid'}}> textDecorationLine: 'line-through',
textDecorationStyle: 'solid',
}}>
Solid line-through Solid line-through
</Text> </Text>
<Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'double', textDecorationColor: '#ff0000'}}> <Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'double',
textDecorationColor: '#ff0000',
}}>
Double line-through with custom color Double line-through with custom color
</Text> </Text>
<Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'dashed', textDecorationColor: '#9CDC40'}}> <Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'dashed',
textDecorationColor: '#9CDC40',
}}>
Dashed line-through with custom color Dashed line-through with custom color
</Text> </Text>
<Text style={{textDecorationLine: 'line-through', textDecorationStyle: 'dotted', textDecorationColor: 'blue'}}> <Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'dotted',
textDecorationColor: 'blue',
}}>
Dotted line-through with custom color Dotted line-through with custom color
</Text> </Text>
<Text style={{textDecorationLine: 'underline line-through'}}> <Text style={{textDecorationLine: 'underline line-through'}}>
@ -317,9 +427,11 @@ exports.examples = [
</View> </View>
); );
}, },
}, { },
{
title: 'Nested', title: 'Nested',
description: 'Nested text components will inherit the styles of their ' + description:
'Nested text components will inherit the styles of their ' +
'parents (only backgroundColor is inherited from non-Text parents). ' + 'parents (only backgroundColor is inherited from non-Text parents). ' +
'<Text> only supports other <Text> and raw text (strings) as children.', '<Text> only supports other <Text> and raw text (strings) as children.',
render: function() { render: function() {
@ -357,42 +469,51 @@ exports.examples = [
</View> </View>
); );
}, },
}, { },
{
title: 'Text Align', title: 'Text Align',
render: function() { render: function() {
return ( return (
<View> <View>
<Text>auto (default) - english LTR</Text>
<Text> <Text>
auto (default) - english LTR \u0623\u062D\u0628 \u0627\u0644\u0644\u063A\u0629
</Text> \u0627\u0644\u0639\u0631\u0628\u064A\u0629 auto (default) - arabic
<Text> RTL
أحب اللغة العربية auto (default) - arabic RTL
</Text> </Text>
<Text style={{textAlign: 'left'}}> <Text style={{textAlign: 'left'}}>
left left left left left left left left left left left left left left left left left left left left left left left left left left left left
left left
</Text> </Text>
<Text style={{textAlign: 'center'}}> <Text style={{textAlign: 'center'}}>
center center center center center center center center center center center center center center center center center center center center
center center
</Text> </Text>
<Text style={{textAlign: 'right'}}> <Text style={{textAlign: 'right'}}>
right right right right right right right right right right right right right right right right right right right right right right right right
right right
</Text> </Text>
<Text style={{textAlign: 'justify'}}> <Text style={{textAlign: 'justify'}}>
justify: this text component{"'"}s contents are laid out with "textAlign: justify" justify: this text component{"'"}s contents are laid out with
and as you can see all of the lines except the last one span the "textAlign: justify" and as you can see all of the lines except the
available width of the parent container. last one span the available width of the parent container.
</Text> </Text>
</View> </View>
); );
}, },
}, { },
{
title: 'Text Align with RTL',
render: function() {
return <TextAlignRTLExample />;
},
},
{
title: 'Letter Spacing', title: 'Letter Spacing',
render: function() { render: function() {
return ( return (
<View> <View>
<Text style={{letterSpacing: 0}}> <Text style={{letterSpacing: 0}}>letterSpacing = 0</Text>
letterSpacing = 0
</Text>
<Text style={{letterSpacing: 2, marginTop: 5}}> <Text style={{letterSpacing: 2, marginTop: 5}}>
letterSpacing = 2 letterSpacing = 2
</Text> </Text>
@ -405,16 +526,18 @@ exports.examples = [
</View> </View>
); );
}, },
}, { },
{
title: 'Spaces', title: 'Spaces',
render: function() { render: function() {
return ( return (
<Text> <Text>
A {'generated'} {' '} {'string'} and some &nbsp;&nbsp;&nbsp; spaces A {'generated'} {'string'} and some &nbsp;&nbsp;&nbsp; spaces
</Text> </Text>
); );
}, },
}, { },
{
title: 'Line Height', title: 'Line Height',
render: function() { render: function() {
return ( return (
@ -426,20 +549,21 @@ exports.examples = [
</Text> </Text>
); );
}, },
}, {
title: 'Empty Text',
description: 'It\'s ok to have Text with zero or null children.',
render: function() {
return (
<Text />
);
}, },
}, { {
title: 'Empty Text',
description: "It's ok to have Text with zero or null children.",
render: function() {
return <Text />;
},
},
{
title: 'Toggling Attributes', title: 'Toggling Attributes',
render: function(): React.Element<any> { render: function(): React.Element<any> {
return <AttributeToggler />; return <AttributeToggler />;
}, },
}, { },
{
title: 'backgroundColor attribute', title: 'backgroundColor attribute',
description: 'backgroundColor is inherited from all types of views.', description: 'backgroundColor is inherited from all types of views.',
render: function() { render: function() {
@ -447,13 +571,17 @@ exports.examples = [
<Text style={{backgroundColor: 'yellow'}}> <Text style={{backgroundColor: 'yellow'}}>
Yellow container background, Yellow container background,
<Text style={{backgroundColor: '#ffaaaa'}}> <Text style={{backgroundColor: '#ffaaaa'}}>
{' '}red background, {' '}
red background,
<Text style={{backgroundColor: '#aaaaff'}}> <Text style={{backgroundColor: '#aaaaff'}}>
{' '}blue background, {' '}
blue background,
<Text> <Text>
{' '}inherited blue background, {' '}
inherited blue background,
<Text style={{backgroundColor: '#aaffaa'}}> <Text style={{backgroundColor: '#aaffaa'}}>
{' '}nested green background. {' '}
nested green background.
</Text> </Text>
</Text> </Text>
</Text> </Text>
@ -461,44 +589,70 @@ exports.examples = [
</Text> </Text>
); );
}, },
}, { },
{
title: 'numberOfLines attribute', title: 'numberOfLines attribute',
render: function() { render: function() {
return ( return (
<View> <View>
<Text numberOfLines={1}> <Text numberOfLines={1}>
Maximum of one line, no matter how much I write here. If I keep writing, it{"'"}ll just truncate after one line. Maximum of one line, no matter how much I write here. If I keep
writing, it{"'"}ll just truncate after one line.
</Text> </Text>
<Text numberOfLines={2} style={{marginTop: 20}}> <Text numberOfLines={2} style={{marginTop: 20}}>
Maximum of two lines, no matter how much I write here. If I keep writing, it{"'"}ll just truncate after two lines. Maximum of two lines, no matter how much I write here. If I keep
writing, it{"'"}ll just truncate after two lines.
</Text> </Text>
<Text style={{marginTop: 20}}> <Text style={{marginTop: 20}}>
No maximum lines specified, no matter how much I write here. If I keep writing, it{"'"}ll just keep going and going. No maximum lines specified, no matter how much I write here. If I
keep writing, it{"'"}ll just keep going and going.
</Text> </Text>
</View> </View>
); );
}, },
}, { },
{
title: 'Text highlighting (tap the link to see highlight)', title: 'Text highlighting (tap the link to see highlight)',
render: function() { render: function() {
return ( return (
<View> <View>
<Text>Lorem ipsum dolor sit amet, <Text suppressHighlighting={false} style={{backgroundColor: 'white', textDecorationLine: 'underline', color: 'blue'}} onPress={() => null}>consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud</Text> exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</Text> <Text>
Lorem ipsum dolor sit amet,{' '}
<Text
suppressHighlighting={false}
style={{
backgroundColor: 'white',
textDecorationLine: 'underline',
color: 'blue',
}}
onPress={() => null}>
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud
</Text>{' '}
exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.
</Text>
</View> </View>
); );
}, },
}, { },
{
title: 'allowFontScaling attribute', title: 'allowFontScaling attribute',
render: function() { render: function() {
return ( return (
<View> <View>
<Text> <Text>
By default, text will respect Text Size accessibility setting on iOS. By default, text will respect Text Size accessibility setting on
It means that all font sizes will be increased or descreased depending on the value of Text Size setting in iOS. It means that all font sizes will be increased or descreased
{" "}<Text style={{fontWeight: 'bold'}}>Settings.app - Display & Brightness - Text Size</Text> depending on the value of Text Size setting in{' '}
<Text style={{fontWeight: 'bold'}}>
Settings.app - Display & Brightness - Text Size
</Text>
</Text> </Text>
<Text style={{marginTop: 10}}> <Text style={{marginTop: 10}}>
You can disable scaling for your Text component by passing {"\""}allowFontScaling={"{"}false{"}\""} prop. You can disable scaling for your Text component by passing {'"'}allowFontScaling={'{'}false{'}"'}{' '}
prop.
</Text> </Text>
<Text allowFontScaling={false} style={{marginTop: 20}}> <Text allowFontScaling={false} style={{marginTop: 20}}>
This text will not scale. This text will not scale.
@ -506,30 +660,46 @@ exports.examples = [
</View> </View>
); );
}, },
}, { },
{
title: 'Inline views', title: 'Inline views',
render: function() { render: function() {
return ( return (
<View> <View>
<Text> <Text>
This text contains an inline blue view <View style={{width: 25, height: 25, backgroundColor: 'steelblue'}} /> and This text contains an inline blue view{' '}
an inline image <Image source={require('./flux.png')} style={{width: 30, height: 11, resizeMode: 'cover'}}/>. Neat, huh? <View
style={{width: 25, height: 25, backgroundColor: 'steelblue'}}
/>{' '}
and an inline image{' '}
<Image
source={require('./flux.png')}
style={{width: 30, height: 11, resizeMode: 'cover'}}
/>. Neat, huh?
</Text> </Text>
</View> </View>
); );
}, },
}, { },
{
title: 'Text shadow', title: 'Text shadow',
render: function() { render: function() {
return ( return (
<View> <View>
<Text style={{fontSize: 20, textShadowOffset: {width: 2, height: 2}, textShadowRadius: 1, textShadowColor: '#00cccc'}}> <Text
style={{
fontSize: 20,
textShadowOffset: {width: 2, height: 2},
textShadowRadius: 1,
textShadowColor: '#00cccc',
}}>
Demo text shadow Demo text shadow
</Text> </Text>
</View> </View>
); );
}, },
}, { },
{
title: 'Ellipsize mode', title: 'Ellipsize mode',
render: function() { render: function() {
return ( return (
@ -549,18 +719,25 @@ exports.examples = [
</View> </View>
); );
}, },
}, { },
{
title: 'Font variants', title: 'Font variants',
render: function() { render: function() {
return ( return (
<View> <View>
<Text style={{fontVariant: ['small-caps']}}> <Text style={{fontVariant: ['small-caps']}}>Small Caps{'\n'}</Text>
Small Caps{'\n'} <Text
</Text> style={{
<Text style={{fontFamily: (Platform.isTVOS ? 'Times' : 'Hoefler Text'), fontVariant: ['oldstyle-nums']}}> fontFamily: Platform.isTVOS ? 'Times' : 'Hoefler Text',
fontVariant: ['oldstyle-nums'],
}}>
Old Style nums 0123456789{'\n'} Old Style nums 0123456789{'\n'}
</Text> </Text>
<Text style={{fontFamily: (Platform.isTVOS ? 'Times' : 'Hoefler Text'), fontVariant: ['lining-nums']}}> <Text
style={{
fontFamily: Platform.isTVOS ? 'Times' : 'Hoefler Text',
fontVariant: ['lining-nums'],
}}>
Lining nums 0123456789{'\n'} Lining nums 0123456789{'\n'}
</Text> </Text>
<Text style={{fontVariant: ['tabular-nums']}}> <Text style={{fontVariant: ['tabular-nums']}}>
@ -576,9 +753,11 @@ exports.examples = [
</View> </View>
); );
}, },
}, { },
{
title: 'Dynamic Font Size Adjustment', title: 'Dynamic Font Size Adjustment',
render: function(): React.Element<any> { render: function(): React.Element<any> {
return <AdjustingFontSize />; return <AdjustingFontSize />;
}, },
}]; },
];

View File

@ -494,7 +494,7 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
reactTags[index] = shadowView.reactTag; reactTags[index] = shadowView.reactTag;
frameDataArray[index++] = (RCTFrameData){ frameDataArray[index++] = (RCTFrameData){
shadowView.frame, shadowView.frame,
shadowView.effectiveLayoutDirection, shadowView.layoutDirection,
shadowView.isNewView, shadowView.isNewView,
shadowView.superview.isNewView, shadowView.superview.isNewView,
shadowView.isHidden, shadowView.isHidden,

View File

@ -27,7 +27,7 @@
absolutePosition:(CGPoint)absolutePosition absolutePosition:(CGPoint)absolutePosition
{ {
// Call super method if LTR layout is enforced. // Call super method if LTR layout is enforced.
if (self.effectiveLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight) { if (self.layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight) {
[super applyLayoutNode:node [super applyLayoutNode:node
viewsWithNewFrame:viewsWithNewFrame viewsWithNewFrame:viewsWithNewFrame
absolutePosition:absolutePosition]; absolutePosition:absolutePosition];

View File

@ -84,9 +84,10 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
@property (nonatomic, assign, getter=isHidden) BOOL hidden; @property (nonatomic, assign, getter=isHidden) BOOL hidden;
/** /**
* Computed layout direction for the view backed to Yoga node value. * Layout direction for the view as computed in applyLayoutNode.
*/ */
@property (nonatomic, assign, readonly) UIUserInterfaceLayoutDirection effectiveLayoutDirection;
@property (nonatomic, assign, readonly) UIUserInterfaceLayoutDirection layoutDirection;
/** /**
* Position and dimensions. * Position and dimensions.
@ -187,6 +188,11 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
*/ */
@property (nonatomic, assign) CGSize intrinsicContentSize; @property (nonatomic, assign) CGSize intrinsicContentSize;
/**
* Return the layout direction stored in the Yoga node that backs this view.
*/
- (UIUserInterfaceLayoutDirection)getLayoutDirectionFromYogaNode;
/** /**
* Calculate property changes that need to be propagated to the view. * Calculate property changes that need to be propagated to the view.
* The applierBlocks set contains RCTApplierBlock functions that must be applied * The applierBlocks set contains RCTApplierBlock functions that must be applied

View File

@ -204,8 +204,11 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT],
RCTRoundPixelValue(absoluteBottomRight.y - absoluteTopLeft.y) RCTRoundPixelValue(absoluteBottomRight.y - absoluteTopLeft.y)
}}; }};
if (!CGRectEqualToRect(frame, _frame)) { UIUserInterfaceLayoutDirection updatedLayoutDirection = [self getLayoutDirectionFromYogaNode];
if (!CGRectEqualToRect(frame, _frame) || _layoutDirection != updatedLayoutDirection) {
_frame = frame; _frame = frame;
_layoutDirection = updatedLayoutDirection;
[viewsWithNewFrame addObject:self]; [viewsWithNewFrame addObject:self];
} }
@ -493,7 +496,7 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT],
// Layout Direction // Layout Direction
- (UIUserInterfaceLayoutDirection)effectiveLayoutDirection { - (UIUserInterfaceLayoutDirection)getLayoutDirectionFromYogaNode {
// Even if `YGNodeLayoutGetDirection` can return `YGDirectionInherit` here, it actually means // Even if `YGNodeLayoutGetDirection` can return `YGDirectionInherit` here, it actually means
// that Yoga will use LTR layout for the view (even if layout process is not finished yet). // that Yoga will use LTR layout for the view (even if layout process is not finished yet).
return YGNodeLayoutGetDirection(_yogaNode) == YGDirectionRTL ? UIUserInterfaceLayoutDirectionRightToLeft : UIUserInterfaceLayoutDirectionLeftToRight; return YGNodeLayoutGetDirection(_yogaNode) == YGDirectionRTL ? UIUserInterfaceLayoutDirectionRightToLeft : UIUserInterfaceLayoutDirectionLeftToRight;