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.
Editor
instance with an explicit HeightRequest
Text
property to a lengthy value (either a multi-line string or a very long single-line string that wraps)ContentPage
Editor should always be scrollable when the text exceeds the bounds of the text input.
Text
property is set _after_ the Editor has been added to the page, scrolling works as expected.Text
property is set _before_ the Editor has been added to the page, scrolling does not work.Editor Scrolling Bug -- Example Code
Device.BeginInvokeOnMainThread(() => editor.Text = "...")
to "defer" the setting of the text causes the Editor to become scrollable. (this seems to work _sometimes_)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:
I'm having same problem.
Here is more description of what I'm seeing.
This is a big issue in our app. Where are you at in getting a fix?
I am seeing the same thing.
Additional learnings:
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.
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 theCharacterSpacing
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
` 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);
}
}
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
` private async void OnXxamNotesExpandedViewEditor_SizeChanged(object sender, EventArgs e)
{
string strCurrentEditorText = xxamNotesExpandedViewEditor.Text;