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

TextLayout: take full width if text wrapped #47435

Closed
wants to merge 3 commits into from

Conversation

s77rt
Copy link
Contributor

@s77rt s77rt commented Nov 5, 2024

Summary:

Problem

The calculated width for a multiline text is based on the longest line. However it does not account for text that wraps.

Example if numberOfLines=1 and the text wraps

+---------------------------+
This is a long text that
will wrap
+---------------------------+

The TextView will render

+---------------------------+
This is a long text t...
+---------------------------+

because the calculatedWidth took the width of the first line.

Also see #41770 (comment) for additional context.

Solution

If the text wraps, take the whole width.

+---------------------------+
This is a long text that w...
+---------------------------+

Fixes #39722
Fixes facebook/yoga#1730

Changelog:

[GENERAL] [FIXED] - Fix text not taking full width

Test Plan:

        <Text
          numberOfLines={1}
          style={{
            backgroundColor: 'red',
            alignSelf: 'flex-start',
            color: 'white',
            fontSize: 34,
          }}>
          {'This is a long text that will wrap.'}
        </Text>
        <Text
          numberOfLines={3}
          style={{
            backgroundColor: 'red',
            alignSelf: 'flex-start',
            color: 'white',
            fontSize: 34,
          }}>
          {
            '1\n\ntest\nThis is a long text that will wrap.This is a long text that will wrap.This is a long text that will wrap.\n'
          }
        </Text>
        <Text
          numberOfLines={3}
          style={{
            backgroundColor: 'red',
            alignSelf: 'flex-start',
            color: 'white',
            fontSize: 34,
          }}>
          {
            '1\n\nThis is a long text that will wrap.This is a long text that will wrap.This is a long text that will wrap.\n'
          }
        </Text>
  1. Verify that the first and third text take full width
  2. Verify that the second text does not take full width
Before After
Screenshot 2024-11-05 at 9 00 24 PM Screenshot 2024-11-05 at 9 01 49 PM

@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. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Nov 5, 2024
@s77rt
Copy link
Contributor Author

s77rt commented Nov 5, 2024

cc @cipolleschi or @realsoelynn You may want to review this since you have the context from #41770

@facebook-github-bot
Copy link
Contributor

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

@NickGerleman
Copy link
Contributor

NickGerleman commented Nov 6, 2024

That also likely fixes issue reported here. facebook/yoga#1730

I think we may want this behavior regardless of max number of lines. Wrapping when we have AtMost constraint should be changed on iOS/Android to take full available width.

@facebook-github-bot
Copy link
Contributor

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

@s77rt
Copy link
Contributor Author

s77rt commented Nov 7, 2024

@NickGerleman It won't fix the other issue. That one is in yoga. I will look into it as well

@s77rt
Copy link
Contributor Author

s77rt commented Nov 7, 2024

@realsoelynn I see a failing internal test. Does this require any action from my side?

@NickGerleman
Copy link
Contributor

@s77rt @realsoelynn the other issue was reported to Yoga, but I highly suspect it is RN measure function issue related to what this change is addressing.

@s77rt s77rt marked this pull request as draft November 8, 2024 08:53
@s77rt
Copy link
Contributor Author

s77rt commented Nov 8, 2024

Making this a draft, will work on what @NickGerleman suggested. It should fix both this issue and the other one

@s77rt s77rt marked this pull request as ready for review November 8, 2024 13:39
@s77rt
Copy link
Contributor Author

s77rt commented Nov 8, 2024

iOS Android
Screenshot 2024-11-08 at 2 38 22 PM Screenshot 2024-11-08 at 2 38 21 PM

@s77rt
Copy link
Contributor Author

s77rt commented Nov 8, 2024

Ready for review. cc @realsoelynn @NickGerleman

@s77rt s77rt changed the title TextLayout: take full width if text wrapped beyond numberOfLines TextLayout: take full width if text wrapped Nov 8, 2024
@cipolleschi
Copy link
Contributor

FYI: @NickGerleman is on PTO this week. He should be back next week

@facebook-github-bot
Copy link
Contributor

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

Copy link
Contributor

@NickGerleman NickGerleman left a comment

Choose a reason for hiding this comment

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

Back from PTO! Took a look, and I think we might need to handle max-content differently.

Comment on lines +664 to +667
if (!endsWithNewLine && lineIndex + 1 < layout.getLineCount()) {
calculatedWidth = width;
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we may also reach this path when widthYogaMeasureMode is YogaMeasureMode.UNDEFINED, in which case we are asking for max-content size, and the width is NaN.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think on L604, we could replace:

if (widthYogaMeasureMode == YogaMeasureMode.AT_MOST && calculatedWidth > width)

with

if (widthYogaMeasureMode == YogaMeasureMode.AT_MOST && (calculatedWidth > width || layout.getLineCount() > 1))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we may also reach this path when widthYogaMeasureMode is YogaMeasureMode.UNDEFINED

In this case, should we keep the calculated width as the max width of the lines?

I think on L604, we could replace

We set calculatedWidth = width only if the text wraps due to overflow. If the user write 2 short lines we still use the width of the maximum line

CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;

if (textDidWrap) {
size.width = textContainer.size.width;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we may have similar case to the above, for measuring max-content, where we don't want to set to size. I think in this case, it looks like that might be represented as Infinity layout constraint which makes its way to the NSTextContainer.

NSUInteger lastCharacterIndex = range.location + range.length - 1;
BOOL endsWithNewLine =
[textStorage.string characterAtIndex:lastCharacterIndex] == '\n';
if (!endsWithNewLine && textStorage.string.length > lastCharacterIndex + 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this correctly detect wrapping for something like the below?

Hello\n
World\n

I think we may be able to just count lines. https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html#//apple_ref/doc/uid/20001810-CJBGBIBB

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, that's not considered text wrap. In this case we want the current behaviour (text width) otherwise we'd break case 2 from #47435 (comment)

Copy link
Contributor

@NickGerleman NickGerleman left a comment

Choose a reason for hiding this comment

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

Ah, you are correct about only wanting to do this when we need to break due to wrapping.

That also solves the max-content case, since we will never wrap in this case since we have unconstrained width.

So I think this is good!

@facebook-github-bot
Copy link
Contributor

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

@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @s77rt in 550b0c0

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label Nov 21, 2024
@facebook-github-bot
Copy link
Contributor

@realsoelynn merged this pull request in 550b0c0.

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. Merged This PR has been merged. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
5 participants