Skip to content

Commit 0481948

Browse files
bang9facebook-github-bot
authored andcommitted
feat(iOS): added lineBreakStrategy attribute to Text/TextInput (#31272)
Summary: iOS did not support the implementation of Korean word-wrap(line-break) before iOS14. If the attribute applied, the word-wrap of Korean will works. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Added] - Line break strategy for Text and TextInput components Pull Request resolved: #31272 Test Plan: 1. Test build and run on above iOS 14. 2. Test it does not affect existing text components when set default(none) strategy. 3. Test whether word-wrap works with Korean when set hangul-word strategy. <img src="https://user-images.githubusercontent.com/26326015/112963967-d7f70c00-9182-11eb-9a34-8c758b80c219.png" width="300" height="" style="max-width:100%;"> Reviewed By: javache Differential Revision: D39824809 Pulled By: lunaleaps fbshipit-source-id: 42cb0385221a38c84e80d3494d1bfc1934ecf32b
1 parent a0ee6fa commit 0481948

File tree

19 files changed

+242
-1
lines changed

19 files changed

+242
-1
lines changed

Libraries/Components/TextInput/RCTTextInputViewConfig.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ const RCTTextInputViewConfig = {
149149
clearTextOnFocus: true,
150150
showSoftInputOnFocus: true,
151151
autoFocus: true,
152+
lineBreakStrategyIOS: true,
152153
...ConditionallyIgnoredEventHandlers({
153154
onChange: true,
154155
onSelectionChange: true,

Libraries/Components/TextInput/TextInput.flow.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ type IOSProps = $ReadOnly<{|
319319
* @platform ios
320320
*/
321321
textContentType?: ?TextContentType,
322+
323+
/**
324+
* Set line break strategy on iOS.
325+
* @platform ios
326+
*/
327+
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),
322328
|}>;
323329

324330
type AndroidProps = $ReadOnly<{|

Libraries/Components/TextInput/TextInput.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,12 @@ type IOSProps = $ReadOnly<{|
352352
* @platform ios
353353
*/
354354
textContentType?: ?TextContentType,
355+
356+
/**
357+
* Set line break strategy on iOS.
358+
* @platform ios
359+
*/
360+
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),
355361
|}>;
356362

357363
type AndroidProps = $ReadOnly<{|

Libraries/Text/BaseText/RCTBaseTextViewManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ - (RCTShadowView *)shadowView
4242
RCT_REMAP_SHADOW_PROPERTY(lineHeight, textAttributes.lineHeight, CGFloat)
4343
RCT_REMAP_SHADOW_PROPERTY(textAlign, textAttributes.alignment, NSTextAlignment)
4444
RCT_REMAP_SHADOW_PROPERTY(writingDirection, textAttributes.baseWritingDirection, NSWritingDirection)
45+
RCT_REMAP_SHADOW_PROPERTY(lineBreakStrategyIOS, textAttributes.lineBreakStrategy, NSLineBreakStrategy)
4546
// Decoration
4647
RCT_REMAP_SHADOW_PROPERTY(textDecorationColor, textAttributes.textDecorationColor, UIColor)
4748
RCT_REMAP_SHADOW_PROPERTY(textDecorationStyle, textAttributes.textDecorationStyle, NSUnderlineStyle)

Libraries/Text/RCTTextAttributes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ extern NSString *const RCTTextAttributesTagAttributeName;
4141
@property (nonatomic, assign) CGFloat lineHeight;
4242
@property (nonatomic, assign) NSTextAlignment alignment;
4343
@property (nonatomic, assign) NSWritingDirection baseWritingDirection;
44+
@property (nonatomic, assign) NSLineBreakStrategy lineBreakStrategy;
4445
// Decoration
4546
@property (nonatomic, strong, nullable) UIColor *textDecorationColor;
4647
@property (nonatomic, assign) NSUnderlineStyle textDecorationStyle;

Libraries/Text/RCTTextAttributes.m

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ - (instancetype)init
2727
_maxFontSizeMultiplier = NAN;
2828
_alignment = NSTextAlignmentNatural;
2929
_baseWritingDirection = NSWritingDirectionNatural;
30+
_lineBreakStrategy = NSLineBreakStrategyNone;
3031
_textShadowRadius = NAN;
3132
_opacity = NAN;
3233
_textTransform = RCTTextTransformUndefined;
@@ -66,6 +67,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
6667
_baseWritingDirection = textAttributes->_baseWritingDirection != NSWritingDirectionNatural
6768
? textAttributes->_baseWritingDirection
6869
: _baseWritingDirection; // *
70+
_lineBreakStrategy = textAttributes->_lineBreakStrategy ?: _lineBreakStrategy;
6971

7072
// Decoration
7173
_textDecorationColor = textAttributes->_textDecorationColor ?: _textDecorationColor;
@@ -117,6 +119,13 @@ - (NSParagraphStyle *)effectiveParagraphStyle
117119
isParagraphStyleUsed = YES;
118120
}
119121

122+
if (_lineBreakStrategy != NSLineBreakStrategyNone) {
123+
if (@available(iOS 14.0, *)) {
124+
paragraphStyle.lineBreakStrategy = _lineBreakStrategy;
125+
isParagraphStyleUsed = YES;
126+
}
127+
}
128+
120129
if (!isnan(_lineHeight)) {
121130
CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier;
122131
paragraphStyle.minimumLineHeight = lineHeight;
@@ -318,7 +327,7 @@ - (BOOL)isEqual:(RCTTextAttributes *)textAttributes
318327
RCTTextAttributesCompareFloats(_letterSpacing) &&
319328
// Paragraph Styles
320329
RCTTextAttributesCompareFloats(_lineHeight) && RCTTextAttributesCompareFloats(_alignment) &&
321-
RCTTextAttributesCompareOthers(_baseWritingDirection) &&
330+
RCTTextAttributesCompareOthers(_baseWritingDirection) && RCTTextAttributesCompareOthers(_lineBreakStrategy) &&
322331
// Decoration
323332
RCTTextAttributesCompareObjects(_textDecorationColor) && RCTTextAttributesCompareOthers(_textDecorationStyle) &&
324333
RCTTextAttributesCompareOthers(_textDecorationLine) &&

Libraries/Text/TextNativeComponent.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const textViewConfig = {
4545
onInlineViewLayout: true,
4646
dataDetectorType: true,
4747
android_hyphenationFrequency: true,
48+
lineBreakStrategyIOS: true,
4849
},
4950
directEventTypes: {
5051
topTextLayout: {

Libraries/Text/TextProps.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,11 @@ export type TextProps = $ReadOnly<{|
236236
* See https://reactnative.dev/docs/text#supperhighlighting
237237
*/
238238
suppressHighlighting?: ?boolean,
239+
240+
/**
241+
* Set line break strategy on iOS.
242+
*
243+
* See https://reactnative.dev/docs/text.html#linebreakstrategyios
244+
*/
245+
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),
239246
|}>;

React/Base/RCTConvert.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ typedef NSURL RCTFileURL;
6565
+ (NSTextAlignment)NSTextAlignment:(id)json;
6666
+ (NSUnderlineStyle)NSUnderlineStyle:(id)json;
6767
+ (NSWritingDirection)NSWritingDirection:(id)json;
68+
+ (NSLineBreakStrategy)NSLineBreakStrategy:(id)json;
6869
+ (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json;
6970
+ (UITextFieldViewMode)UITextFieldViewMode:(id)json;
7071
+ (UIKeyboardType)UIKeyboardType:(id)json;

React/Base/RCTConvert.m

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,25 @@ + (NSLocale *)NSLocale:(id)json
375375
NSWritingDirectionNatural,
376376
integerValue)
377377

378+
+ (NSLineBreakStrategy)NSLineBreakStrategy:(id)json RCT_DYNAMIC
379+
{
380+
if (@available(iOS 14.0, *)) {
381+
static NSDictionary *mapping;
382+
static dispatch_once_t onceToken;
383+
dispatch_once(&onceToken, ^{
384+
mapping = @{
385+
@"none" : @(NSLineBreakStrategyNone),
386+
@"standard" : @(NSLineBreakStrategyStandard),
387+
@"hangul-word" : @(NSLineBreakStrategyHangulWordPriority),
388+
@"push-out" : @(NSLineBreakStrategyPushOut)
389+
};
390+
});
391+
return RCTConvertEnumValue("NSLineBreakStrategy", mapping, @(NSLineBreakStrategyNone), json).integerValue;
392+
} else {
393+
return NSLineBreakStrategyNone;
394+
}
395+
}
396+
378397
RCT_ENUM_CONVERTER(
379398
UITextAutocapitalizationType,
380399
(@{

ReactCommon/react/renderer/attributedstring/TextAttributes.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ void TextAttributes::apply(TextAttributes textAttributes) {
6262
baseWritingDirection = textAttributes.baseWritingDirection.has_value()
6363
? textAttributes.baseWritingDirection
6464
: baseWritingDirection;
65+
lineBreakStrategy = textAttributes.lineBreakStrategy.has_value()
66+
? textAttributes.lineBreakStrategy
67+
: lineBreakStrategy;
6568

6669
// Decoration
6770
textDecorationColor = textAttributes.textDecorationColor
@@ -110,6 +113,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
110113
allowFontScaling,
111114
alignment,
112115
baseWritingDirection,
116+
lineBreakStrategy,
113117
textDecorationColor,
114118
textDecorationLineType,
115119
textDecorationStyle,
@@ -129,6 +133,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
129133
rhs.allowFontScaling,
130134
rhs.alignment,
131135
rhs.baseWritingDirection,
136+
rhs.lineBreakStrategy,
132137
rhs.textDecorationColor,
133138
rhs.textDecorationLineType,
134139
rhs.textDecorationStyle,
@@ -187,6 +192,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
187192
debugStringConvertibleItem("lineHeight", lineHeight),
188193
debugStringConvertibleItem("alignment", alignment),
189194
debugStringConvertibleItem("baseWritingDirection", baseWritingDirection),
195+
debugStringConvertibleItem("lineBreakStrategyIOS", lineBreakStrategy),
190196

191197
// Decoration
192198
debugStringConvertibleItem("textDecorationColor", textDecorationColor),

ReactCommon/react/renderer/attributedstring/TextAttributes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class TextAttributes : public DebugStringConvertible {
5757
Float lineHeight{std::numeric_limits<Float>::quiet_NaN()};
5858
std::optional<TextAlignment> alignment{};
5959
std::optional<WritingDirection> baseWritingDirection{};
60+
std::optional<LineBreakStrategy> lineBreakStrategy{};
6061

6162
// Decoration
6263
SharedColor textDecorationColor{};
@@ -121,6 +122,7 @@ struct hash<facebook::react::TextAttributes> {
121122
textAttributes.lineHeight,
122123
textAttributes.alignment,
123124
textAttributes.baseWritingDirection,
125+
textAttributes.lineBreakStrategy,
124126
textAttributes.textDecorationColor,
125127
textAttributes.textDecorationLineType,
126128
textAttributes.textDecorationStyle,

ReactCommon/react/renderer/attributedstring/conversions.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,52 @@ inline std::string toString(const WritingDirection &writingDirection) {
420420
return "auto";
421421
}
422422

423+
inline void fromRawValue(
424+
const PropsParserContext &context,
425+
const RawValue &value,
426+
LineBreakStrategy &result) {
427+
react_native_assert(value.hasType<std::string>());
428+
if (value.hasType<std::string>()) {
429+
auto string = (std::string)value;
430+
if (string == "none") {
431+
result = LineBreakStrategy::None;
432+
} else if (string == "push-out") {
433+
result = LineBreakStrategy::PushOut;
434+
} else if (string == "hangul-word") {
435+
result = LineBreakStrategy::HangulWordPriority;
436+
} else if (string == "standard") {
437+
result = LineBreakStrategy::Standard;
438+
} else {
439+
LOG(ERROR) << "Unsupported LineBreakStrategy value: " << string;
440+
react_native_assert(false);
441+
// sane default for prod
442+
result = LineBreakStrategy::None;
443+
}
444+
return;
445+
}
446+
447+
LOG(ERROR) << "Unsupported LineBreakStrategy type";
448+
// sane default for prod
449+
result = LineBreakStrategy::None;
450+
}
451+
452+
inline std::string toString(const LineBreakStrategy &lineBreakStrategy) {
453+
switch (lineBreakStrategy) {
454+
case LineBreakStrategy::None:
455+
return "none";
456+
case LineBreakStrategy::PushOut:
457+
return "push-out";
458+
case LineBreakStrategy::HangulWordPriority:
459+
return "hangul-word";
460+
case LineBreakStrategy::Standard:
461+
return "standard";
462+
}
463+
464+
LOG(ERROR) << "Unsupported LineBreakStrategy value";
465+
// sane default for prod
466+
return "none";
467+
}
468+
423469
inline void fromRawValue(
424470
const PropsParserContext &context,
425471
const RawValue &value,
@@ -873,6 +919,10 @@ inline folly::dynamic toDynamic(const TextAttributes &textAttributes) {
873919
_textAttributes(
874920
"baseWritingDirection", toString(*textAttributes.baseWritingDirection));
875921
}
922+
if (textAttributes.lineBreakStrategy.has_value()) {
923+
_textAttributes(
924+
"lineBreakStrategyIOS", toString(*textAttributes.lineBreakStrategy));
925+
}
876926
// Decoration
877927
if (textAttributes.textDecorationColor) {
878928
_textAttributes(
@@ -982,6 +1032,7 @@ constexpr static MapBuffer::Key TA_KEY_TEXT_SHADOW_COLOR = 19;
9821032
constexpr static MapBuffer::Key TA_KEY_IS_HIGHLIGHTED = 20;
9831033
constexpr static MapBuffer::Key TA_KEY_LAYOUT_DIRECTION = 21;
9841034
constexpr static MapBuffer::Key TA_KEY_ACCESSIBILITY_ROLE = 22;
1035+
constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 23;
9851036

9861037
// constants for ParagraphAttributes serialization
9871038
constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0;
@@ -1084,6 +1135,11 @@ inline MapBuffer toMapBuffer(const TextAttributes &textAttributes) {
10841135
TA_KEY_BEST_WRITING_DIRECTION,
10851136
toString(*textAttributes.baseWritingDirection));
10861137
}
1138+
if (textAttributes.lineBreakStrategy.has_value()) {
1139+
builder.putString(
1140+
TA_KEY_LINE_BREAK_STRATEGY,
1141+
toString(*textAttributes.lineBreakStrategy));
1142+
}
10871143
// Decoration
10881144
if (textAttributes.textDecorationColor) {
10891145
builder.putInt(

ReactCommon/react/renderer/attributedstring/primitives.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ enum class WritingDirection {
7474
RightToLeft // Right to left writing direction.
7575
};
7676

77+
enum class LineBreakStrategy {
78+
None, // Don't use any line break strategies
79+
PushOut, // Use the push out line break strategy.
80+
HangulWordPriority, // When specified, it prohibits breaking between Hangul
81+
// characters.
82+
Standard // Use the same configuration of line break strategies that the
83+
// system uses for standard UI labels.
84+
};
85+
7786
enum class TextDecorationLineType {
7887
None,
7988
Underline,

ReactCommon/react/renderer/components/text/BaseTextProps.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ static TextAttributes convertRawProp(
105105
"baseWritingDirection",
106106
sourceTextAttributes.baseWritingDirection,
107107
defaultTextAttributes.baseWritingDirection);
108+
textAttributes.lineBreakStrategy = convertRawProp(
109+
context,
110+
rawProps,
111+
"lineBreakStrategyIOS",
112+
sourceTextAttributes.lineBreakStrategy,
113+
defaultTextAttributes.lineBreakStrategy);
108114

109115
// Decoration
110116
textAttributes.textDecorationColor = convertRawProp(
@@ -243,6 +249,12 @@ void BaseTextProps::setProp(
243249
textAttributes,
244250
baseWritingDirection,
245251
"baseWritingDirection");
252+
REBUILD_FIELD_SWITCH_CASE(
253+
defaults,
254+
value,
255+
textAttributes,
256+
lineBreakStrategy,
257+
"lineBreakStrategyIOS");
246258
REBUILD_FIELD_SWITCH_CASE(
247259
defaults,
248260
value,

ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex
156156
isParagraphStyleUsed = YES;
157157
}
158158

159+
if (textAttributes.lineBreakStrategy.has_value()) {
160+
paragraphStyle.lineBreakStrategy =
161+
RCTNSLineBreakStrategyFromLineBreakStrategy(textAttributes.lineBreakStrategy.value());
162+
isParagraphStyleUsed = YES;
163+
}
164+
159165
if (!isnan(textAttributes.lineHeight)) {
160166
CGFloat lineHeight = textAttributes.lineHeight * RCTEffectiveFontSizeMultiplierFromTextAttributes(textAttributes);
161167
paragraphStyle.minimumLineHeight = lineHeight;

ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTTextPrimitivesConversions.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,28 @@ inline static NSWritingDirection RCTNSWritingDirectionFromWritingDirection(Writi
4040
}
4141
}
4242

43+
inline static NSLineBreakStrategy RCTNSLineBreakStrategyFromLineBreakStrategy(LineBreakStrategy lineBreakStrategy)
44+
{
45+
switch (lineBreakStrategy) {
46+
case LineBreakStrategy::None:
47+
return NSLineBreakStrategyNone;
48+
case LineBreakStrategy::PushOut:
49+
return NSLineBreakStrategyPushOut;
50+
case LineBreakStrategy::HangulWordPriority:
51+
if (@available(iOS 14.0, *)) {
52+
return NSLineBreakStrategyHangulWordPriority;
53+
} else {
54+
return NSLineBreakStrategyNone;
55+
}
56+
case LineBreakStrategy::Standard:
57+
if (@available(iOS 14.0, *)) {
58+
return NSLineBreakStrategyStandard;
59+
} else {
60+
return NSLineBreakStrategyNone;
61+
}
62+
}
63+
}
64+
4365
inline static RCTFontStyle RCTFontStyleFromFontStyle(FontStyle fontStyle)
4466
{
4567
switch (fontStyle) {

0 commit comments

Comments
 (0)