diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue20911.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue20911.cs new file mode 100644 index 000000000000..381f261b7263 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue20911.cs @@ -0,0 +1,41 @@ +namespace Controls.TestCases.HostApp.Issues; + +[Issue(IssueTracker.Github, 20911, "Updating text in the Entry does not update CursorPosition during the TextChanged event", PlatformAffected.iOS)] +public class Issue20911 : ContentPage +{ + Entry entry; + Label cursorPositonStatusLabel; + + public Issue20911() + { + entry = new Entry + { + AutomationId = "ValidateEntryCursorPosition", + }; + + cursorPositonStatusLabel = new Label + { + AutomationId = "CursorPositionStatusLabel", + Text = "Cursor Position: 0", + FontSize = 16, + }; + + Button button = new Button + { + AutomationId = "ValidateEntryCursorPositionBtn", + Text = "Validate Entry Cursor Position", + }; + + button.Clicked += (sender, e) => + { + cursorPositonStatusLabel.Text = $"{entry.CursorPosition}"; + }; + + Content = new VerticalStackLayout + { + Padding = new Thickness(20), + Spacing = 10, + Children = { entry, cursorPositonStatusLabel, button } + }; + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue20911.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue20911.cs new file mode 100644 index 000000000000..c414b8e966a3 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue20911.cs @@ -0,0 +1,29 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue20911 : _IssuesUITest +{ + public Issue20911(TestDevice device) : base(device) + { + } + + public override string Issue => "Updating text in the Entry does not update CursorPosition during the TextChanged event"; + + [Test] + [Category(UITestCategories.Entry)] + public void VerifyEntryCursorPositionOnTextChanged() + { + App.WaitForElement("ValidateEntryCursorPosition"); + + App.EnterText("ValidateEntryCursorPosition", "Test"); + + App.WaitForElement("ValidateEntryCursorPositionBtn"); + App.Tap("ValidateEntryCursorPositionBtn"); + + var cursorPositionStatus = App.FindElement("CursorPositionStatusLabel").GetText(); + Assert.That(cursorPositionStatus, Is.EqualTo("4")); + } +} \ No newline at end of file diff --git a/src/Core/src/Core/Extensions/ITextInputExtensions.cs b/src/Core/src/Core/Extensions/ITextInputExtensions.cs index 1a708f78aa86..c733c1174bab 100644 --- a/src/Core/src/Core/Extensions/ITextInputExtensions.cs +++ b/src/Core/src/Core/Extensions/ITextInputExtensions.cs @@ -50,6 +50,14 @@ public static bool TextWithinMaxLength(this ITextInput textInput, string? text, return shouldChange; } + + internal static void UpdateCursorPosition(this ITextInput textInput, int cursorPosition) + { + if (textInput.CursorPosition != cursorPosition) + { + textInput.CursorPosition = cursorPosition; + } + } #endif #if ANDROID diff --git a/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs b/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs index e6e2030716c8..ae11b863557a 100644 --- a/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs +++ b/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs @@ -190,12 +190,16 @@ void OnEditingBegan(object? sender, EventArgs e) void OnEditingChanged(object? sender, EventArgs e) { - if (sender is MauiTextField platformView) + if (sender is MauiTextField platformView && VirtualView is not null) { - VirtualView?.UpdateText(platformView.Text); - } - } + // Update cursor position before updating text so that when TextChanged event fires, + // the CursorPosition property reflects the current native cursor position + VirtualView.UpdateCursorPosition(platformView.GetCursorPosition()); + VirtualView.UpdateText(platformView.Text); + } + } + void OnEditingEnded(object? sender, EventArgs e) { if (sender is MauiTextField platformView && VirtualView is IEntry virtualView)