Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@hellohuanlin
Copy link
Contributor

@hellohuanlin hellohuanlin commented Oct 5, 2023

According to Apple's API doc, the line range enclosing position should return nil if no such range. Since endOfDocument is exclusive, we should return nil when UIKit queries the line range enclosing it.

Note that endOfDocument with forward affinity is still queried with "delete line" voice control command, which we should still return the range of the last line.

It is unclear why iOS 17 starts to query this API, but both issues resolved seems to be related to auto-correction.

More discussion can be found in the design doc.

List which issues are fixed by this PR. You must list at least one issue.

Fixes flutter/flutter#134716

Fixes flutter/flutter#132594

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@hellohuanlin hellohuanlin force-pushed the text_input_freeze_switch_language branch from ae7db18 to 9cb0af7 Compare October 5, 2023 18:58
@hellohuanlin hellohuanlin changed the title [DRAFT][ios17][text_input]fix chinese language switch freeze, and missing auto correction menu in iOS 17 [ios17][text_input]fix chinese language switch freeze, and missing auto correction menu in iOS 17 Oct 6, 2023
@hellohuanlin hellohuanlin marked this pull request as ready for review October 6, 2023 17:40
// https://github.com/flutter/flutter/issues/134716
// 2. Auto correction candidate menu does not show up in iOS 17:
// https://github.com/flutter/flutter/issues/132594
// The end of document with forward affinity should still return the last line, which is used
Copy link
Contributor

Choose a reason for hiding this comment

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

What's endOfDocument supposed to return? If my text is abcd then with the current implementation endOfDocument is (4, backward), so beginningOfDocument and endOfDocument form a closed interval that describes valid cursor positions within the document?

If that's the case shouldn't we return nil when position is greater than (4, backward), and still return a non-nil value when it's (4, backward)?

Copy link
Contributor

Choose a reason for hiding this comment

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

According to this comment,

// This is an upstream position
closestPosition = [FlutterTextPosition positionWithIndex:position
affinity:UITextStorageDirectionBackward];

backward translates to TextAffinity.upstream. So beginningOfDocument and endOfDocument is a close interval in the current implementation. Returning nil for endOfDocument doesn't seem right (unless endOfDocument should be made exclusive).

// TODO(hellohuanlin): Remove iOS 17 check. The same logic should apply to older versions too.
if (@available(iOS 17.0, *)) {
// The end of document (with backward affinity) should not be part of any line, since end is
// exclusive. This is to fix 2 bugs:
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong Oct 6, 2023

Choose a reason for hiding this comment

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

@chunhtai is "backward" equivalent to TextAffinity.upstream? If so I guess the "end is exclusive" statement isn't true?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes in flutter we use upstream and downstream to describe text affinity, and the upstream afinity is considered inclusive. not sure if they have different meaning or treated differently in iOS

// TODO(hellohuanlin): Remove iOS 17 check. The same logic should apply to older versions too.
if (@available(iOS 17.0, *)) {
// The end of document (with backward affinity) should not be part of any line, since end is
// exclusive. This is to fix 2 bugs:
Copy link
Contributor

Choose a reason for hiding this comment

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

yes in flutter we use upstream and downstream to describe text affinity, and the upstream afinity is considered inclusive. not sure if they have different meaning or treated differently in iOS

// The end of document with forward affinity should still return the last line, which is used
// by voice control's delete line command.
if ([position isEqual:[_textInputView endOfDocument]]) {
return nil;
Copy link
Contributor

Choose a reason for hiding this comment

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

What would happen if this returns the last line?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we return the last line, we will see the 2 bugs linked in my description.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, if endOfDocument is exclusive and does return (text.length, upstream), then it is contradicting with how flutter handle text position.

Copy link
Contributor

Choose a reason for hiding this comment

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

(text.length, downstream) is a valid cursor position as well. In most cases (text.length, upstream) and (text.length, downstream) refer to the same cursor positoin, but when there's bidi text at the end of the text they refer to different locations.

Copy link
Contributor

Choose a reason for hiding this comment

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

I tried in UITextField. It's returning 7F for a string that's 7 code units long. So it's inclusive I think.

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 can play around with it more tomorrow, but afaik apple's sample project uses exclusive endOfDocument. (It does not use affinity tho)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah cursor position and text position have different end positions. I'm not sure which one UITextField is using.

// The end of document with forward affinity should still return the last line, which is used
// by voice control's delete line command.
if ([position isEqual:[_textInputView endOfDocument]]) {
return nil;
Copy link
Contributor

Choose a reason for hiding this comment

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

I see, if endOfDocument is exclusive and does return (text.length, upstream), then it is contradicting with how flutter handle text position.

- (UITextRange*)lineEnclosingPosition:(UITextPosition*)position {
// TODO(hellohuanlin): Remove iOS 17 check. The same logic should apply to older versions too.
if (@available(iOS 17.0, *)) {
// The end of document (with backward affinity) should not be part of any line, since end is
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe mention this is iOS expectation, since in flutter (text.length, TextAffinity.upstream) is still considered pointing to the last character due to gesture reason. e.g., we need to tell the difference between user hitting the last half of the last character vs outside of the last character to the right.

@hellohuanlin hellohuanlin force-pushed the text_input_freeze_switch_language branch from 9cb0af7 to a0ad92d Compare October 9, 2023 18:20
@WithToken
Copy link

I found that I couldn't reproduce this problem on iOS17.1.

@castleche
Copy link

Not repro on iOS 17.1 beta 3.
@hellohuanlin Please make sure your fix code works fine on iOS 17.1.

@chinmaygarde
Copy link
Member

Are we making progress on this?

@hellohuanlin
Copy link
Contributor Author

Are we making progress on this?

Yes, still working on it

@hellohuanlin
Copy link
Contributor Author

Closing this in favor of #47566

auto-submit bot pushed a commit that referenced this pull request Nov 14, 2023
…ges (without relying on text affinity) (#47566)

After close examination of the UIKit's default string tokenizer, when querying the line enclosing the end of doc position **in forward direction**, we should return nil (regardless whether the position is forward or backward affinity). 

This aligns with the [API doc](https://developer.apple.com/documentation/uikit/uitextinputtokenizer/1614464-rangeenclosingposition?language=objc): 

> If the text position is at a text-unit boundary, it is considered enclosed only if the next position in the given direction is entirely enclosed.

Will cherry pick this soon. Otherwise it will be less and less important as users upgrade to iOS 17.1. 

### Why my previous workaround also works? 

It turns out my previous workaround PR #46591 works only because our misuse of text affinity in our text input. Specifically, when adding text affinity support, we only added it to `FlutterTextPosition`, but not `FlutterTextRange`. So when getting the beginning/end position from the range, we assign arbitrary affinities. 

*List which issues are fixed by this PR. You must list at least one issue.*

#46591

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
hellohuanlin added a commit to hellohuanlin/engine that referenced this pull request Nov 14, 2023
…ges (without relying on text affinity) (flutter#47566)

After close examination of the UIKit's default string tokenizer, when querying the line enclosing the end of doc position **in forward direction**, we should return nil (regardless whether the position is forward or backward affinity). 

This aligns with the [API doc](https://developer.apple.com/documentation/uikit/uitextinputtokenizer/1614464-rangeenclosingposition?language=objc): 

> If the text position is at a text-unit boundary, it is considered enclosed only if the next position in the given direction is entirely enclosed.

Will cherry pick this soon. Otherwise it will be less and less important as users upgrade to iOS 17.1. 

### Why my previous workaround also works? 

It turns out my previous workaround PR flutter#46591 works only because our misuse of text affinity in our text input. Specifically, when adding text affinity support, we only added it to `FlutterTextPosition`, but not `FlutterTextRange`. So when getting the beginning/end position from the range, we assign arbitrary affinities. 

*List which issues are fixed by this PR. You must list at least one issue.*

flutter#46591

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
hellohuanlin added a commit to hellohuanlin/engine that referenced this pull request Nov 14, 2023
…ges (without relying on text affinity) (flutter#47566)

After close examination of the UIKit's default string tokenizer, when querying the line enclosing the end of doc position **in forward direction**, we should return nil (regardless whether the position is forward or backward affinity). 

This aligns with the [API doc](https://developer.apple.com/documentation/uikit/uitextinputtokenizer/1614464-rangeenclosingposition?language=objc): 

> If the text position is at a text-unit boundary, it is considered enclosed only if the next position in the given direction is entirely enclosed.

Will cherry pick this soon. Otherwise it will be less and less important as users upgrade to iOS 17.1. 

### Why my previous workaround also works? 

It turns out my previous workaround PR flutter#46591 works only because our misuse of text affinity in our text input. Specifically, when adding text affinity support, we only added it to `FlutterTextPosition`, but not `FlutterTextRange`. So when getting the beginning/end position from the range, we assign arbitrary affinities. 

*List which issues are fixed by this PR. You must list at least one issue.*

flutter#46591

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

7 participants