[Visual] Material Editor (#5345)
authorShane Neuville <shane94@hotmail.com>
Tue, 26 Feb 2019 19:46:48 +0000 (12:46 -0700)
committerGitHub <noreply@github.com>
Tue, 26 Feb 2019 19:46:48 +0000 (12:46 -0700)
* [Material] Visual

* - gallery and simplify android renderers

* Update Xamarin.Forms.Material.iOS/MaterialEditorRenderer.cs

* Update Xamarin.Forms.Platform.Android/Material/MaterialEditorRenderer.cs

* wire up done and fix ph bug

* - ios fix auto sizing issues

* fix NRE

* - Android: fix bounce and IsreadOnly
- iOS: fix IsReadonly and sizing issue

* - fix editor

* Fix ios Editor to shrink when lines are deleted

* Update Xamarin.Forms.Material.iOS/MaterialMultiLineTextField.cs

* - formatting fixes

15 files changed:
Xamarin.Forms.Controls/ControlGalleryPages/VisualGallery.xaml
Xamarin.Forms.Material.iOS/MaterialEditorRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Material.iOS/MaterialEntryRenderer.cs
Xamarin.Forms.Material.iOS/MaterialMultiLineTextField.cs [new file with mode: 0644]
Xamarin.Forms.Material.iOS/MaterialTextField.cs
Xamarin.Forms.Material.iOS/MaterialTextManager.cs [new file with mode: 0644]
Xamarin.Forms.Material.iOS/Properties/AssemblyInfo.cs
Xamarin.Forms.Material.iOS/Xamarin.Forms.Material.iOS.csproj
Xamarin.Forms.Platform.Android/Material/MaterialEditorRenderer.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/Material/MaterialEntryRenderer.cs
Xamarin.Forms.Platform.Android/Material/MaterialFormsEditTextManager.cs
Xamarin.Forms.Platform.Android/Renderers/EditorRenderer.cs
Xamarin.Forms.Platform.Android/Renderers/EntryRenderer.cs
Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
Xamarin.Forms.Platform.iOS/Renderers/EditorRenderer.cs

index 527ddbe..9cfa068 100644 (file)
             <Label Text="Time Picker" Margin="0,0,0,-10" />
             <TimePicker></TimePicker>
             <Label Text="Picker" Margin="0,0,0,-10" />
-            <Picker  Title="Select a monkey">
+            <Picker Title="Select a monkey">
                 <Picker.ItemsSource>
                     <x:Array Type="{x:Type x:String}">
                         <x:String>Baboon</x:String>
             <Label Text="Red background" Margin="0,0,0,-10" />
             <Stepper BackgroundColor="Red"></Stepper>
             
+            <Label Text="Editors" FontSize="Large"></Label>
+            <Label Text="Normal" Margin="0,0,0,-10" />
+            <Editor HeightRequest="200"></Editor>
+            <Label Text="Place Holder" Margin="0,0,0,-10" />
+            <Editor Placeholder="Place Holder" HeightRequest="200"></Editor>
         </StackLayout>
     </ScrollView>
 
diff --git a/Xamarin.Forms.Material.iOS/MaterialEditorRenderer.cs b/Xamarin.Forms.Material.iOS/MaterialEditorRenderer.cs
new file mode 100644 (file)
index 0000000..ba580ff
--- /dev/null
@@ -0,0 +1,115 @@
+using UIKit;
+using MaterialComponents;
+using System;
+
+namespace Xamarin.Forms.Platform.iOS.Material
+{
+       public class MaterialEditorRenderer : EditorRendererBase<MaterialMultilineTextField>, IMaterialEntryRenderer
+       {
+               bool _hackHasRan = false;
+
+               protected override MaterialMultilineTextField CreateNativeControl()
+               {                       
+                       return new MaterialMultilineTextField(this, Element);
+               }
+
+               protected override void SetBackgroundColor(Color color)
+               {
+                       ApplyTheme();                   
+               }
+
+
+               protected internal override void UpdateTextColor()
+               {
+                       Control?.UpdateTextColor(this);
+               }
+
+
+               protected virtual void ApplyTheme()
+               {
+                       Control?.ApplyTheme(this);
+               }
+
+               protected internal override void UpdateFont()
+               {
+                       base.UpdateFont();
+                       Control?.ApplyTypographyScheme(Element);
+               }       
+
+               protected internal override void UpdatePlaceholderText()
+               {
+                       if (Control == null || !_hackHasRan)
+                               return;
+
+                       Control.UpdatePlaceholder(this);
+               }
+
+               protected internal override void UpdatePlaceholderColor()
+               {
+                       if (Control == null || !_hackHasRan)
+                               return;
+
+                       Control.UpdatePlaceholder(this);
+               }
+
+               protected internal override void UpdateText()
+               {
+                       if (!_hackHasRan)
+                               return;
+
+                       base.UpdateText();
+               }
+
+               protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
+               {
+                       base.OnElementChanged(e);
+                       InitialPlaceholderSetupHack();
+               }
+
+               protected internal override void UpdateAutoSizeOption()
+               {
+                       base.UpdateAutoSizeOption();
+                       Control.AutoSizeWithChanges = Element.AutoSize == EditorAutoSizeOption.TextChanges;
+
+                       if(!Control.ExpandsOnOverflow)
+                               Control.ExpandsOnOverflow = Element.AutoSize == EditorAutoSizeOption.TextChanges;
+               }
+
+               // this is required to force the placeholder to size correctly if it starts out prefilled
+               void InitialPlaceholderSetupHack()
+               {
+                       if (Element == null)
+                               return;
+
+                       if(String.IsNullOrWhiteSpace(Element.Text) || String.IsNullOrWhiteSpace(Element.Placeholder))
+                       {
+                               _hackHasRan = true;
+                               UpdateText();
+                               UpdatePlaceholderText();
+                               return;
+                       }
+
+                       TextView.BecomeFirstResponder();
+                       Control.UpdatePlaceholder(this);
+                       Device.BeginInvokeOnMainThread(() =>
+                       {
+                               _hackHasRan = true;
+                               UpdateText();
+                               TextView.ResignFirstResponder();
+                       });
+               }
+
+
+               // Placeholder is currently broken upstream and doesn't animate to the correct scale
+               string IMaterialEntryRenderer.Placeholder => Element?.Placeholder;
+               // string IMaterialEntryRenderer.Placeholder => String.Empty;
+
+               Color IMaterialEntryRenderer.TextColor => Element?.TextColor ?? Color.Default;
+               Color IMaterialEntryRenderer.PlaceholderColor => Element?.PlaceholderColor ?? Color.Default;
+               Color IMaterialEntryRenderer.BackgroundColor => Element?.BackgroundColor ?? Color.Default;
+
+               protected IntrinsicHeightTextView IntrinsicHeightTextView => (IntrinsicHeightTextView)TextView;
+               protected override UITextView TextView => Control?.TextView;
+
+       }
+}
\ No newline at end of file
index 1e9d9d9..e3bf4be 100644 (file)
@@ -1,4 +1,5 @@
-using UIKit;
+using CoreGraphics;
+using UIKit;
 
 namespace Xamarin.Forms.Platform.iOS.Material
 {
@@ -45,7 +46,6 @@ namespace Xamarin.Forms.Platform.iOS.Material
                        
                }
 
-
                Color IMaterialEntryRenderer.TextColor => Element?.TextColor ?? Color.Default;
                Color IMaterialEntryRenderer.PlaceholderColor => Element?.PlaceholderColor ?? Color.Default;
                Color IMaterialEntryRenderer.BackgroundColor => Element?.BackgroundColor ?? Color.Default;
diff --git a/Xamarin.Forms.Material.iOS/MaterialMultiLineTextField.cs b/Xamarin.Forms.Material.iOS/MaterialMultiLineTextField.cs
new file mode 100644 (file)
index 0000000..e3cd673
--- /dev/null
@@ -0,0 +1,109 @@
+using System;
+using CoreGraphics;
+using MaterialComponents;
+using UIKit;
+using MMultilineTextField = MaterialComponents.MultilineTextField;
+using MTextInputControllerBase = MaterialComponents.TextInputControllerBase;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Platform.iOS.Material
+{
+       public class MaterialMultilineTextField : MMultilineTextField, IMaterialTextField
+       {
+               public SemanticColorScheme ColorScheme { get; set; }
+               public TypographyScheme TypographyScheme { get; set; }
+               public MTextInputControllerBase ActiveTextInputController { get; set; }
+               public ITextInput TextInput => this;
+               internal bool AutoSizeWithChanges { get; set; } = false;
+               CGSize _contentSize;
+
+
+               public MaterialMultilineTextField(IMaterialEntryRenderer element, IFontElement fontElement)
+               {
+                       VisualElement.VerifyVisualFlagEnabled();
+                       MaterialTextManager.Init(element, this, fontElement);
+               }
+
+               public override CGSize SizeThatFits(CGSize size)
+               {
+                       bool expandTurnedBackOn = UpdateIfTextViewShouldCollapse();
+                       var result = base.SizeThatFits(size);
+
+                       if (nfloat.IsInfinity(result.Width))
+                               result = SystemLayoutSizeFittingSize(result, (float)UILayoutPriority.FittingSizeLevel, (float)UILayoutPriority.DefaultHigh);
+
+                       if (ExpandsOnOverflow)
+                               _contentSize = result;
+                       else
+                               _contentSize = TextView.ContentSize;
+
+                       if (!expandTurnedBackOn)
+                               UpdateIfTextViewShouldStopExpanding();
+
+                       return result;
+               }
+
+               bool UpdateIfTextViewShouldCollapse()
+               {
+                       if (!ExpandsOnOverflow &&
+                               !AutoSizeWithChanges &&
+                               !ShouldRestrainSize())
+                       {
+                               ExpandsOnOverflow = true;
+                               return true;
+                       }
+
+                       return false;
+               }
+
+               bool ShouldRestrainSize()
+               {
+                       if (TextView?.Font == null)
+                               return false;
+
+                       return (((NumberOfLines + 1) * TextView.Font.LineHeight) > Frame.Height);
+               }
+
+               void UpdateIfTextViewShouldStopExpanding()
+               {
+                       if (!UpdateIfTextViewShouldCollapse() &&
+                               !AutoSizeWithChanges &&
+                               ExpandsOnOverflow &&
+                               ShouldRestrainSize()) 
+                       {
+                               ExpandsOnOverflow = false;
+                       }
+               }
+
+               public override CGRect Frame
+               {
+                       get => base.Frame;
+                       set
+                       {
+                               base.Frame = value;
+                               UpdateIfTextViewShouldStopExpanding();
+                       }
+               }
+
+               int NumberOfLines
+               {
+                       get
+                       {
+                               if (TextView?.ContentSize == null || TextView.Font == null || TextView.Font.LineHeight == 0)
+                                       return 0;
+
+                               return (int)(_contentSize.Height / TextView.Font.LineHeight);
+                       }
+               }
+
+               internal void ApplyTypographyScheme(IFontElement fontElement) => MaterialTextManager.ApplyTypographyScheme(this, fontElement);
+
+               internal void ApplyTheme(IMaterialEntryRenderer element) => MaterialTextManager.ApplyTheme(this, element);
+
+               internal void UpdatePlaceholder(IMaterialEntryRenderer element) => MaterialTextManager.UpdatePlaceholder(this, element);
+
+               internal void UpdateTextColor(IMaterialEntryRenderer element) => MaterialTextManager.UpdateTextColor(this, element);
+
+
+       }
+}
index 77ac995..ac73d4e 100644 (file)
@@ -12,23 +12,27 @@ using Xamarin.Forms.Internals;
 
 namespace Xamarin.Forms.Platform.iOS.Material
 {
-       public class MaterialTextField : MTextField
+       internal interface IMaterialTextField
        {
-               SemanticColorScheme _colorScheme;
-               TypographyScheme _typographyScheme;
-               MTextInputControllerBase _activeTextinputController;
+               SemanticColorScheme ColorScheme { get; set; }
+               TypographyScheme TypographyScheme { get; set; }
+               MTextInputControllerBase ActiveTextInputController { get; set; }
+
+               ITextInput TextInput { get; }
+       }
+
+       public class MaterialTextField : MTextField, IMaterialTextField
+       {
+               public SemanticColorScheme ColorScheme { get; set; }
+               public TypographyScheme TypographyScheme { get; set; }
+               public MTextInputControllerBase ActiveTextInputController { get; set; }
+
+               public ITextInput TextInput => this;
 
                public MaterialTextField(IMaterialEntryRenderer element, IFontElement fontElement)
                {
                        VisualElement.VerifyVisualFlagEnabled();
-                       ClearButtonMode = UITextFieldViewMode.Never;
-                       _activeTextinputController = new MTextInputControllerFilled(this);
-                       TextInsetsMode = TextInputTextInsetsMode.IfContent;
-                       _typographyScheme = CreateTypographyScheme();
-                       _colorScheme = (SemanticColorScheme)CreateColorScheme();
-                       ApplyTypographyScheme(fontElement);
-                       ApplyTheme(element);
-
+                       MaterialTextManager.Init(element, this, fontElement);
                }
 
                public override CGSize SizeThatFits(CGSize size)
@@ -41,74 +45,15 @@ namespace Xamarin.Forms.Platform.iOS.Material
                        return result;
                }
 
-               internal void ApplyTypographyScheme(IFontElement fontElement)
-               {
-                       Font = fontElement?.ToUIFont();
-                       _typographyScheme.Subtitle1 = Font;
-                       TextFieldTypographyThemer.ApplyTypographyScheme(_typographyScheme, this);
-                       TextFieldTypographyThemer.ApplyTypographyScheme(_typographyScheme, _activeTextinputController);
-               }
-
-               internal void ApplyTheme(IMaterialEntryRenderer element)
-               {
-                       if (element == null)
-                               return;
-
-                       if (_activeTextinputController == null)
-                               return;
-
-                       FilledTextFieldColorThemer.ApplySemanticColorScheme(_colorScheme, (MTextInputControllerFilled)_activeTextinputController);
-
-                       var textColor = MaterialColors.GetEntryTextColor(element.TextColor);
-                       var placeHolderColors = MaterialColors.GetPlaceHolderColor(element.PlaceholderColor, element.TextColor);
-                       var underlineColors = MaterialColors.GetUnderlineColor(element.TextColor);
-
-                       TextColor = textColor;
-                       _activeTextinputController.InlinePlaceholderColor = placeHolderColors.InlineColor;
-                       _activeTextinputController.FloatingPlaceholderNormalColor = placeHolderColors.InlineColor;
-                       _activeTextinputController.FloatingPlaceholderActiveColor = placeHolderColors.FloatingColor;
-
-                       // BackgroundColor
-                       _activeTextinputController.BorderFillColor = MaterialColors.CreateEntryFilledInputBackgroundColor(element.BackgroundColor, element.TextColor);
-
-                       _activeTextinputController.ActiveColor = underlineColors.FocusedColor;
-                       _activeTextinputController.NormalColor = underlineColors.UnFocusedColor;
-               }
-
-               internal void UpdatePlaceholder(IMaterialEntryRenderer element)
-               {
-                       var placeholderText = element.Placeholder ?? String.Empty;
-                       _activeTextinputController.PlaceholderText = placeholderText;
-                       ApplyTheme(element);
-
-                       var previous = _activeTextinputController.FloatingPlaceholderScale;
-                       if (String.IsNullOrWhiteSpace(placeholderText))
-                               _activeTextinputController.FloatingPlaceholderScale = 0;
-                       else
-                               _activeTextinputController.FloatingPlaceholderScale = (float)TextInputControllerBase.FloatingPlaceholderScaleDefault;
-
-                       if (previous != _activeTextinputController.FloatingPlaceholderScale && element is IVisualElementRenderer controller)
-                               controller.Element?.InvalidateMeasureInternal(InvalidationTrigger.VerticalOptionsChanged);
-               }
+               internal void ApplyTypographyScheme(IFontElement fontElement) =>
+                       MaterialTextManager.ApplyTypographyScheme(this, fontElement);
 
+               internal void ApplyTheme(IMaterialEntryRenderer element) => MaterialTextManager.ApplyTheme(this, element);
 
-               internal void UpdateTextColor(IMaterialEntryRenderer element)
-               {
-                       var uIColor = MaterialColors.GetEntryTextColor(element.TextColor);
-                       _colorScheme.OnSurfaceColor = uIColor;
-                       _colorScheme.PrimaryColor = uIColor;
-               }
+               internal void UpdatePlaceholder(IMaterialEntryRenderer element) => MaterialTextManager.UpdatePlaceholder(this, element);
 
-               protected virtual IColorScheming CreateColorScheme()
-               {
-                       var returnValue = MaterialColors.Light.CreateColorScheme();
-                       return returnValue;
-               }
 
-               protected virtual TypographyScheme CreateTypographyScheme()
-               {
-                       return new TypographyScheme();
-               }
+               internal void UpdateTextColor(IMaterialEntryRenderer element) => MaterialTextManager.UpdateTextColor(this, element);
        }
 
 
diff --git a/Xamarin.Forms.Material.iOS/MaterialTextManager.cs b/Xamarin.Forms.Material.iOS/MaterialTextManager.cs
new file mode 100644 (file)
index 0000000..e9eb20b
--- /dev/null
@@ -0,0 +1,95 @@
+using System;
+using CoreGraphics;
+using MaterialComponents;
+using UIKit;
+using MTextField = MaterialComponents.TextField;
+using MTextInputControllerFilled = MaterialComponents.TextInputControllerFilled;
+using MTextInputControllerBase = MaterialComponents.TextInputControllerBase;
+using System.Collections.Generic;
+using ObjCRuntime;
+using Foundation;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Platform.iOS.Material
+{
+       internal static class MaterialTextManager
+       {
+               public static void Init(IMaterialEntryRenderer element, IMaterialTextField textField, IFontElement fontElement)
+               {
+                       textField.TextInput.ClearButtonMode = UITextFieldViewMode.Never;
+                       textField.ActiveTextInputController = new MTextInputControllerFilled(textField.TextInput);
+                       textField.TextInput.TextInsetsMode = TextInputTextInsetsMode.IfContent;
+                       textField.TypographyScheme = CreateTypographyScheme();
+                       textField.ColorScheme = (SemanticColorScheme)CreateColorScheme();
+                       ApplyTypographyScheme(textField, fontElement);
+                       ApplyTheme(textField, element);
+               }
+               public static void ApplyTypographyScheme(IMaterialTextField textField, IFontElement fontElement)
+               {
+                       textField.TextInput.Font = fontElement?.ToUIFont();
+                       textField.TypographyScheme.Subtitle1 = textField.TextInput.Font;
+                       TextFieldTypographyThemer.ApplyTypographyScheme(textField.TypographyScheme, textField.TextInput);
+                       TextFieldTypographyThemer.ApplyTypographyScheme(textField.TypographyScheme, textField.ActiveTextInputController);
+               }
+
+               public static void ApplyTheme(IMaterialTextField textField, IMaterialEntryRenderer element)
+               {
+                       if (element == null)
+                               return;
+
+                       if (textField.ActiveTextInputController == null)
+                               return;
+
+                       FilledTextFieldColorThemer.ApplySemanticColorScheme(textField.ColorScheme, (MTextInputControllerFilled)textField.ActiveTextInputController);
+
+                       var textColor = MaterialColors.GetEntryTextColor(element.TextColor);
+                       var placeHolderColors = MaterialColors.GetPlaceHolderColor(element.PlaceholderColor, element.TextColor);
+                       var underlineColors = MaterialColors.GetUnderlineColor(element.TextColor);
+
+                       textField.TextInput.TextColor = textColor;
+                       textField.ActiveTextInputController.InlinePlaceholderColor = placeHolderColors.InlineColor;
+                       textField.ActiveTextInputController.FloatingPlaceholderNormalColor = placeHolderColors.InlineColor;
+                       textField.ActiveTextInputController.FloatingPlaceholderActiveColor = placeHolderColors.FloatingColor;
+
+                       // BackgroundColor
+                       textField.ActiveTextInputController.BorderFillColor = MaterialColors.CreateEntryFilledInputBackgroundColor(element.BackgroundColor, element.TextColor);
+
+                       textField.ActiveTextInputController.ActiveColor = underlineColors.FocusedColor;
+                       textField.ActiveTextInputController.NormalColor = underlineColors.UnFocusedColor;
+               }
+
+               public static void UpdatePlaceholder(IMaterialTextField textField, IMaterialEntryRenderer element)
+               {
+                       var placeholderText = element.Placeholder ?? String.Empty;
+                       textField.ActiveTextInputController.PlaceholderText = placeholderText;
+                       ApplyTheme(textField, element);
+
+                       var previous = textField.ActiveTextInputController.FloatingPlaceholderScale;
+                       if (String.IsNullOrWhiteSpace(placeholderText))
+                               textField.ActiveTextInputController.FloatingPlaceholderScale = 0;
+                       else
+                               textField.ActiveTextInputController.FloatingPlaceholderScale = (float)TextInputControllerBase.FloatingPlaceholderScaleDefault;
+
+                       if (previous != textField.ActiveTextInputController.FloatingPlaceholderScale && element is IVisualElementRenderer controller)
+                               controller.Element?.InvalidateMeasureInternal(InvalidationTrigger.VerticalOptionsChanged);
+               }
+
+               public static void UpdateTextColor(IMaterialTextField textField, IMaterialEntryRenderer element)
+               {
+                       var uIColor = MaterialColors.GetEntryTextColor(element.TextColor);
+                       textField.ColorScheme.OnSurfaceColor = uIColor;
+                       textField.ColorScheme.PrimaryColor = uIColor;
+               }
+
+               static IColorScheming CreateColorScheme()
+               {
+                       var returnValue = MaterialColors.Light.CreateColorScheme();
+                       return returnValue;
+               }
+
+               static TypographyScheme CreateTypographyScheme()
+               {
+                       return new TypographyScheme();
+               }
+       }
+}
\ No newline at end of file
index 5c949c1..5d90ca2 100644 (file)
@@ -24,4 +24,5 @@ using Xamarin.Forms;
 [assembly: ExportRenderer(typeof(Xamarin.Forms.TimePicker), typeof(Xamarin.Forms.Platform.iOS.Material.MaterialTimePickerRenderer), new[] { typeof(VisualMarker.MaterialVisual) })]
 [assembly: ExportRenderer(typeof(Xamarin.Forms.Picker), typeof(Xamarin.Forms.Platform.iOS.Material.MaterialPickerRenderer), new[] { typeof(VisualMarker.MaterialVisual) })]
 [assembly: ExportRenderer(typeof(Xamarin.Forms.DatePicker), typeof(Xamarin.Forms.Platform.iOS.Material.MaterialDatePickerRenderer), new[] { typeof(VisualMarker.MaterialVisual) })]
-[assembly: ExportRenderer(typeof(Xamarin.Forms.Stepper), typeof(Xamarin.Forms.Platform.iOS.Material.MaterialStepperRenderer), new[] { typeof(VisualMarker.MaterialVisual) })]
\ No newline at end of file
+[assembly: ExportRenderer(typeof(Xamarin.Forms.Stepper), typeof(Xamarin.Forms.Platform.iOS.Material.MaterialStepperRenderer), new[] { typeof(VisualMarker.MaterialVisual) })]
+[assembly: ExportRenderer(typeof(Xamarin.Forms.Editor), typeof(Xamarin.Forms.Platform.iOS.Material.MaterialEditorRenderer), new[] { typeof(VisualMarker.MaterialVisual) })]
\ No newline at end of file
index 7d0d51c..8059c8c 100644 (file)
       <Link>MaterialColors.cs</Link>
     </Compile>
     <Compile Include="IMaterialEntryRenderer.cs" />
+    <Compile Include="MaterialEditorRenderer.cs" />
     <Compile Include="MaterialPickerRenderer.cs" />
     <Compile Include="MaterialDatePickerRenderer.cs" />
+    <Compile Include="MaterialMultiLineTextField.cs" />
+    <Compile Include="MaterialTextManager.cs" />
     <Compile Include="MaterialTimePickerRenderer.cs" />
     <Compile Include="MaterialTextField.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
diff --git a/Xamarin.Forms.Platform.Android/Material/MaterialEditorRenderer.cs b/Xamarin.Forms.Platform.Android/Material/MaterialEditorRenderer.cs
new file mode 100644 (file)
index 0000000..0f8e69c
--- /dev/null
@@ -0,0 +1,84 @@
+#if __ANDROID_28__
+using Android.Content;
+using Android.Util;
+using Android.Views;
+using Android.Widget;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android.Material;
+
+[assembly: ExportRenderer(typeof(Xamarin.Forms.Editor), typeof(MaterialEditorRenderer), new[] { typeof(VisualMarker.MaterialVisual) })]
+namespace Xamarin.Forms.Platform.Android.Material
+{
+       public sealed class MaterialEditorRenderer : EditorRendererBase<MaterialFormsTextInputLayout>
+       {
+               bool _disposed;
+               MaterialFormsEditText _textInputEditText;
+               MaterialFormsTextInputLayout _textInputLayout;
+
+               public MaterialEditorRenderer(Context context) :
+                       base(MaterialContextThemeWrapper.Create(context))
+               {
+                       VisualElement.VerifyVisualFlagEnabled();
+               }
+
+               IElementController ElementController => Element as IElementController;
+
+               protected override EditText EditText => _textInputEditText;
+
+               protected override MaterialFormsTextInputLayout CreateNativeControl()
+               {
+                       LayoutInflater inflater = LayoutInflater.FromContext(Context);
+                       var view = inflater.Inflate(Resource.Layout.TextInputLayoutFilledBox, null);
+                       _textInputLayout = (MaterialFormsTextInputLayout)view;
+                       _textInputEditText = _textInputLayout.FindViewById<MaterialFormsEditText>(Resource.Id.materialformsedittext);
+                       UpdatePlaceholderText();
+
+                       return _textInputLayout;
+               }
+
+               protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
+               {
+                       base.OnElementChanged(e);
+                       UpdateBackgroundColor();
+               }
+
+               protected override void UpdateTextColor() => ApplyTheme();
+
+               protected override void UpdateBackgroundColor()
+               {
+                       if (_disposed || _textInputLayout == null)
+                               return;
+
+                       _textInputLayout.BoxBackgroundColor = MaterialColors.CreateEntryFilledInputBackgroundColor(Element.BackgroundColor, Element.TextColor);
+               }
+
+               protected internal override void UpdatePlaceholderText()
+               {
+                       if (_disposed || _textInputLayout == null)
+                               return;
+
+                       _textInputLayout?.SetHint(Element.Placeholder, Element);
+               }
+
+               
+               protected override void UpdatePlaceholderColor() => ApplyTheme();
+               void ApplyTheme() => _textInputLayout?.ApplyTheme(Element.TextColor, Element.PlaceholderColor);
+
+               protected internal override void UpdateFont()
+               {
+                       if (_disposed || _textInputLayout == null)
+                               return;
+
+                       base.UpdateFont();
+                       _textInputLayout.Typeface = Element.ToTypeface();
+                       _textInputEditText.SetTextSize(ComplexUnitType.Sp, (float)Element.FontSize);
+               }
+
+               protected override void Dispose(bool disposing)
+               {
+                       _disposed = true;
+                       base.Dispose(disposing);
+               }
+       }
+}
+#endif
\ No newline at end of file
index c2ac95b..c83ecf9 100644 (file)
@@ -13,7 +13,6 @@ namespace Xamarin.Forms.Platform.Android.Material
 {
        public sealed class MaterialEntryRenderer : EntryRendererBase<MaterialFormsTextInputLayout>
        {
-               bool _disposed;
                MaterialFormsEditText _textInputEditText;
                MaterialFormsTextInputLayout _textInputLayout;
 
@@ -40,60 +39,9 @@ namespace Xamarin.Forms.Platform.Android.Material
                protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
                {
                        base.OnElementChanged(e);
-                       var oldElement = e.OldElement;
-
-                       if (oldElement != null)
-                       {
-                               oldElement.PropertyChanged -= OnElementPropertyChanged;
-                               oldElement.FocusChangeRequested -= OnFocusChangeRequested;
-                       }
-
-                       if (e.NewElement != null)
-                               Element.FocusChangeRequested += OnFocusChangeRequested;
-
                        UpdateBackgroundColor();
                }
 
-
-               protected override void OnFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e)
-               {
-                       e.Result = true;
-
-                       if (e.Focus)
-                       {
-                               // use post being BeginInvokeOnMainThread will not delay on android
-                               Looper looper = Context.MainLooper;
-                               var handler = new Handler(looper);
-                               handler.Post(() =>
-                               {
-                                       _textInputEditText.RequestFocus();
-                               });
-                       }
-                       else
-                       {
-                               _textInputEditText.ClearFocus();
-                       }
-
-                       if (e.Focus)
-                               this.ShowKeyboard();
-                       else
-                               this.HideKeyboard();
-               }
-
-
-               protected override void Dispose(bool disposing)
-               {
-                       if (_disposed)
-                               return;
-
-                       _disposed = true;
-
-                       if (disposing && Element != null)
-                               Element.FocusChangeRequested -= OnFocusChangeRequested;
-
-                       base.Dispose(disposing);
-               }
-
                protected override void UpdateTextColor() => ApplyTheme();
 
                protected override void UpdateBackgroundColor()
@@ -107,7 +55,6 @@ namespace Xamarin.Forms.Platform.Android.Material
                protected internal override void UpdatePlaceHolderText()
                {
                        _textInputLayout.SetHint(Element.Placeholder, Element);
-                       Element.InvalidateMeasureNonVirtual(Internals.InvalidationTrigger.VerticalOptionsChanged);
                }
 
                
index c019c51..983d537 100644 (file)
@@ -61,7 +61,16 @@ namespace Xamarin.Forms.Platform.Android.Material
                        }
 
                        Context Context = textInputEditText.Context;
-                       textInputEditText.SetPadding((int)Context.ToPixels(rect.Left), (int)Context.ToPixels(rect.Top), (int)Context.ToPixels(rect.Right), (int)Context.ToPixels(rect.Bottom));
+                       var left = (int)Context.ToPixels(rect.Left);
+                       var top = (int)Context.ToPixels(rect.Top);
+                       var right = (int)Context.ToPixels(rect.Right);
+                       var bottom = (int)Context.ToPixels(rect.Bottom);
+
+                       if(textInputEditText.PaddingLeft != left ||
+                          textInputEditText.PaddingTop != top ||
+                          textInputEditText.PaddingRight != right ||
+                          textInputEditText.PaddingBottom != bottom)
+                               textInputEditText.SetPadding(left, top, right, bottom);
                }
        }
 }
index ca34eb0..9bf07a4 100644 (file)
@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
-using System.Linq;
 using Android.Content;
 using Android.Content.Res;
 using Android.OS;
@@ -10,25 +9,60 @@ using Android.Text.Method;
 using Android.Util;
 using Android.Views;
 using Java.Lang;
-using Xamarin.Forms.Internals;
-using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using Android.Widget;
 
 namespace Xamarin.Forms.Platform.Android
 {
-       public class EditorRenderer : ViewRenderer<Editor, FormsEditText>, ITextWatcher
+       public class EditorRenderer : EditorRendererBase<FormsEditText>
        {
-               bool _disposed;
+               TextColorSwitcher _hintColorSwitcher;
                TextColorSwitcher _textColorSwitcher;
-               ColorStateList defaultPlaceholdercolor;
 
                public EditorRenderer(Context context) : base(context)
                {
+               }
+
+               [Obsolete("This constructor is obsolete as of version 2.5. Please use EntryRenderer(Context) instead.")]
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public EditorRenderer()
+               {
+                       AutoPackage = false;
+               }
+
+               protected override FormsEditText CreateNativeControl()
+               {
+                       return new FormsEditText(Context);
+               }
+
+               protected override EditText EditText => Control;
+
+               protected override void UpdatePlaceholderColor()
+               {
+                       _hintColorSwitcher = _hintColorSwitcher ?? new TextColorSwitcher(EditText.HintTextColors, Element.UseLegacyColorManagement());
+                       _hintColorSwitcher.UpdateTextColor(EditText, Element.PlaceholderColor, EditText.SetHintTextColor);
+               }
+
+               protected override void UpdateTextColor()
+               {
+                       _textColorSwitcher = _textColorSwitcher ?? new TextColorSwitcher(EditText.TextColors, Element.UseLegacyColorManagement());
+                       _textColorSwitcher.UpdateTextColor(EditText, Element.TextColor);
+               }
+       }
+
+       public abstract class EditorRendererBase<TControl> : ViewRenderer<Editor, TControl>, ITextWatcher
+               where TControl : global::Android.Views.View
+       {
+               bool _disposed;
+               protected abstract EditText EditText { get; }
+
+               public EditorRendererBase(Context context) : base(context)
+               {
                        AutoPackage = false;
                }
 
                [Obsolete("This constructor is obsolete as of version 2.5. Please use EditorRenderer(Context) instead.")]
                [EditorBrowsable(EditorBrowsableState.Never)]
-               public EditorRenderer()
+               public EditorRendererBase()
                {
                        AutoPackage = false;
                }
@@ -52,16 +86,11 @@ namespace Xamarin.Forms.Platform.Android
                                ((IElementController)Element).SetValueFromRenderer(Editor.TextProperty, s.ToString());
                }
 
-               protected override FormsEditText CreateNativeControl()
-               {
-                       return new FormsEditText(Context);
-               }
-
                protected override void OnFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e)
                {
                        if (!e.Focus)
                        {
-                               Control.HideKeyboard();
+                               EditText.HideKeyboard();
                        }
 
                        base.OnFocusChangeRequested(sender, e);
@@ -71,7 +100,7 @@ namespace Xamarin.Forms.Platform.Android
                                // Post this to the main looper queue so it doesn't happen until the other focus stuff has resolved
                                // Otherwise, ShowKeyboard will be called before this control is truly focused, and we will potentially
                                // be displaying the wrong keyboard
-                               Control?.PostShowKeyboard();
+                               EditText?.PostShowKeyboard();
                        }
                }
 
@@ -85,21 +114,16 @@ namespace Xamarin.Forms.Platform.Android
                                edit = CreateNativeControl();
 
                                SetNativeControl(edit);
-                               edit.AddTextChangedListener(this);
-                               if(edit is IFormsEditText formsEditText)
+                               EditText.AddTextChangedListener(this);
+                               if(EditText is IFormsEditText formsEditText)
                                        formsEditText.OnKeyboardBackPressed += OnKeyboardBackPressed;
-
-                               var useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
-                               _textColorSwitcher = new TextColorSwitcher(edit.TextColors, useLegacyColorManagement);
-
-                               defaultPlaceholdercolor = Control.HintTextColors;
                        }
 
-                       edit.SetSingleLine(false);
-                       edit.Gravity = GravityFlags.Top;
+                       EditText.SetSingleLine(false);
+                       EditText.Gravity = GravityFlags.Top;
                        if ((int)Build.VERSION.SdkInt > 16)
-                               edit.TextAlignment = global::Android.Views.TextAlignment.ViewStart;
-                       edit.SetHorizontallyScrolling(false);
+                               EditText.TextAlignment = global::Android.Views.TextAlignment.ViewStart;
+                       EditText.SetHorizontallyScrolling(false);
 
                        UpdateText();
                        UpdateInputType();
@@ -152,7 +176,7 @@ namespace Xamarin.Forms.Platform.Android
 
                        if (disposing)
                        {
-                               if (Control != null && Control is IFormsEditText formsEditText)
+                               if (EditText != null && EditText is IFormsEditText formsEditText)
                                {
                                        formsEditText.OnKeyboardBackPressed -= OnKeyboardBackPressed;
                                }
@@ -175,16 +199,16 @@ namespace Xamarin.Forms.Platform.Android
                                ElementController.SendCompleted();
                }
 
-               void UpdateFont()
+               protected internal virtual void UpdateFont()
                {
-                       Control.Typeface = Element.ToTypeface();
-                       Control.SetTextSize(ComplexUnitType.Sp, (float)Element.FontSize);
+                       EditText.Typeface = Element.ToTypeface();
+                       EditText.SetTextSize(ComplexUnitType.Sp, (float)Element.FontSize);
                }
 
                void UpdateInputType()
                {
                        Editor model = Element;
-                       FormsEditText edit = Control;
+                       var edit = EditText;
                        var keyboard = model.Keyboard;
 
                        edit.InputType = keyboard.ToInputType() | InputTypes.TextFlagMultiLine;
@@ -212,43 +236,34 @@ namespace Xamarin.Forms.Platform.Android
                {
                        string newText = Element.Text ?? "";
 
-                       if (Control.Text == newText)
+                       if (EditText.Text == newText)
                                return;
 
-                       Control.Text = newText;
-                       Control.SetSelection(newText.Length);
+                       EditText.Text = newText;
+                       EditText.SetSelection(newText.Length);
                }
 
-               void UpdateTextColor()
-               {
-                       _textColorSwitcher?.UpdateTextColor(Control, Element.TextColor);
-               }
+               abstract protected void UpdateTextColor();
 
-               void UpdatePlaceholderText()
+               internal protected virtual void UpdatePlaceholderText()
                {
-                       if (Control.Hint == Element.Placeholder)
+                       if (EditText.Hint == Element.Placeholder)
                                return;
 
-                       Control.Hint = Element.Placeholder;
+                       EditText.Hint = Element.Placeholder;
                }
 
-               void UpdatePlaceholderColor()
-               {
-                       if (Element.PlaceholderColor == Color.Default)
-                               Control.SetHintTextColor(defaultPlaceholdercolor);
-                       else
-                               Control.SetHintTextColor(Element.PlaceholderColor.ToAndroid());
-               }
+               abstract protected void UpdatePlaceholderColor();
 
                void OnKeyboardBackPressed(object sender, EventArgs eventArgs)
                {
                        ElementController?.SendCompleted();
-                       Control?.ClearFocus();
+                       EditText?.ClearFocus();
                }
 
                void UpdateMaxLength()
                {
-                       var currentFilters = new List<IInputFilter>(Control?.GetFilters() ?? new IInputFilter[0]);
+                       var currentFilters = new List<IInputFilter>(EditText?.GetFilters() ?? new IInputFilter[0]);
 
                        for (var i = 0; i < currentFilters.Count; i++)
                        {
@@ -261,21 +276,21 @@ namespace Xamarin.Forms.Platform.Android
 
                        currentFilters.Add(new InputFilterLengthFilter(Element.MaxLength));
 
-                       Control?.SetFilters(currentFilters.ToArray());
+                       EditText?.SetFilters(currentFilters.ToArray());
 
-                       var currentControlText = Control?.Text;
+                       var currentControlText = EditText?.Text;
 
                        if (currentControlText.Length > Element.MaxLength)
-                               Control.Text = currentControlText.Substring(0, Element.MaxLength);
+                               EditText.Text = currentControlText.Substring(0, Element.MaxLength);
                }
 
                void UpdateIsReadOnly()
                {
                        bool isReadOnly = !Element.IsReadOnly;
 
-                       Control.FocusableInTouchMode = isReadOnly;
-                       Control.Focusable = isReadOnly;
-                       Control.SetCursorVisible(isReadOnly);
+                       EditText.FocusableInTouchMode = isReadOnly;
+                       EditText.Focusable = isReadOnly;
+                       EditText.SetCursorVisible(isReadOnly);
                }
        }
 }
\ No newline at end of file
index ef712ba..c64da8e 100644 (file)
@@ -40,7 +40,7 @@ namespace Xamarin.Forms.Platform.Android
                {
                        base.UpdateIsReadOnly();
                        bool isReadOnly = !Element.IsReadOnly;
-                       Control.SetCursorVisible(isReadOnly);
+                       EditText.SetCursorVisible(isReadOnly);
                }
 
                protected override void UpdatePlaceholderColor()
@@ -86,7 +86,7 @@ namespace Xamarin.Forms.Platform.Android
                        // Fire Completed and dismiss keyboard for hardware / physical keyboards
                        if (actionId == ImeAction.Done || actionId == _currentInputImeFlag || (actionId == ImeAction.ImeNull && e.KeyCode == Keycode.Enter && e.Action == KeyEventActions.Up))
                        {
-                               Control.ClearFocus();
+                               EditText.ClearFocus();
                                v.HideKeyboard();
                                ((IEntryController)Element).SendCompleted();
                        }
@@ -114,7 +114,7 @@ namespace Xamarin.Forms.Platform.Android
                {
                        if (!e.Focus)
                        {
-                               Control.HideKeyboard();
+                               EditText.HideKeyboard();
                        }
 
                        base.OnFocusChangeRequested(sender, e);
@@ -124,7 +124,7 @@ namespace Xamarin.Forms.Platform.Android
                                // Post this to the main looper queue so it doesn't happen until the other focus stuff has resolved
                                // Otherwise, ShowKeyboard will be called before this control is truly focused, and we will potentially
                                // be displaying the wrong keyboard
-                               Control?.PostShowKeyboard();
+                               EditText?.PostShowKeyboard();
                        }
                }
 
@@ -179,7 +179,7 @@ namespace Xamarin.Forms.Platform.Android
 
                        if (disposing)
                        {
-                               if (Control != null && Control is IFormsEditText formsEditContext)
+                               if (EditText != null && EditText is IFormsEditText formsEditContext)
                                {
                                        formsEditContext.OnKeyboardBackPressed -= OnKeyboardBackPressed;
                                        formsEditContext.SelectionChanged -= SelectionChanged;
@@ -203,10 +203,10 @@ namespace Xamarin.Forms.Platform.Android
                                if (EditText.Text != Element.Text)
                                {
                                        EditText.Text = Element.Text;
-                                       if (Control.IsFocused)
+                                       if (EditText.IsFocused)
                                        {
                                                EditText.SetSelection(EditText.Text.Length);
-                                               Control.ShowKeyboard();
+                                               EditText.ShowKeyboard();
                                        }
                                }
                        }
@@ -382,10 +382,10 @@ namespace Xamarin.Forms.Platform.Android
 
                void UpdateCursorSelection()
                {
-                       if (_nativeSelectionIsUpdating || Control == null || Element == null)
+                       if (_nativeSelectionIsUpdating || Control == null || Element == null || EditText == null)
                                return;
 
-                       if (!Element.IsReadOnly && Control.RequestFocus())
+                       if (!Element.IsReadOnly && EditText.RequestFocus())
                        {
                                try
                                {
@@ -472,8 +472,8 @@ namespace Xamarin.Forms.Platform.Android
                {
                        bool isReadOnly = !Element.IsReadOnly;
 
-                       Control.FocusableInTouchMode = isReadOnly;
-                       Control.Focusable = isReadOnly;
+                       EditText.FocusableInTouchMode = isReadOnly;
+                       EditText.Focusable = isReadOnly;
                }
        }
 }
index a3ac088..7a492bb 100644 (file)
     <Compile Include="IBorderVisualElementRenderer.cs" />
     <Compile Include="IDeviceInfoProvider.cs" />
     <Compile Include="ITabStop.cs" />
+    <Compile Include="Material\MaterialEditorRenderer.cs" />
     <Compile Include="Material\MaterialFormsTextInputLayoutBase.cs" />
     <Compile Include="Material\MaterialFormsEditTextBase.cs" />
     <Compile Include="Material\MaterialFormsEditTextManager.cs" />
index 47d0577..6bfdeca 100644 (file)
@@ -7,11 +7,94 @@ using RectangleF = CoreGraphics.CGRect;
 
 namespace Xamarin.Forms.Platform.iOS
 {
-       public class EditorRenderer : ViewRenderer<Editor, UITextView>
+       public class EditorRenderer : EditorRendererBase<UITextView>
+       {
+               UILabel _placeholderLabel;
+
+               public EditorRenderer()
+               {
+                       Frame = new RectangleF(0, 20, 320, 40);
+               }
+
+               protected override UITextView CreateNativeControl()
+               {
+                       return new FormsUITextView(RectangleF.Empty);
+               }
+
+               protected override UITextView TextView => Control;
+
+               protected internal override void UpdateText()
+               {
+                       base.UpdateText();
+                       _placeholderLabel.Hidden = !string.IsNullOrEmpty(TextView.Text);
+               }
+
+               protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
+               {
+                       // create label so it can get updated during the initial setup loop
+                       _placeholderLabel = new UILabel
+                       {
+                               BackgroundColor = UIColor.Clear
+                       };
+
+                       base.OnElementChanged(e);
+
+                       CreatePlaceholderLabel();
+               }
+
+               protected internal override void UpdateFont()
+               {
+                       base.UpdateFont();
+                       _placeholderLabel.Font = Element.ToUIFont();
+               }
+
+               protected internal override void UpdatePlaceholderText()
+               {
+                       _placeholderLabel.Text = Element.Placeholder;
+               }
+
+               protected internal override void UpdatePlaceholderColor()
+               {
+                       if (Element.PlaceholderColor == Color.Default)
+                               _placeholderLabel.TextColor = UIColor.DarkGray;
+                       else
+                               _placeholderLabel.TextColor = Element.PlaceholderColor.ToUIColor();
+               }
+
+               void CreatePlaceholderLabel()
+               {
+                       Control.AddSubview(_placeholderLabel);
+
+                       var edgeInsets = TextView.TextContainerInset;
+                       var lineFragmentPadding = TextView.TextContainer.LineFragmentPadding;
+
+                       var vConstraints = NSLayoutConstraint.FromVisualFormat(
+                               "V:|-" + edgeInsets.Top + "-[_placeholderLabel]-" + edgeInsets.Bottom + "-|", 0, new NSDictionary(),
+                               NSDictionary.FromObjectsAndKeys(
+                                       new NSObject[] { _placeholderLabel }, new NSObject[] { new NSString("_placeholderLabel") })
+                       );
+
+                       var hConstraints = NSLayoutConstraint.FromVisualFormat(
+                               "H:|-" + lineFragmentPadding + "-[_placeholderLabel]-" + lineFragmentPadding + "-|",
+                               0, new NSDictionary(),
+                               NSDictionary.FromObjectsAndKeys(
+                                       new NSObject[] { _placeholderLabel }, new NSObject[] { new NSString("_placeholderLabel") })
+                       );
+
+                       _placeholderLabel.TranslatesAutoresizingMaskIntoConstraints = false;
+
+                       Control.AddConstraints(hConstraints);
+                       Control.AddConstraints(vConstraints);
+               }
+
+       }
+
+       public abstract class EditorRendererBase<TControl> : ViewRenderer<Editor, TControl>
+               where TControl : UIView
        {
                bool _disposed;
                IEditorController ElementController => Element;
-               UILabel _placeholderLabel;
+               protected abstract UITextView TextView { get; }
 
                protected override void Dispose(bool disposing)
                {
@@ -24,11 +107,12 @@ namespace Xamarin.Forms.Platform.iOS
                        {
                                if (Control != null)
                                {
-                                       Control.Changed -= HandleChanged;
-                                       Control.Started -= OnStarted;
-                                       Control.Ended -= OnEnded;
-                                       Control.ShouldChangeText -= ShouldChangeText;
-                                       (Control as FormsUITextView).FrameChanged -= OnFrameChanged;
+                                       TextView.Changed -= HandleChanged;
+                                       TextView.Started -= OnStarted;
+                                       TextView.Ended -= OnEnded;
+                                       TextView.ShouldChangeText -= ShouldChangeText;
+                                       if(Control is IFormsUITextView formsUITextView)
+                                               formsUITextView.FrameChanged -= OnFrameChanged;
                                }
                        }
 
@@ -44,7 +128,7 @@ namespace Xamarin.Forms.Platform.iOS
 
                        if (Control == null)
                        {
-                               SetNativeControl(new FormsUITextView(RectangleF.Empty));
+                               SetNativeControl(CreateNativeControl());
 
                                if (Device.Idiom == TargetIdiom.Phone)
                                {
@@ -55,25 +139,24 @@ namespace Xamarin.Forms.Platform.iOS
                                        var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
                                        var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (o, a) =>
                                        {
-                                               Control.ResignFirstResponder();
+                                               TextView.ResignFirstResponder();
                                                ElementController.SendCompleted();
                                        });
                                        accessoryView.SetItems(new[] { spacer, doneButton }, false);
-                                       Control.InputAccessoryView = accessoryView;
+                                       TextView.InputAccessoryView = accessoryView;
                                }
 
-                               Control.Changed += HandleChanged;
-                               Control.Started += OnStarted;
-                               Control.Ended += OnEnded;
-                               Control.ShouldChangeText += ShouldChangeText;
+                               TextView.Changed += HandleChanged;
+                               TextView.Started += OnStarted;
+                               TextView.Ended += OnEnded;
+                               TextView.ShouldChangeText += ShouldChangeText;
                        }
 
-                       CreatePlaceholderLabel();
+                       UpdateFont();
                        UpdatePlaceholderText();
                        UpdatePlaceholderColor();
                        UpdateTextColor();
                        UpdateText();
-                       UpdateFont();
                        UpdateKeyboard();
                        UpdateEditable();
                        UpdateTextAlignment();
@@ -83,9 +166,9 @@ namespace Xamarin.Forms.Platform.iOS
                        UpdateUserInteraction();
                }
 
-               private void UpdateAutoSizeOption()
+               protected internal virtual void UpdateAutoSizeOption()
                {
-                       if (Control is FormsUITextView textView)
+                       if (Control is IFormsUITextView textView)
                        {
                                textView.FrameChanged -= OnFrameChanged;
                                if (Element.AutoSize == EditorAutoSizeOption.TextChanges)
@@ -93,37 +176,6 @@ namespace Xamarin.Forms.Platform.iOS
                        }
                }
 
-               void CreatePlaceholderLabel()
-               {
-                       _placeholderLabel = new UILabel
-                       {
-                               BackgroundColor = UIColor.Clear
-                       };
-
-                       Control.AddSubview(_placeholderLabel);
-
-                       var edgeInsets = Control.TextContainerInset;
-                       var lineFragmentPadding = Control.TextContainer.LineFragmentPadding;
-
-                       var vConstraints = NSLayoutConstraint.FromVisualFormat(
-                       "V:|-" + edgeInsets.Top + "-[_placeholderLabel]-" + edgeInsets.Bottom + "-|", 0, new NSDictionary(),
-                       NSDictionary.FromObjectsAndKeys(
-                               new NSObject[] { _placeholderLabel }, new NSObject[] { new NSString("_placeholderLabel") })
-               );
-
-                       var hConstraints = NSLayoutConstraint.FromVisualFormat(
-                               "H:|-" + lineFragmentPadding + "-[_placeholderLabel]-" + lineFragmentPadding + "-|",
-                               0, new NSDictionary(),
-                               NSDictionary.FromObjectsAndKeys(
-                                       new NSObject[] { _placeholderLabel }, new NSObject[] { new NSString("_placeholderLabel") })
-                       );
-
-                       _placeholderLabel.TranslatesAutoresizingMaskIntoConstraints = false;
-
-                       Control.AddConstraints(hConstraints);
-                       Control.AddConstraints(vConstraints);
-               }
-
                protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
                {
                        base.OnElementPropertyChanged(sender, e);
@@ -160,7 +212,7 @@ namespace Xamarin.Forms.Platform.iOS
 
                void HandleChanged(object sender, EventArgs e)
                {
-                       ElementController.SetValueFromRenderer(Editor.TextProperty, Control.Text);
+                       ElementController.SetValueFromRenderer(Editor.TextProperty, TextView.Text);
                }
 
                private void OnFrameChanged(object sender, EventArgs e)
@@ -170,14 +222,14 @@ namespace Xamarin.Forms.Platform.iOS
                        // will resize until it can't anymore and thus it should never be scrolled until the Frame can't increase in size
                        if (Element.AutoSize == EditorAutoSizeOption.TextChanges)
                        {
-                               Control.ScrollRangeToVisible(new NSRange(0, 0));
+                               TextView.ScrollRangeToVisible(new NSRange(0, 0));
                        }
                }
 
                void OnEnded(object sender, EventArgs eventArgs)
                {
-                       if (Control.Text != Element.Text)
-                               ElementController.SetValueFromRenderer(Editor.TextProperty, Control.Text);
+                       if (TextView.Text != Element.Text)
+                               ElementController.SetValueFromRenderer(Editor.TextProperty, TextView.Text);
 
                        Element.SetValue(VisualElement.IsFocusedPropertyKey, false);
                        ElementController.SendCompleted();
@@ -190,87 +242,76 @@ namespace Xamarin.Forms.Platform.iOS
 
                void UpdateEditable()
                {
-                       Control.Editable = Element.IsEnabled;
-                       Control.UserInteractionEnabled = Element.IsEnabled;
+                       TextView.Editable = Element.IsEnabled;
+                       TextView.UserInteractionEnabled = Element.IsEnabled;
 
-                       if (Control.InputAccessoryView != null)
-                               Control.InputAccessoryView.Hidden = !Element.IsEnabled;
+                       if (TextView.InputAccessoryView != null)
+                               TextView.InputAccessoryView.Hidden = !Element.IsEnabled;
                }
 
-               void UpdateFont()
+               protected internal virtual void UpdateFont()
                {
                        var font = Element.ToUIFont();
-                       Control.Font = font;
-                       _placeholderLabel.Font = font;
+                       TextView.Font = font;
                }
 
                void UpdateKeyboard()
                {
                        var keyboard = Element.Keyboard;
-                       Control.ApplyKeyboard(keyboard);
+                       TextView.ApplyKeyboard(keyboard);
                        if (!(keyboard is Internals.CustomKeyboard))
                        {
                                if (Element.IsSet(Xamarin.Forms.InputView.IsSpellCheckEnabledProperty))
                                {
                                        if (!Element.IsSpellCheckEnabled)
                                        {
-                                               Control.SpellCheckingType = UITextSpellCheckingType.No;
+                                               TextView.SpellCheckingType = UITextSpellCheckingType.No;
                                        }
                                }
                                if (Element.IsSet(Editor.IsTextPredictionEnabledProperty))
                                {
                                        if (!Element.IsTextPredictionEnabled)
                                        {
-                                               Control.AutocorrectionType = UITextAutocorrectionType.No;
+                                               TextView.AutocorrectionType = UITextAutocorrectionType.No;
                                        }
                                }
                        }
-                       Control.ReloadInputViews();
+                       TextView.ReloadInputViews();
                }
 
-               void UpdateText()
+               protected internal virtual void UpdateText()
                {
-                       if (Control.Text != Element.Text)
+                       if (TextView.Text != Element.Text)
                        {
-                               Control.Text = Element.Text;
+                               TextView.Text = Element.Text;
                        }
-                       _placeholderLabel.Hidden = !string.IsNullOrEmpty(Control.Text);
                }
 
-               void UpdatePlaceholderText()
-               {
-                       _placeholderLabel.Text = Element.Placeholder;
-               }
+               protected internal abstract void UpdatePlaceholderText();
+               protected internal abstract void UpdatePlaceholderColor();
 
-               void UpdatePlaceholderColor()
-               {
-                       if (Element.PlaceholderColor == Color.Default)
-                               _placeholderLabel.TextColor = UIColor.DarkGray;
-                       else
-                               _placeholderLabel.TextColor = Element.PlaceholderColor.ToUIColor();
-               }
 
                void UpdateTextAlignment()
                {
-                       Control.UpdateTextAlignment(Element);
+                       TextView.UpdateTextAlignment(Element);
                }
 
-               void UpdateTextColor()
+               protected internal virtual void UpdateTextColor()
                {
                        var textColor = Element.TextColor;
 
                        if (textColor.IsDefault)
-                               Control.TextColor = UIColor.Black;
+                               TextView.TextColor = UIColor.Black;
                        else
-                               Control.TextColor = textColor.ToUIColor();
+                               TextView.TextColor = textColor.ToUIColor();
                }
 
                void UpdateMaxLength()
                {
-                       var currentControlText = Control.Text;
+                       var currentControlText = TextView.Text;
 
                        if (currentControlText.Length > Element.MaxLength)
-                               Control.Text = currentControlText.Substring(0, Element.MaxLength);
+                               TextView.Text = currentControlText.Substring(0, Element.MaxLength);
                }
 
                bool ShouldChangeText(UITextView textView, NSRange range, string text)
@@ -281,6 +322,9 @@ namespace Xamarin.Forms.Platform.iOS
 
                void UpdateReadOnly()
                {
+                       TextView.UserInteractionEnabled = !Element.IsReadOnly;
+
+                       // Control and TextView might be different
                        Control.UserInteractionEnabled = !Element.IsReadOnly;
                }
 
@@ -292,9 +336,8 @@ namespace Xamarin.Forms.Platform.iOS
                                UpdateEditable();
                }
 
-               internal class FormsUITextView : UITextView
+               internal class FormsUITextView : UITextView, IFormsUITextView
                {
-                       public event EventHandler ContentSizeChanged;
                        public event EventHandler FrameChanged;
 
                        public FormsUITextView(RectangleF frame) : base(frame)
@@ -314,19 +357,11 @@ namespace Xamarin.Forms.Platform.iOS
                                        FrameChanged?.Invoke(this, EventArgs.Empty);
                                }
                        }
-
-                       public override CGSize ContentSize
-                       {
-                               get
-                               {
-                                       return base.ContentSize;
-                               }
-                               set
-                               {
-                                       base.ContentSize = value;
-                                       ContentSizeChanged?.Invoke(this, EventArgs.Empty);
-                               }
-                       }
                }
        }
-}
+
+       internal interface IFormsUITextView
+       {
+               event EventHandler FrameChanged;
+       }
+}
\ No newline at end of file