Xamarin.forms: [Bug] iOS: Editor is not scrollable when initialized with long text

Created on 13 Feb 2020  路  20Comments  路  Source: xamarin/Xamarin.Forms

Description

On iOS, when an Editor is initialized (programatically) with a Text value that exceeds the bounds of the input, the text area is not scrollable as it should be. This is not the case when text is entered manually.

Steps to Reproduce

  1. Construct a new Editor instance with an explicit HeightRequest
  2. Set the Text property to a lengthy value (either a multi-line string or a very long single-line string that wraps)
  3. Add the Editor to the view hierarchy within the constructor of a ContentPage
  4. Observe that the UITextView that is rendered cannot be scrolled to view/edit all the text

Expected Behavior

Editor should always be scrollable when the text exceeds the bounds of the text input.

Actual Behavior

  • When long text is entered manually, scrolling works as expected.
  • When the Text property is set _after_ the Editor has been added to the page, scrolling works as expected.
  • When the Text property is set _before_ the Editor has been added to the page, scrolling does not work.

Basic Information

  • Version with issue: 4.2.0.848062, 4.4.0.991640
  • Last known good version: ?
  • Platform Target Frameworks:

    • iOS: 13.6.0.12

    • Android: n/a

    • UWP: n/a

  • Affected Devices: iPhone X, iOS 13.3.1

Reproduction Link

Editor Scrolling Bug -- Example Code

Workaround

  1. Placing the cursor in the text area and entering a newline causes the Editor to become scrollable.
  2. Calling Device.BeginInvokeOnMainThread(() => editor.Text = "...") to "defer" the setting of the text causes the Editor to become scrollable. (this seems to work _sometimes_)
editor 2 high impact iOS 馃崕 bug

Most helpful comment

Ok workaround.
Before I was adding a "\n " and removing it after a delay to make it work. It seems that no longer works. Now I have to

  1. Implement the Custom Renderer mentioned above. Only doing this step cause the issue to come back as soon as you type something in the Editor.
  2. Add "\n\n " and remove it later. More stupid logic sigh...

` private async void OnXxamNotesExpandedViewEditor_SizeChanged(object sender, EventArgs e)
{
string strCurrentEditorText = xxamNotesExpandedViewEditor.Text;

        if (Device.RuntimePlatform == Device.iOS && !xxamNotesExpandedViewEditor.IsReadOnly)
        {
            xxamNotesExpandedViewEditor.Text = strCurrentEditorText + "\n \n ";

                await Task.Delay(200);
                xxamNotesExpandedViewEditor.Text = strCurrentEditorText;
        }
    }`

All 20 comments

I can confirm I have exactly the same problem.
Works fine with 4.2.0.910310 and lower versions.
Contains error in 4.3.0.908675 and higher versions.

Tested on iOS 13.3/iPhone 8.
Tested with brand new Xamarin.Forms project:

  • NETStandard.Library: v2.0.3

I'm having same problem.
Here is more description of what I'm seeing.

  1. I set height and width of editor.
  2. When user types in editor and it goes to second row the editor scrolls left and right (it shouldn't do that).
  3. If user continues to type and fills the editor without hitting the return. The editor never scrolls up and down and user does not see what he is typing.
  4. If text is placed into the editor that fills more than the set height and width. It never scrolls up and down and user can not get to the end of his text. Unless he enters a return then the editor will scroll up and down.

This is a big issue in our app. Where are you at in getting a fix?

I am seeing the same thing.

Additional learnings:

  1. I can confirm that typing an Enter or placing a \n via code does resolve the issue. This causes headaches for us and I can't figure out how to inject the char in a way that doesn't create more problems for clients.
  2. If you add a \n and take it away on focus in, the editor will behave until enough text is entered to cause another wrap within the editor (might explain the 'sometimes' fix above). I'm wrapping the changes with "await Task.Delay(500);". This gets us closer to a workaround, but not yet there.
  3. If you roll back to Xamarin Forms 4.2, the horizontal scroll goes away, but the vertical scroll still does not work.

I have read elsewhere (https://github.com/xamarin/Xamarin.Forms/issues/8569) that the issue is a bug in the placeholder text. Based on what I am seeing this makes sense. On newer versions of Forms (I'm testing on 4.5), the Editor scrolls horizontally and it seems like the horizontal scroll is related to the length of the text entered.

Any updates or further suggestions for workarounds would be welcome!

Hi,
I was having the same problem and I came up with a work around, backup solution.
I am calculating the line count with using width, text font and text and when there is a change on the line number I am replacing last (' ') space with ('\n'). This is a backup solution for me until issue is fixed. But it is working as expexted.
ex.
CGSize size = Control.Text.StringSize(Control.Font, Control.Frame.Size, UILineBreakMode.WordWrap); int numLines = (int)(size.Height / Control.Font.LineHeight); if (numLines > lastLine) { lastLine = numLines; BeginInvokeOnMainThread(() => { int pos = Control.Text.LastIndexOf(' '); Control.Text = Control.Text.Substring(0, pos) + "\n" + Control.Text.Substring(pos + 1); }); }

It is very disappointing that since version 4.2, this issue hasn't been fixed. It is a high priority issue in my opinion.

  1. It would be great to get a status update on this.
  2. I assume it must not affect most people or it would have been fixed sooner. If we knew what was causing it to act oddly for some, we could possibly change our process so it didn't misbehave for us.

Here is my workaround - Set a timer for 0.5 seconds and set the text again.

Device.StartTimer(new TimeSpan(500), () =>
            {
                string existingText = noteBody.Text;
                noteBody.Text = "";
                noteBody.Text = existingText;
                return false;
            });

Here is my workaround - Set a timer for 0.5 seconds and set the text again.

Device.StartTimer(new TimeSpan(500), () =>
            {
                string existingText = noteBody.Text;
                noteBody.Text = "";
                noteBody.Text = existingText;
                return false;
            });

Thanks for sharing - we will try and test that!

@WillSinger I guess it affects a lot of people, but (at least in our case), this bug was discovered accidentally: just by having a little more text than usually. We did not even expect to test this piece of UI. In fact it affects us so much that we have rolled back to 4.2 until it is fixed.

Thanks for the input. Sometimes I wonder if we are doing things differently than others or if others are just okay with it...

Hi,
I was having the same problem and I came up with a workaround, backup solution.
I am calculating the line count by using width, text font, and text, and when there is a change on the line number I am replacing last (' ') space with ('\n'). This is a backup solution for me until the issue is fixed. But it is working as expected.
ex.
CGSize size = Control.Text.StringSize(Control.Font, Control.Frame.Size, UILineBreakMode.WordWrap); int numLines = (int)(size.Height / Control.Font.LineHeight); if (numLines > lastLine) { lastLine = numLines; BeginInvokeOnMainThread(() => { int pos = Control.Text.LastIndexOf(' '); Control.Text = Control.Text.Substring(0, pos) + "\n" + Control.Text.Substring(pos + 1); }); }

It works for me thanks

The cause of this issue seems to be in the CharacterSpacing code (I guess #9710 should fix this).
So for now if you don't need the CharacterSpacing we can prevent that code from running with a custom renderer:
```C#
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == Editor.TextProperty.PropertyName)
{
UpdateText();
return;
}
if (e.PropertyName == Editor.CharacterSpacingProperty.PropertyName ||
e.PropertyName == Editor.PlaceholderProperty.PropertyName)
return;

base.OnElementPropertyChanged(sender, e);

}
```

Hi,
I was having the same problem and I came up with a work around, backup solution.
I am calculating the line count with using width, text font and text and when there is a change on the line number I am replacing last (' ') space with ('\n'). This is a backup solution for me until issue is fixed. But it is working as expexted.
ex.
CGSize size = Control.Text.StringSize(Control.Font, Control.Frame.Size, UILineBreakMode.WordWrap); int numLines = (int)(size.Height / Control.Font.LineHeight); if (numLines > lastLine) { lastLine = numLines; BeginInvokeOnMainThread(() => { int pos = Control.Text.LastIndexOf(' '); Control.Text = Control.Text.Substring(0, pos) + "\n" + Control.Text.Substring(pos + 1); }); }

I am also having same issue in 4.6 Xamarin forms version. Can you please let me know , here what is lastLine. So I can use your solution. Thanks in advance

The cause of this issue seems to be in the CharacterSpacing code (I guess #9710 should fix this).
So for now if you don't need the CharacterSpacing we can prevent that code from running with a custom renderer:

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == Editor.TextProperty.PropertyName)
    {
        UpdateText();
        return;
    } 
    if (e.PropertyName == Editor.CharacterSpacingProperty.PropertyName || 
        e.PropertyName == Editor.PlaceholderProperty.PropertyName)
        return;

    base.OnElementPropertyChanged(sender, e);
}

Thank you very much! That was the "solution". I already had a custom renderer for the editor and so it was a simple copy/paste that solves the Problems - I tried so many things for hours until i found this issue thread! So thankfull for users like to sharing working work-arounds here. Great!

This is really impacting us in a big way. The Custom Renderer solution fixes the auto scroll when typing issue (without hitting return, hitting return fixes everything) but now there is no way to scroll the Editor manually even after focusing the editor.

Ok workaround.
Before I was adding a "\n " and removing it after a delay to make it work. It seems that no longer works. Now I have to

  1. Implement the Custom Renderer mentioned above. Only doing this step cause the issue to come back as soon as you type something in the Editor.
  2. Add "\n\n " and remove it later. More stupid logic sigh...

` private async void OnXxamNotesExpandedViewEditor_SizeChanged(object sender, EventArgs e)
{
string strCurrentEditorText = xxamNotesExpandedViewEditor.Text;

        if (Device.RuntimePlatform == Device.iOS && !xxamNotesExpandedViewEditor.IsReadOnly)
        {
            xxamNotesExpandedViewEditor.Text = strCurrentEditorText + "\n \n ";

                await Task.Delay(200);
                xxamNotesExpandedViewEditor.Text = strCurrentEditorText;
        }
    }`

This bug seems related to the horizontal scrolling issue (which is fixed)
https://github.com/xamarin/Xamarin.Forms/issues/8569

Using Xamarin.Forms 4.8.0.1560 I tried @boris-df solution but that didn't work. The solution as posted by @FIELDPOINT works, but i modified it like this::

  public class EditorRenderer : Xamarin.Forms.Platform.iOS.EditorRenderer
  {
      protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
      {
          if (Element == null) return;
          Element.SizeChanged += Handle_Element_SizeChanged;
          base.OnElementChanged(e);
      }

      private async void Handle_Element_SizeChanged(object sender, EventArgs e)
      {
          var text = Control.Text;
          await System.Threading.Tasks.Task.Delay(1);
          Control.Text = text + "\n";
          await System.Threading.Tasks.Task.Delay(1);
          Control.Text = text;
      }

      protected override void Dispose(bool disposing)
      {
          if (Element != null)
          {
              Element.SizeChanged -= Handle_Element_SizeChanged;
          }

          base.Dispose(disposing);
      }
  }

any ideas what the problem is?

my workaround.

public class FixedEditorRenderer : EditorRenderer
{
private UILabel _placeholder;

    protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
    {
        base.OnElementChanged(e);

        if (Control != null)
        {
            UpdatePlaceholder();
        }
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    { 
        if(e.PropertyName == Editor.TextProperty.PropertyName)
        {
            UpdatePlaceholder();
            return;
        }

        base.OnElementPropertyChanged(sender, e);
    }

    private void UpdatePlaceholder()
    {
        if(_placeholder == null)
        {
            var subview = TextView.Subviews?.OfType<UILabel>().FirstOrDefault();

            _placeholder = subview;
        }

        if (string.IsNullOrEmpty(TextView.Text))
        {
                TextView.AddSubview(_placeholder);

                UpdateUIConstraints();
        }
        else
        {
            _placeholder.RemoveFromSuperview();
        }
    }

    private void UpdateUIConstraints()
    {
        var edgeInsets = TextView.TextContainerInset;
        var lineFragmentPadding = TextView.TextContainer.LineFragmentPadding;

        var vConstraints = NSLayoutConstraint.FromVisualFormat(
            "V:|-" + edgeInsets.Top + $"-[{nameof(_placeholder)}]-" + edgeInsets.Bottom + "-|", 0, new NSDictionary(),
            NSDictionary.FromObjectsAndKeys(
                new NSObject[] { _placeholder }, new NSObject[] { new NSString(nameof(_placeholder)) })
        );

        var hConstraints = NSLayoutConstraint.FromVisualFormat(
            "H:|-" + lineFragmentPadding + $"-[{nameof(_placeholder)}]-" + lineFragmentPadding + "-|",
            0, new NSDictionary(),
            NSDictionary.FromObjectsAndKeys(
                new NSObject[] { _placeholder }, new NSObject[] { new NSString(nameof(_placeholder)) })
        );

        _placeholder.TranslatesAutoresizingMaskIntoConstraints = false;

        Control.AddConstraints(hConstraints);
        Control.AddConstraints(vConstraints);
    }
}
Was this page helpful?
0 / 5 - 0 ratings