Skip to content

Commit

Permalink
iOS: Support allowFontScaling on TextInput
Browse files Browse the repository at this point in the history
Summary:
Currently, only `Text` supports the `allowFontScaling` prop. This commit adds support for it on `TextInput`.

As part of this change, the TextInput setters for font attributes (e.g. size, weight) had to be refactored. The problem with them is that they use RCTFont's helpers which create a new font based on an existing font. These helpers lose information. In particular, they lose the scaleMultiplier.

For example, suppose the font size is 12 and the device's font multiplier is set to 1.5. So we'd create a font with size 12 and scaleMultiplier 1.5 which is an effective size of 18 (which is the only thing stored in the font). Next, suppose the device's font multiplier changes to 1. So we'd use an RCTFont helper to create a new font based on the existing font but with a scaleMultiplier of 1. However, the font didn't store the font size (12) and scaleMultiplier (1.5) separately. It just knows the (effective) font size of 18. So RCTFont thinks the new font has a font size of 18 and a scaleMultiplier of 1 so its effective font size is 18. This is incorrect and it should have been 12.

To fix this, the font attributes are now all stored individually. Anytime one of them changes, updateFont is called which recreates the font from scratch. This happens to fix some bugs around fontStyle and fontWeight which were reported several times before: #13730, #12738, #2140, #8533.

Created a test app where I verified that `allowFontScaling` works properly for `TextInputs` for all values (`undefined`, `true`, `false`) for a variety of `TextInputs`:
  - Singleline TextInput
  - Singleline TextInput's placeholder
  - Multiline TextInput
  - Multiline TextInput's placeholder
  - Multiline TextInput using children instead of `value`

Also, verified switching `fontSize`, `fontWeight`, `fontStyle` and `fontFamily` through a bunch of combinations works properly.

Lastly, my team has been using this change in our app.

Adam Comella
Microsoft Corp.
Closes #14030

Reviewed By: TheSavior

Differential Revision: D5899959

Pulled By: shergin

fbshipit-source-id: c8c8c4d4d670cd2a142286e79bfffef3b58cecd3
  • Loading branch information
Adam Comella authored and facebook-github-bot committed Oct 2, 2017
1 parent cd74e46 commit 9c4ec30
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 37 deletions.
13 changes: 11 additions & 2 deletions Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ const TextInput = createReactClass({
* @platform android
*/
autoGrow: PropTypes.bool,
/**
* Specifies whether fonts should scale to respect Text Size accessibility settings. The
* default is `true`.
*/
allowFontScaling: PropTypes.bool,
/**
* If `false`, text is not editable. The default value is `true`.
*/
Expand Down Expand Up @@ -566,7 +571,11 @@ const TextInput = createReactClass({
*/
caretHidden: PropTypes.bool,
},

getDefaultProps(): Object {
return {
allowFontScaling: true,
};
},
/**
* `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We
* make `this` look like an actual native component class.
Expand Down Expand Up @@ -704,7 +713,7 @@ const TextInput = createReactClass({
'Cannot specify both value and children.'
);
if (childCount >= 1) {
children = <Text style={props.style}>{children}</Text>;
children = <Text style={props.style} allowFontScaling={props.allowFontScaling}>{children}</Text>;
}
if (props.inputView) {
children = [children, props.inputView];
Expand Down
31 changes: 31 additions & 0 deletions Libraries/Text/RCTFontAttributes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <UIKit/UIKit.h>

#import "RCTFontAttributesDelegate.h"

@class RCTAccessibilityManager;

@interface RCTFontAttributes : NSObject

@property (nonatomic, weak) id<RCTFontAttributesDelegate> delegate;

@property (readonly, nonatomic, strong) UIFont *font;

@property (nonatomic, assign) BOOL allowFontScaling;
@property (nonatomic, copy) NSString *fontFamily;
@property (nonatomic, strong) NSNumber *fontSize;
@property (nonatomic, assign) CGFloat fontSizeMultiplier;
@property (nonatomic, copy) NSString *fontStyle;
@property (nonatomic, copy) NSString *fontWeight;

- (instancetype)initWithAccessibilityManager:(RCTAccessibilityManager *)accessibilityManager;

@end
112 changes: 112 additions & 0 deletions Libraries/Text/RCTFontAttributes.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "RCTFontAttributes.h"

#import <React/RCTAccessibilityManager.h>
#import <React/RCTAssert.h>
#import <React/RCTFont.h>
#import <React/RCTLog.h>

@interface RCTFontAttributes ()
{
RCTAccessibilityManager *_accessibilityManager;
}

@property (nonatomic, strong) UIFont *font;

@end

@implementation RCTFontAttributes

- (instancetype)initWithAccessibilityManager:(RCTAccessibilityManager *)accessibilityManager
{
RCTAssertParam(accessibilityManager);

if (self = [super init]) {
_accessibilityManager = accessibilityManager;
_fontSizeMultiplier = _accessibilityManager.multiplier;

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contentSizeMultiplierDidChange)
name:RCTAccessibilityManagerDidUpdateMultiplierNotification
object:_accessibilityManager];

[self updateFont];
}
return self;
}

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)contentSizeMultiplierDidChange
{
self.fontSizeMultiplier = _accessibilityManager.multiplier;
}

- (void)setAllowFontScaling:(BOOL)allowFontScaling
{
_allowFontScaling = allowFontScaling;
[self updateFont];
}

- (void)setFontFamily:(NSString *)fontFamily
{
_fontFamily = fontFamily;
[self updateFont];
}

- (void)setFontSize:(NSNumber *)fontSize
{
_fontSize = fontSize;
[self updateFont];
}

- (void)setFontSizeMultiplier:(CGFloat)fontSizeMultiplier
{
_fontSizeMultiplier = fontSizeMultiplier;

if (_fontSizeMultiplier == 0) {
RCTLogError(@"fontSizeMultiplier value must be > zero.");
_fontSizeMultiplier = 1.0;
}

[self updateFont];
}

- (void)setFontStyle:(NSString *)fontStyle
{
_fontStyle = fontStyle;
[self updateFont];
}

- (void)setFontWeight:(NSString *)fontWeight
{
_fontWeight = fontWeight;
[self updateFont];
}

- (void)updateFont
{
CGFloat scaleMultiplier = self.allowFontScaling ? self.fontSizeMultiplier : 1.0;
self.font = [RCTFont updateFont:nil
withFamily:self.fontFamily
size:self.fontSize
weight:self.fontWeight
style:self.fontStyle
variant:nil
scaleMultiplier:scaleMultiplier];

[self.delegate fontAttributesDidChangeWithFont:self.font];
}

@end
14 changes: 14 additions & 0 deletions Libraries/Text/RCTFontAttributesDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

@protocol RCTFontAttributesDelegate <NSObject>

- (void)fontAttributesDidChangeWithFont:(UIFont *)font;

@end
8 changes: 8 additions & 0 deletions Libraries/Text/RCTText.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
59F60E921E661BDD0081153B /* RCTShadowTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 59F60E8E1E661BDD0081153B /* RCTShadowTextField.m */; };
59F60E931E661BDD0081153B /* RCTShadowTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59F60E901E661BDD0081153B /* RCTShadowTextView.m */; };
59F60E941E661BDD0081153B /* RCTShadowTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59F60E901E661BDD0081153B /* RCTShadowTextView.m */; };
A85C829A1F742AA20036C019 /* RCTFontAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = A85C82991F742AA20036C019 /* RCTFontAttributes.m */; };
AF3225F91DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */; };
AF3225FA1DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -111,6 +112,9 @@
59F60E8E1E661BDD0081153B /* RCTShadowTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowTextField.m; sourceTree = "<group>"; };
59F60E8F1E661BDD0081153B /* RCTShadowTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowTextView.h; sourceTree = "<group>"; };
59F60E901E661BDD0081153B /* RCTShadowTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowTextView.m; sourceTree = "<group>"; };
A85C82981F742AA20036C019 /* RCTFontAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFontAttributes.h; sourceTree = "<group>"; };
A85C82991F742AA20036C019 /* RCTFontAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFontAttributes.m; sourceTree = "<group>"; };
A85C82BA1F742D8F0036C019 /* RCTFontAttributesDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFontAttributesDelegate.h; sourceTree = "<group>"; };
AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+Text.h"; sourceTree = "<group>"; };
AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+Text.m"; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand All @@ -126,6 +130,9 @@
599DF25D1F0304B30079B53E /* RCTBackedTextInputViewProtocol.h */,
AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */,
AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */,
A85C82981F742AA20036C019 /* RCTFontAttributes.h */,
A85C82991F742AA20036C019 /* RCTFontAttributes.m */,
A85C82BA1F742D8F0036C019 /* RCTFontAttributesDelegate.h */,
58B511C61A9E6C5C00147676 /* RCTRawTextManager.h */,
58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */,
58B511C81A9E6C5C00147676 /* RCTShadowRawText.h */,
Expand Down Expand Up @@ -278,6 +285,7 @@
1362F1001B4D51F400E06D8C /* RCTTextField.m in Sources */,
59AF89AA1EDCBCC700F004B1 /* RCTUITextField.m in Sources */,
598F41261F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m in Sources */,
A85C829A1F742AA20036C019 /* RCTFontAttributes.m in Sources */,
58B512161A9E6EFF00147676 /* RCTText.m in Sources */,
599DF2641F03076D0079B53E /* RCTTextInput.m in Sources */,
1362F1011B4D51F400E06D8C /* RCTTextFieldManager.m in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions Libraries/Text/RCTTextField.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTFont.h>
#import <React/RCTUIManager.h>
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
Expand Down Expand Up @@ -40,6 +41,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
_backedTextInput = [[RCTUITextField alloc] initWithFrame:self.bounds];
_backedTextInput.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_backedTextInput.textInputDelegate = self;
_backedTextInput.font = self.fontAttributes.font;

[self addSubview:_backedTextInput];
}
Expand Down
22 changes: 6 additions & 16 deletions Libraries/Text/RCTTextFieldManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ - (UIView *)view

#pragma mark - Unified <TextInput> properties

RCT_REMAP_VIEW_PROPERTY(allowFontScaling, fontAttributes.allowFontScaling, BOOL)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, backedTextInputView.autocapitalizationType, UITextAutocapitalizationType)
RCT_REMAP_VIEW_PROPERTY(autoCorrect, backedTextInputView.autocorrectionType, UITextAutocorrectionType)
RCT_REMAP_VIEW_PROPERTY(color, backedTextInputView.textColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(editable, backedTextInputView.editable, BOOL)
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, backedTextInputView.enablesReturnKeyAutomatically, BOOL)
RCT_REMAP_VIEW_PROPERTY(fontSize, fontAttributes.fontSize, NSNumber)
RCT_REMAP_VIEW_PROPERTY(fontWeight, fontAttributes.fontWeight, NSString)
RCT_REMAP_VIEW_PROPERTY(fontStyle, fontAttributes.fontStyle, NSString)
RCT_REMAP_VIEW_PROPERTY(fontFamily, fontAttributes.fontFamily, NSString)
RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, backedTextInputView.keyboardAppearance, UIKeyboardAppearance)
RCT_REMAP_VIEW_PROPERTY(keyboardType, backedTextInputView.keyboardType, UIKeyboardType)
RCT_REMAP_VIEW_PROPERTY(placeholder, backedTextInputView.placeholder, NSString)
Expand All @@ -61,22 +66,7 @@ - (UIView *)view
RCT_REMAP_VIEW_PROPERTY(caretHidden, backedTextInputView.caretHidden, BOOL)
RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(fontSize, NSNumber, RCTTextField)
{
view.backedTextInputView.font = [RCTFont updateFont:view.backedTextInputView.font withSize:json ?: @(defaultView.backedTextInputView.font.pointSize)];
}
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, __unused RCTTextField)
{
view.backedTextInputView.font = [RCTFont updateFont:view.backedTextInputView.font withWeight:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, __unused RCTTextField)
{
view.backedTextInputView.font = [RCTFont updateFont:view.backedTextInputView.font withStyle:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextField)
{
view.backedTextInputView.font = [RCTFont updateFont:view.backedTextInputView.font withFamily:json ?: defaultView.backedTextInputView.font.familyName];
}

RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)

- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
Expand Down
8 changes: 7 additions & 1 deletion Libraries/Text/RCTTextInput.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
#import <React/RCTView.h>

#import "RCTBackedTextInputViewProtocol.h"
#import "RCTFontAttributes.h"
#import "RCTFontAttributesDelegate.h"

@class RCTBridge;
@class RCTEventDispatcher;
@class RCTTextSelection;

@interface RCTTextInput : RCTView {
@interface RCTTextInput : RCTView <RCTFontAttributesDelegate> {
@protected
RCTBridge *_bridge;
RCTEventDispatcher *_eventDispatcher;
Expand All @@ -41,12 +43,16 @@
@property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange;
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;

@property (nonatomic, readonly, strong) RCTFontAttributes *fontAttributes;

@property (nonatomic, assign) NSInteger mostRecentEventCount;
@property (nonatomic, assign) BOOL blurOnSubmit;
@property (nonatomic, assign) BOOL selectTextOnFocus;
@property (nonatomic, assign) BOOL clearTextOnFocus;
@property (nonatomic, copy) RCTTextSelection *selection;

- (void)setFont:(UIFont *)font;

- (void)invalidateContentSize;

// Temporary exposure of particial `RCTBackedTextInputDelegate` support.
Expand Down
16 changes: 15 additions & 1 deletion Libraries/Text/RCTTextInput.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@

#import "RCTTextInput.h"

#import <React/RCTAccessibilityManager.h>
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTUtils.h>
#import <React/RCTUIManager.h>
#import <React/RCTUtils.h>
#import <React/UIView+React.h>

#import "RCTTextSelection.h"
Expand All @@ -29,6 +30,8 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
if (self = [super initWithFrame:CGRectZero]) {
_bridge = bridge;
_eventDispatcher = bridge.eventDispatcher;
_fontAttributes = [[RCTFontAttributes alloc] initWithAccessibilityManager:bridge.accessibilityManager];
_fontAttributes.delegate = self;
}

return self;
Expand All @@ -44,6 +47,17 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
return nil;
}

- (void)setFont:(UIFont *)font
{
self.backedTextInputView.font = font;
[self invalidateContentSize];
}

- (void)fontAttributesDidChangeWithFont:(UIFont *)font
{
self.font = font;
}

#pragma mark - Properties

- (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets
Expand Down
3 changes: 2 additions & 1 deletion Libraries/Text/RCTTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTFont.h>
#import <React/RCTUIManager.h>
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
Expand Down Expand Up @@ -54,8 +55,8 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
_backedTextInput.scrollsToTop = NO;
#endif
_backedTextInput.scrollEnabled = YES;

_backedTextInput.textInputDelegate = self;
_backedTextInput.font = self.fontAttributes.font;

[self addSubview:_backedTextInput];
}
Expand Down
Loading

0 comments on commit 9c4ec30

Please sign in to comment.