Iris Classon
Iris Classon - In Love with Code

Debugging MAUI for Beginners and the Erroneous Cursor Position

On to another MAUI bug post, this time for beginners that would like to get to know the platform better. If it seems like I write a lot of defects, AKA bugs, it’s probably because I work with MAUI all day at work, and then some after work. I like MAUI, and on most days it likes me. I subscribe to the repo and I like to keep an eye on issues. Today a new issue was submitted, regarding the cursor position in the Entry element.

The issue shows the problem (provided by the user who submitted the bug).

Although typing in the Entry field results in the right position from the native code, the entry.CursorPosition returns the wrong position.

I ran the code, and now and then would even get an error, which I couldn’t say for sure was due to the bug. Playing around I also noticed that the cursor position would be update if you moved the cursor to inside the text, and then made a change (adding or deleting) but the position was still wrong.

Let’s find out why!

I’ll cover repo code search in a different post, so for this one we will be debugging the source code. If you want to debug along, here are the steps.

Assumptions: You have your local environments set up for creating and building MAUI apps, and it’s up to date.

Fork the repository, and clone the fork with the usual git clone

Open in your editor, for me this would be Rider which is what my team has decided on using. I use VS Code for a lot of private projects, but for this post let’s use Rider.

Open the solution and make sure to select the right solution as there are quite a few to choose between. Now build. This will probably take some time, in particular if you use Rider (seems to be RAM hungry).

When you’ve confirmed everything builds, add a new project, called Ui something, which we’ll use to test the Entry control. Build the new project, open packages and remove the Maui.Controls package. We will replace them with a direct reference in the solution. Do a Clean, and a new build.

Add the code from earlier in the new project and set a breakpoint in the UpdateCursorPosition method in the TextFieldExtensions for iOS.

Deploy to the iOS simulator.

Type in something in the Entry. Breakpoint is not being hit. Move cursor to the left, and type. BAM! We hit the breakpoint. In other words, it didn’t update when we were writing.

You should be able to see in the stacktrace the flow through the source code. In the SelectionChanged method in the EntryHandler class we can see the position being changed. However, this method isn’t triggered when new text is being typed.

I tested, just for fun, what would happen if I added a cursor update in the OnEditedChanged as well, and aha! The cursor is now updated to its position, but that doesn’t explain how the bug came in to existence to begin with.

What’s next? Definitely not a pull request yet. I can’t say that I understand the flow, so this needs more love. But we can look at older pull requests and try to identify the PR that broke this behavior. A little side note, what we could do is create a test that fails so we or someone on the team has a good starting point. Anyway, 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.

Debugging MAUI for Beginners and the Erroneous Cursor Position

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);
…
 ((ITextInput)inputView).CursorPosition = cursorPosition;

which even later was replaced with

textView.SetTextRange(cursorPosition, 0);

SetTextRange sets the platformView.SelectedTextRange

Which in turn is overridden by MauiTextField and in the setter the famous SelectionChanged handler is called.

I’ll update this post with screenshots so it all makes sense, I just want to confirm that I’ve understyood the flow correctly.

SO what happened?

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.

Debugging MAUI for Beginners and the Erroneous Cursor Position
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();
    }
}

Comments

Leave a comment below, or by email.

Last modified on 2024-02-29

comments powered by Disqus