From 0370c5d743bf3310bde6471642a13d258e855c34 Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Fri, 24 Aug 2018 12:51:58 -0700 Subject: [PATCH 1/3] iOS: Add a maxContentSizeMultiplier prop to and **Motivation** Whenever a user changes the system font size to its maximum allowable setting, React Native apps that allow font scaling can become unusable because the text gets too big. Experimenting with a native app like iMessage on iOS, the font size used for non-body text (e.g. header, navigational elements) is capped while the body text (e.g. text in the message bubbles) is allowed to grow. This PR introduces a new prop on `` and `` called `maxContentSizeMultiplier`. This enables devs to set the maximum allowed text scale factor on a Text/TextInput. The default is 0 which means no limit. Another PR will add this feature to Android. **Test Plan** I created a test app which utilizes all categories of values of `maxContentSizeMultiplier`: - `undefined`: inherit from parent - `0`: no limit - `1`, `1.2`: fixed limits I tried this with `Text`, `TextInput` with `value`, and `TextInput` with children. For `Text`, I also verified that nesting works properly (if a child `Text` doesn't specify `maxContentSizeMultiplier`, it inherits it from its parent). Lastly, we've been using a version of this in Skype for several months. --- Libraries/Components/TextInput/TextInput.js | 15 ++++++++++++++- Libraries/Text/BaseText/RCTBaseTextViewManager.m | 1 + Libraries/Text/RCTTextAttributes.h | 3 ++- Libraries/Text/RCTTextAttributes.m | 16 +++++++++++++++- Libraries/Text/Text.js | 2 ++ Libraries/Text/Text/RCTTextViewManager.m | 1 - Libraries/Text/TextPropTypes.js | 8 ++++++++ 7 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index c1ddc8b3e062bd..f2c641c9c85887 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -177,6 +177,7 @@ type Props = $ReadOnly<{| autoCorrect?: ?boolean, autoFocus?: ?boolean, allowFontScaling?: ?boolean, + maxContentSizeMultiplier?: ?boolean, editable?: ?boolean, keyboardType?: ?KeyboardType, returnKeyType?: ?ReturnKeyType, @@ -367,6 +368,14 @@ const TextInput = createReactClass({ * default is `true`. */ allowFontScaling: PropTypes.bool, + /** + * Specifies largest possible scale a font can reach when `allowFontScaling` is enabled. + * Possible values: + * `null/undefined` (default): inherit from the parent node or the global default (0) + * `0`: no max, ignore parent/global default + * `>= 1`: sets the maxContentSizeMultiplier of this node to this value + */ + maxContentSizeMultiplier: PropTypes.number, /** * If `false`, text is not editable. The default value is `true`. */ @@ -933,7 +942,11 @@ const TextInput = createReactClass({ ); if (childCount >= 1) { children = ( - + {children} ); diff --git a/Libraries/Text/BaseText/RCTBaseTextViewManager.m b/Libraries/Text/BaseText/RCTBaseTextViewManager.m index 2cf43ebf38cf6c..c98b0ab6340348 100644 --- a/Libraries/Text/BaseText/RCTBaseTextViewManager.m +++ b/Libraries/Text/BaseText/RCTBaseTextViewManager.m @@ -36,6 +36,7 @@ - (RCTShadowView *)shadowView RCT_REMAP_SHADOW_PROPERTY(fontStyle, textAttributes.fontStyle, NSString) RCT_REMAP_SHADOW_PROPERTY(fontVariant, textAttributes.fontVariant, NSArray) RCT_REMAP_SHADOW_PROPERTY(allowFontScaling, textAttributes.allowFontScaling, BOOL) +RCT_REMAP_SHADOW_PROPERTY(maxContentSizeMultiplier, textAttributes.maxContentSizeMultiplier, CGFloat) RCT_REMAP_SHADOW_PROPERTY(letterSpacing, textAttributes.letterSpacing, CGFloat) // Paragraph Styles RCT_REMAP_SHADOW_PROPERTY(lineHeight, textAttributes.lineHeight, CGFloat) diff --git a/Libraries/Text/RCTTextAttributes.h b/Libraries/Text/RCTTextAttributes.h index 7575bb17269f80..49f0cdc8641a42 100644 --- a/Libraries/Text/RCTTextAttributes.h +++ b/Libraries/Text/RCTTextAttributes.h @@ -31,6 +31,7 @@ extern NSString *const RCTTextAttributesTagAttributeName; @property (nonatomic, copy, nullable) NSString *fontFamily; @property (nonatomic, assign) CGFloat fontSize; @property (nonatomic, assign) CGFloat fontSizeMultiplier; +@property (nonatomic, assign) CGFloat maxContentSizeMultiplier; @property (nonatomic, copy, nullable) NSString *fontWeight; @property (nonatomic, copy, nullable) NSString *fontStyle; @property (nonatomic, copy, nullable) NSArray *fontVariant; @@ -71,7 +72,7 @@ extern NSString *const RCTTextAttributesTagAttributeName; - (UIFont *)effectiveFont; /** - * Font size multiplier reflects `allowFontScaling` and `fontSizeMultiplier`. + * Font size multiplier reflects `allowFontScaling`, `fontSizeMultiplier`, and `maxContentSizeMultiplier`. */ - (CGFloat)effectiveFontSizeMultiplier; diff --git a/Libraries/Text/RCTTextAttributes.m b/Libraries/Text/RCTTextAttributes.m index 4309138d00fe09..74ea375015c175 100644 --- a/Libraries/Text/RCTTextAttributes.m +++ b/Libraries/Text/RCTTextAttributes.m @@ -14,6 +14,9 @@ NSString *const RCTTextAttributesIsHighlightedAttributeName = @"RCTTextAttributesIsHighlightedAttributeName"; NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttributeName"; +// Setting the default to 0 indicates that there is no max. +static CGFloat defaultMaxContentSizeMultiplier = 0.0; + @implementation RCTTextAttributes - (instancetype)init @@ -24,6 +27,7 @@ - (instancetype)init _lineHeight = NAN; _textDecorationStyle = NSUnderlineStyleSingle; _fontSizeMultiplier = NAN; + _maxContentSizeMultiplier = NAN; _alignment = NSTextAlignmentNatural; _baseWritingDirection = NSWritingDirectionNatural; _textShadowRadius = NAN; @@ -49,6 +53,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes _fontFamily = textAttributes->_fontFamily ?: _fontFamily; _fontSize = !isnan(textAttributes->_fontSize) ? textAttributes->_fontSize : _fontSize; _fontSizeMultiplier = !isnan(textAttributes->_fontSizeMultiplier) ? textAttributes->_fontSizeMultiplier : _fontSizeMultiplier; + _maxContentSizeMultiplier = !isnan(textAttributes->_maxContentSizeMultiplier) ? textAttributes->_maxContentSizeMultiplier : _maxContentSizeMultiplier; _fontWeight = textAttributes->_fontWeight ?: _fontWeight; _fontStyle = textAttributes->_fontStyle ?: _fontStyle; _fontVariant = textAttributes->_fontVariant ?: _fontVariant; @@ -191,7 +196,15 @@ - (UIFont *)effectiveFont - (CGFloat)effectiveFontSizeMultiplier { - return !RCTHasFontHandlerSet() && _allowFontScaling && !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0; + bool fontScalingEnabled = !RCTHasFontHandlerSet() && _allowFontScaling; + + if (fontScalingEnabled) { + CGFloat fontSizeMultiplier = !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0; + CGFloat maxContentSizeMultiplier = !isnan(_maxContentSizeMultiplier) ? _maxContentSizeMultiplier : defaultMaxContentSizeMultiplier; + return maxContentSizeMultiplier >= 1.0 ? fminf(maxContentSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; + } else { + return 1.0; + } } - (UIColor *)effectiveForegroundColor @@ -260,6 +273,7 @@ - (BOOL)isEqual:(RCTTextAttributes *)textAttributes RCTTextAttributesCompareObjects(_fontFamily) && RCTTextAttributesCompareFloats(_fontSize) && RCTTextAttributesCompareFloats(_fontSizeMultiplier) && + RCTTextAttributesCompareFloats(_maxContentSizeMultiplier) && RCTTextAttributesCompareStrings(_fontWeight) && RCTTextAttributesCompareObjects(_fontStyle) && RCTTextAttributesCompareObjects(_fontVariant) && diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 2a0a939f876af7..80c93e4ec55009 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -58,6 +58,7 @@ const viewConfig = { numberOfLines: true, ellipsizeMode: true, allowFontScaling: true, + maxContentSizeMultiplier: true, disabled: true, selectable: true, selectionColor: true, @@ -257,6 +258,7 @@ const RCTVirtualText = validAttributes: { ...ReactNativeViewAttributes.UIView, isHighlighted: true, + maxContentSizeMultiplier: true, }, uiViewClassName: 'RCTVirtualText', })); diff --git a/Libraries/Text/Text/RCTTextViewManager.m b/Libraries/Text/Text/RCTTextViewManager.m index a729e644898286..6421e118627c97 100644 --- a/Libraries/Text/Text/RCTTextViewManager.m +++ b/Libraries/Text/Text/RCTTextViewManager.m @@ -24,7 +24,6 @@ @interface RCTTextViewManager () @implementation RCTTextViewManager { NSHashTable *_shadowViews; - CGFloat _fontSizeMultiplier; } RCT_EXPORT_MODULE(RCTText) diff --git a/Libraries/Text/TextPropTypes.js b/Libraries/Text/TextPropTypes.js index 5a3c99d7378531..bc61de2964e088 100644 --- a/Libraries/Text/TextPropTypes.js +++ b/Libraries/Text/TextPropTypes.js @@ -100,6 +100,14 @@ module.exports = { * See https://facebook.github.io/react-native/docs/text.html#allowfontscaling */ allowFontScaling: PropTypes.bool, + /** + * Specifies largest possible scale a font can reach when `allowFontScaling` is enabled. + * Possible values: + * `null/undefined` (default): inherit from the parent node or the global default (0) + * `0`: no max, ignore parent/global default + * `>= 1`: sets the maxContentSizeMultiplier of this node to this value + */ + maxContentSizeMultiplier: PropTypes.number, /** * Indicates whether the view is an accessibility element. * From ab7ea6aa7cb773c4bd29505faecbb1562db7e074 Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Tue, 4 Sep 2018 11:52:03 -0700 Subject: [PATCH 2/3] Inline `defaultMaxContentSizeMultiplier` --- Libraries/Text/RCTTextAttributes.m | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Libraries/Text/RCTTextAttributes.m b/Libraries/Text/RCTTextAttributes.m index 74ea375015c175..d21e93ad7eb789 100644 --- a/Libraries/Text/RCTTextAttributes.m +++ b/Libraries/Text/RCTTextAttributes.m @@ -14,9 +14,6 @@ NSString *const RCTTextAttributesIsHighlightedAttributeName = @"RCTTextAttributesIsHighlightedAttributeName"; NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttributeName"; -// Setting the default to 0 indicates that there is no max. -static CGFloat defaultMaxContentSizeMultiplier = 0.0; - @implementation RCTTextAttributes - (instancetype)init @@ -200,7 +197,7 @@ - (CGFloat)effectiveFontSizeMultiplier if (fontScalingEnabled) { CGFloat fontSizeMultiplier = !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0; - CGFloat maxContentSizeMultiplier = !isnan(_maxContentSizeMultiplier) ? _maxContentSizeMultiplier : defaultMaxContentSizeMultiplier; + CGFloat maxContentSizeMultiplier = !isnan(_maxContentSizeMultiplier) ? _maxContentSizeMultiplier : 0.0; return maxContentSizeMultiplier >= 1.0 ? fminf(maxContentSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; } else { return 1.0; From cd1c24b760f9fa40a5b7cbd0318c8d8411b0f040 Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Tue, 4 Sep 2018 11:54:48 -0700 Subject: [PATCH 3/3] Rename `maxContentSizeMultiplier` to `maxFontSizeMultiplier` --- Libraries/Components/TextInput/TextInput.js | 8 ++++---- Libraries/Text/BaseText/RCTBaseTextViewManager.m | 2 +- Libraries/Text/RCTTextAttributes.h | 4 ++-- Libraries/Text/RCTTextAttributes.m | 10 +++++----- Libraries/Text/Text.js | 4 ++-- Libraries/Text/TextPropTypes.js | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index f2c641c9c85887..d8de6d193ade47 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -177,7 +177,7 @@ type Props = $ReadOnly<{| autoCorrect?: ?boolean, autoFocus?: ?boolean, allowFontScaling?: ?boolean, - maxContentSizeMultiplier?: ?boolean, + maxFontSizeMultiplier?: ?boolean, editable?: ?boolean, keyboardType?: ?KeyboardType, returnKeyType?: ?ReturnKeyType, @@ -373,9 +373,9 @@ const TextInput = createReactClass({ * Possible values: * `null/undefined` (default): inherit from the parent node or the global default (0) * `0`: no max, ignore parent/global default - * `>= 1`: sets the maxContentSizeMultiplier of this node to this value + * `>= 1`: sets the maxFontSizeMultiplier of this node to this value */ - maxContentSizeMultiplier: PropTypes.number, + maxFontSizeMultiplier: PropTypes.number, /** * If `false`, text is not editable. The default value is `true`. */ @@ -945,7 +945,7 @@ const TextInput = createReactClass({ {children} diff --git a/Libraries/Text/BaseText/RCTBaseTextViewManager.m b/Libraries/Text/BaseText/RCTBaseTextViewManager.m index c98b0ab6340348..7ecf57588affec 100644 --- a/Libraries/Text/BaseText/RCTBaseTextViewManager.m +++ b/Libraries/Text/BaseText/RCTBaseTextViewManager.m @@ -36,7 +36,7 @@ - (RCTShadowView *)shadowView RCT_REMAP_SHADOW_PROPERTY(fontStyle, textAttributes.fontStyle, NSString) RCT_REMAP_SHADOW_PROPERTY(fontVariant, textAttributes.fontVariant, NSArray) RCT_REMAP_SHADOW_PROPERTY(allowFontScaling, textAttributes.allowFontScaling, BOOL) -RCT_REMAP_SHADOW_PROPERTY(maxContentSizeMultiplier, textAttributes.maxContentSizeMultiplier, CGFloat) +RCT_REMAP_SHADOW_PROPERTY(maxFontSizeMultiplier, textAttributes.maxFontSizeMultiplier, CGFloat) RCT_REMAP_SHADOW_PROPERTY(letterSpacing, textAttributes.letterSpacing, CGFloat) // Paragraph Styles RCT_REMAP_SHADOW_PROPERTY(lineHeight, textAttributes.lineHeight, CGFloat) diff --git a/Libraries/Text/RCTTextAttributes.h b/Libraries/Text/RCTTextAttributes.h index 49f0cdc8641a42..8a358866b82632 100644 --- a/Libraries/Text/RCTTextAttributes.h +++ b/Libraries/Text/RCTTextAttributes.h @@ -31,7 +31,7 @@ extern NSString *const RCTTextAttributesTagAttributeName; @property (nonatomic, copy, nullable) NSString *fontFamily; @property (nonatomic, assign) CGFloat fontSize; @property (nonatomic, assign) CGFloat fontSizeMultiplier; -@property (nonatomic, assign) CGFloat maxContentSizeMultiplier; +@property (nonatomic, assign) CGFloat maxFontSizeMultiplier; @property (nonatomic, copy, nullable) NSString *fontWeight; @property (nonatomic, copy, nullable) NSString *fontStyle; @property (nonatomic, copy, nullable) NSArray *fontVariant; @@ -72,7 +72,7 @@ extern NSString *const RCTTextAttributesTagAttributeName; - (UIFont *)effectiveFont; /** - * Font size multiplier reflects `allowFontScaling`, `fontSizeMultiplier`, and `maxContentSizeMultiplier`. + * Font size multiplier reflects `allowFontScaling`, `fontSizeMultiplier`, and `maxFontSizeMultiplier`. */ - (CGFloat)effectiveFontSizeMultiplier; diff --git a/Libraries/Text/RCTTextAttributes.m b/Libraries/Text/RCTTextAttributes.m index d21e93ad7eb789..e4db8031514f86 100644 --- a/Libraries/Text/RCTTextAttributes.m +++ b/Libraries/Text/RCTTextAttributes.m @@ -24,7 +24,7 @@ - (instancetype)init _lineHeight = NAN; _textDecorationStyle = NSUnderlineStyleSingle; _fontSizeMultiplier = NAN; - _maxContentSizeMultiplier = NAN; + _maxFontSizeMultiplier = NAN; _alignment = NSTextAlignmentNatural; _baseWritingDirection = NSWritingDirectionNatural; _textShadowRadius = NAN; @@ -50,7 +50,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes _fontFamily = textAttributes->_fontFamily ?: _fontFamily; _fontSize = !isnan(textAttributes->_fontSize) ? textAttributes->_fontSize : _fontSize; _fontSizeMultiplier = !isnan(textAttributes->_fontSizeMultiplier) ? textAttributes->_fontSizeMultiplier : _fontSizeMultiplier; - _maxContentSizeMultiplier = !isnan(textAttributes->_maxContentSizeMultiplier) ? textAttributes->_maxContentSizeMultiplier : _maxContentSizeMultiplier; + _maxFontSizeMultiplier = !isnan(textAttributes->_maxFontSizeMultiplier) ? textAttributes->_maxFontSizeMultiplier : _maxFontSizeMultiplier; _fontWeight = textAttributes->_fontWeight ?: _fontWeight; _fontStyle = textAttributes->_fontStyle ?: _fontStyle; _fontVariant = textAttributes->_fontVariant ?: _fontVariant; @@ -197,8 +197,8 @@ - (CGFloat)effectiveFontSizeMultiplier if (fontScalingEnabled) { CGFloat fontSizeMultiplier = !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0; - CGFloat maxContentSizeMultiplier = !isnan(_maxContentSizeMultiplier) ? _maxContentSizeMultiplier : 0.0; - return maxContentSizeMultiplier >= 1.0 ? fminf(maxContentSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; + CGFloat maxFontSizeMultiplier = !isnan(_maxFontSizeMultiplier) ? _maxFontSizeMultiplier : 0.0; + return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier; } else { return 1.0; } @@ -270,7 +270,7 @@ - (BOOL)isEqual:(RCTTextAttributes *)textAttributes RCTTextAttributesCompareObjects(_fontFamily) && RCTTextAttributesCompareFloats(_fontSize) && RCTTextAttributesCompareFloats(_fontSizeMultiplier) && - RCTTextAttributesCompareFloats(_maxContentSizeMultiplier) && + RCTTextAttributesCompareFloats(_maxFontSizeMultiplier) && RCTTextAttributesCompareStrings(_fontWeight) && RCTTextAttributesCompareObjects(_fontStyle) && RCTTextAttributesCompareObjects(_fontVariant) && diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 80c93e4ec55009..011fba8a0afec8 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -58,7 +58,7 @@ const viewConfig = { numberOfLines: true, ellipsizeMode: true, allowFontScaling: true, - maxContentSizeMultiplier: true, + maxFontSizeMultiplier: true, disabled: true, selectable: true, selectionColor: true, @@ -258,7 +258,7 @@ const RCTVirtualText = validAttributes: { ...ReactNativeViewAttributes.UIView, isHighlighted: true, - maxContentSizeMultiplier: true, + maxFontSizeMultiplier: true, }, uiViewClassName: 'RCTVirtualText', })); diff --git a/Libraries/Text/TextPropTypes.js b/Libraries/Text/TextPropTypes.js index bc61de2964e088..e99cf1cc56cb83 100644 --- a/Libraries/Text/TextPropTypes.js +++ b/Libraries/Text/TextPropTypes.js @@ -105,9 +105,9 @@ module.exports = { * Possible values: * `null/undefined` (default): inherit from the parent node or the global default (0) * `0`: no max, ignore parent/global default - * `>= 1`: sets the maxContentSizeMultiplier of this node to this value + * `>= 1`: sets the maxFontSizeMultiplier of this node to this value */ - maxContentSizeMultiplier: PropTypes.number, + maxFontSizeMultiplier: PropTypes.number, /** * Indicates whether the view is an accessibility element. *