maui
maui copied to clipboard
Entry control CursorPosition does not update on TextChanged event [iOS Maui 8.0.7]
Description
When typing into Entry control on iOS, the CursorPosition property is not updated. For example output when typing text "Test"
->T expectedPosition[1] actualPosition[0]
T->Te expectedPosition[2] actualPosition[0]
Te->Tes expectedPosition[3] actualPosition[0]
Tes->Test expectedPosition[4] actualPosition[0]
Steps to Reproduce
- Add Entry control with TextChanged event
<Entry TextChanged="Entry_TextChanged" />
- Add TextChanged event handler
void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
Entry entry = sender as Entry;
#if IOS
if (entry?.Handler?.PlatformView is UIKit.UITextField entryTextField)
{
var position = entryTextField.GetOffsetFromPosition(entryTextField.BeginningOfDocument, entryTextField.SelectedTextRange.Start);
Console.WriteLine($"'{e.OldTextValue}'->'{e.NewTextValue}' expectedPosition=[{position}] actualPosition=[{entry.CursorPosition}]");
}
#endif
}
- Observe actualPosition (entry.CursorPosition) does not get updated, compared to native control
Link to public reproduction project repository
No response
Version with bug
8.0.7 SR2
Is this a regression from previous behavior?
Yes, this used to work in .NET MAUI
Last version that worked well
Unknown/Other
Affected platforms
iOS
Affected platform versions
No response
Did you find any workaround?
Use position from native UITextField control.
Relevant log output
No response
Can confirm something strange is going on. I get this error: (however, not every time I enter text). I don't know if the error is directly related, or if it somehow interfers with the cursor position update.
2024-02-29 11:09:29.589485+0100 MauiButtons[31806:18792457] [Unknown process name] Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem.
Other behavior that i observed: The cursor value is updated if one moves the cursor a inside the text and either types or deletes. The position is still incorrect though.
I took a look at the EntryHandler git history and found the commit that added the SelectionChanged. I looked through the commits trying to get a better understanding for how the cursor update code had changed.
From what I can tell, the UpdateText in TextExtensions used to update the cursor position. At first the direct call to a method that updates the position was used.
textView.UpdateCursorPosition(oldText, newText, currentCursorPosition);
and later with
var cursorPosition = textView.GetCursorPosition(cursorOffset);
// other code
((ITextInput)inputView).CursorPosition = cursorPosition;
which even later was replaced with
textView.SetTextRange(cursorPosition, 0);
SetTextRange sets the platformView.SelectedTextRange which in turn is overridden in the MauiTextField class and in its setter the SelectionChanged handler is called which in turn updates the position. So I guess the question is wether we want to set the position directly, or let that handler handle it. The code history suggests both options have been used in the UpdateText method. (Side note, wonder why the tests didn't pick this up?)
I'll update this post with screenshots so it all makes sense, I just want to confirm that I've understood the flow correctly.
I believe, and I could be very wrong, that the condition that was added later, that checked if the new text was transformed would skip the part that would update the cursor. Unless transformed, the old and new text was always the same.
Adding the else, with a previously removed line, updates the cursor:
static void UpdateText(this IUITextInput textInput, InputView inputView, bool isEditing)
{
// Setting the text causes the cursor to be reset to the end of the IUITextInput.
// So, let's set back the cursor to the last known position and calculate a new
// position if needed when the text was modified by a Converter.
var textRange = textInput.GetTextRange(textInput.BeginningOfDocument, textInput.EndOfDocument);
var oldText = textInput.TextInRange(textRange) ?? string.Empty;
var newText = TextTransformUtilites.GetTransformedText(
inputView?.Text,
textInput.GetSecureTextEntry() ? TextTransform.Default : inputView.TextTransform
);
if (oldText != newText)
{
// Re-calculate the cursor offset position if the text was modified by a Converter.
// but if the text is being set by code, let's just move the cursor to the end.
var cursorOffset = newText.Length - oldText.Length;
var cursorPosition = isEditing ? textInput.GetCursorPosition(cursorOffset) : newText.Length;
textInput.ReplaceText(textRange, newText);
textInput.SetTextRange(cursorPosition, 0);
}
else
{
// This works. But I'm not sure if this is the way.
((ITextInput)inputView).CursorPosition = textInput.GetCursorPosition();
}
}
I'm not able to do a PR right now, and frankly I'm not confident in my solution/findings. But hope this helps nonetheless. I'll take another look and maybe put together a PR later.
Do you know what version this used to work on?
Verified this issue with Visual Studio 17.10.0 Preview 2. Can repro on iOS platform.
Can confirm this very annoying behavior on MacCatalyst (MAUI 8.0.10) as well.
As a workaround, I add this line of code to @jurijr's event handler:
entry.CursorPosition = (int)position;
This way, I can at least move on until this is fixed.