-
Notifications
You must be signed in to change notification settings - Fork 6k
[ios17][text_input]fix chinese language switch freeze, and missing auto correction menu in iOS 17 #46591
[ios17][text_input]fix chinese language switch freeze, and missing auto correction menu in iOS 17 #46591
Conversation
ae7db18 to
9cb0af7
Compare
shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
Outdated
Show resolved
Hide resolved
shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
Outdated
Show resolved
Hide resolved
shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
Outdated
Show resolved
Hide resolved
shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
Outdated
Show resolved
Hide resolved
shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
Outdated
Show resolved
Hide resolved
| // 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 |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to this comment,
engine/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
Lines 1892 to 1894 in b28032c
| // 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: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
…to correction menu on iOS 17"
9cb0af7 to
a0ad92d
Compare
|
I found that I couldn't reproduce this problem on iOS17.1. |
|
Not repro on iOS 17.1 beta 3. |
|
Are we making progress on this? |
Yes, still working on it |
|
Closing this in favor of #47566 |
…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
…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
…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
According to Apple's API doc, the line range enclosing position should return
nilif no such range. SinceendOfDocumentis exclusive, we should returnnilwhen UIKit queries the line range enclosing it.Note that
endOfDocumentwith 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
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.