Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS: Support allowFontScaling on TextInput #14030

Closed

Conversation

rigdern
Copy link
Contributor

@rigdern rigdern commented May 18, 2017

Motivation (required)

Currently, only Text supports the allowFontScaling prop. This commit adds support for it on TextInput.

Notes

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.

Test Plan (required)

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.

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. GH Review: review-needed labels May 18, 2017
@jacobp100
Copy link
Contributor

It would be good if we could have support for font variants. You can get a font variant from RCTFontVariantDescriptor!

@shergin shergin self-requested a review May 26, 2017 23:44
@shergin shergin added the Platform: iOS iOS applications. label May 26, 2017
@rigdern rigdern force-pushed the rigdern/ios-TextInput-allowFontScaling branch from c28b4af to 5cbd062 Compare June 18, 2017 00:34
@shergin
Copy link
Contributor

shergin commented Jun 23, 2017

(t19597801)

@shergin
Copy link
Contributor

shergin commented Jun 23, 2017

@rigdern That's amazing! Thank you! Right now I am in the middle of huge refactoring of <TextInput>, so I will be able to merge it only after the refactoring is done. I also plan to decouple all font-related logic into separate class, so we can keep managers and view classes with RN specific data-flow logic as simple as possible.

@rigdern
Copy link
Contributor Author

rigdern commented Aug 28, 2017

@shergin has your TextInput refactoring been merged to master?

@shergin
Copy link
Contributor

shergin commented Aug 29, 2017

Yes, kind of. I merged all of what I had iOS TextInput related.

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: facebook#13730, facebook#12738, facebook#2140, facebook#8533.
@rigdern rigdern force-pushed the rigdern/ios-TextInput-allowFontScaling branch from 5cbd062 to 4268fba Compare August 31, 2017 07:13
@pull-bot
Copy link

pull-bot commented Aug 31, 2017

Attention: @shergin

Generated by 🚫 dangerJS

Copy link
Contributor

@shergin shergin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the direction of this approach.
But here is how we can do it even better:

  • Make a font RCTTextInput's prop.
  • Create a new class, something like RCTFontAttributes (We already have RCTFontDescriptor, we have to also be sure that we are not reinventing this.)
  • RCTFontAttributes should have all font specific props; getter which returns UIFont instance; a delegate with one method fontAttributesDidChangeWithFont:
  • Have NSNotificationCenter related stuff inside that class;
  • Add a prop to RCTTextInput fontAttributes;
  • Make a RCTTextInput a delegate and implement that method as self.font = font;;
  • Redirect all font specific props in ViewManager to view.fontAttributes.xxxx.

And then later, finally, we can reuse it inside RCTText and co.

@rigdern
Copy link
Contributor Author

rigdern commented Sep 7, 2017

@shergin I didn't have time to test it yet but is my latest commit along the lines that you were thinking?

A tricky detail was that RCTTextInput can't set font in its constructor because that would cause RCTTextView to attempt to set font on _backedTextInput before _backedTextInput was created. Instead, RCTTextInput subclasses manually set font in their constructors (the previous implementation had a similar problem but I didn't notice).

Copy link
Contributor

@shergin shergin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, yes, yes.

@@ -27,8 +28,10 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
RCTAssertParam(bridge);

if (self = [super initWithFrame:CGRectZero]) {
_bridge = bridge;
_bridge = bridge;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style.

_eventDispatcher = bridge.eventDispatcher;
_fontAttributes = [[RCTFontAttributes alloc ] initWithAccessibilityManager:bridge.accessibilityManager];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style.

@@ -70,6 +73,18 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
return _backedTextInput;
}

- (void)fontAttributesDidChangeWithFont:(RCTFont *)font
{
[super fontAttributesDidChangeWithFont:fonte];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fonte? French? 😄

@@ -35,6 +35,7 @@ - (UIView *)view

#pragma mark - Unified <TextInput> properties

RCT_REMAP_VIEW_PROPERTY(allowFontScaling, fontAttributes.allowFontScaling, BOOL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have to remap other font-related props like this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What props are you talking about? What should they be remapped to? It looks like you are highlighting allowFontScaling which is already getting remapped to fontAttributes.

@@ -66,19 +67,19 @@ - (UIView *)view

RCT_CUSTOM_VIEW_PROPERTY(fontSize, NSNumber, RCTTextView)
{
view.font = [RCTFont updateFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
view.fontAttributes.fontSize = json ?: @(defaultView.font.pointSize);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and get rid of all custom imperative logic inside ViewManager.
We don't really need defaultView here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shergin Are you saying we should default to passing nil to fontAttributes properties instead of forwarding whatever was in defaultView?

I think that will change the behavior but at least now things will be more consistent. For example, today I think if you don't specify fontSize during the initial render, the rendered fontSize will be RCTFont's default. If you later set the fontSize to 20 and then back to nil, I think it'll fallback to a default of defaultView.font.pointSize which is not necessarily the same as RCTFont's default so the font could be a different size now than it was when it was initially rendered.

@@ -41,6 +43,8 @@
@property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange;
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instad of having real prop, we probably want to have only setter which will redirect the value to backedTextInputView.font. (Also we have to add font to BackedTextInputProtocol if it does not have it already.)

Adam Comella added 3 commits September 21, 2017 10:11
I think this will change the behavior but at least now things will be more consistent. For example, today I think if you don't specify fontSize during the initial render, the rendered fontSize will be RCTFont's default. If you later set the fontSize to 20 and then back to nil, I think it'll fallback to a default of defaultView.font.pointSize which is not necessarily the same as RCTFont's default so the font could be a different size now than it was when it was initially rendered.
@rigdern
Copy link
Contributor Author

rigdern commented Sep 21, 2017

@shergin Sorry for the delay. I pushed some changes that address your feedback.

Copy link
Contributor

@shergin shergin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️
I wll try to land this in the begginnig of the week!
Thank you so much for your effort!

@@ -57,6 +58,8 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge

_backedTextInput.textInputDelegate = self;

self.font = self.fontAttributes.font;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ensures that _backedTextInput's font gets initialized to RCTFontAttributes's default font. This is not necessarily the same as _backedTextInput's default font.

@@ -70,6 +73,18 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
return _backedTextInput;
}

- (void)fontAttributesDidChangeWithFont:(UIFont *)font
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(We can consider moving this to superclass.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This overrides the superclass's implementation of fontAttributesDidChangeWithFont. Are you suggesting I move this logic to the superclass's implementation so RCTTextView doesn't have to override it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. invalidateContentSize does not exist in base class, right? I think we can also move this method to the base class (even with possibly empty implementation).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This does not block landing. We can do it in the future.)

@facebook-github-bot facebook-github-bot added the Import Started This pull request has been imported. This does not imply the PR has been approved. label Sep 24, 2017
@facebook-github-bot
Copy link
Contributor

@shergin has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@rigdern rigdern deleted the rigdern/ios-TextInput-allowFontScaling branch November 12, 2017 02:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Import Started This pull request has been imported. This does not imply the PR has been approved. Platform: iOS iOS applications.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants