Xamarin .
Editor
, . , bindable FormattedString
. , , TextChanged
( Regex
).
FormattedText
bindable-property, Spannable
android AttributedString
iOS.

public class ExEditor : Editor
{
public static readonly BindableProperty FormattedTextProperty =
BindableProperty.Create(
"FormattedText", typeof(FormattedString), typeof(ExEditor),
defaultValue: default(FormattedString));
public FormattedString FormattedText
{
get { return (FormattedString)GetValue(FormattedTextProperty); }
set { SetValue(FormattedTextProperty, value); }
}
public ExEditor()
{
TextChanged += ExEditor_TextChanged;
}
void ExEditor_TextChanged(object sender, TextChangedEventArgs e)
{
if (string.IsNullOrWhiteSpace(Text))
return;
var pattern = @"\b(SELECT|WHERE|AND|OR)\b";
var words = Regex.Split(Text, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
var formattedString = new FormattedString();
foreach (var word in words)
formattedString.Spans.Add(new Span
{
Text = word,
BackgroundColor = BackgroundColor,
FontSize = FontSize,
FontFamily = FontFamily,
FontAttributes = FontAttributes,
ForegroundColor = Regex.IsMatch(word, pattern, RegexOptions.IgnoreCase) ? Color.Red : TextColor
});
FormattedText = formattedString;
}
}
iOS Renderer
[assembly: ExportRenderer(typeof(ExEditor), typeof(ExEditorRenderer))]
namespace SampleApp.iOS
{
public class ExEditorRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (Control == null || Element == null)
return;
UpdateTextOnControl();
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(ExEditor.FormattedText)
|| e.PropertyName == nameof(Editor.FontFamily)
|| e.PropertyName == nameof(Editor.FontSize)
|| e.PropertyName == nameof(Editor.TextColor)
|| e.PropertyName == nameof(Editor.BackgroundColor)
|| e.PropertyName == nameof(Editor.FontAttributes))
{
UpdateTextOnControl();
}
}
void UpdateTextOnControl()
{
var caretPos = Control.GetOffsetFromPosition(Control.BeginningOfDocument, Control.SelectedTextRange.Start);
if (Element is ExEditor formsElement)
if (formsElement.FormattedText != null)
Control.AttributedText = formsElement.FormattedText.ToAttributed(new Font(), Element.TextColor);
var newPosition = Control.GetPosition(Control.BeginningOfDocument, offset: caretPos);
Control.SelectedTextRange = Control.GetTextRange(newPosition, newPosition);
}
}
}
Android Renderer
[assembly: ExportRenderer(typeof(ExEditor), typeof(ExEditorRenderer))]
namespace SampleApp.Android
{
public class ExEditorRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (Control == null || Element == null)
return;
UpdateTextOnControl();
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(ExEditor.FormattedText)
|| e.PropertyName == nameof(Editor.FontFamily)
|| e.PropertyName == nameof(Editor.FontSize)
|| e.PropertyName == nameof(Editor.TextColor)
|| e.PropertyName == nameof(Editor.BackgroundColor)
|| e.PropertyName == nameof(Editor.FontAttributes))
{
UpdateTextOnControl();
}
}
void UpdateTextOnControl()
{
var caretPos = Control.SelectionStart;
if (Element is ExEditor formsElement)
if (formsElement.FormattedText != null)
Control.SetText(formsElement.FormattedText.ToAttributed(new Font(), Element.TextColor, Control),
TextView.BufferType.Spannable);
Control.SetSelection(caretPos);
}
}
}
<!-- make sure to map local prefix to control namespace -->
<local:ExEditor Text="Select * from Table where text='1' and type='str'"
VerticalOptions="Start" />