4
votes

I Currently have entry fields in my Xamarin forms app that just have bottom borders on iOS that work perfectly using the following custom renderer:

using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using UIKit;
using YOUTNAMESPACE.iOS;
using System.ComponentModel;
using CoreAnimation;
using Foundation;

[assembly: ExportRenderer (typeof(YOUTNAMESPACE.LineEntry), typeof(LineEntryRenderer))]
namespace YOUTNAMESPACE.iOS
{
    public class LineEntryRenderer: EntryRenderer
    {
        protected override void OnElementChanged (ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged (e);

            if (Control != null) {
                Control.BorderStyle = UITextBorderStyle.None;

                var view = (Element as LineEntry);
                if (view != null) {
                    DrawBorder (view);
                    SetFontSize (view);
                    SetPlaceholderTextColor (view);
                }
            }
        }

        protected override void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged (sender, e);

            var view = (LineEntry)Element;

            if (e.PropertyName.Equals (view.BorderColor))
                DrawBorder (view);
            if (e.PropertyName.Equals (view.FontSize))
                SetFontSize (view);
            if (e.PropertyName.Equals (view.PlaceholderColor))
                SetPlaceholderTextColor (view);
        }

        void DrawBorder (LineEntry view)
        {
            var borderLayer = new CALayer ();
            borderLayer.MasksToBounds = true;
            borderLayer.Frame = new CoreGraphics.CGRect (0f, Frame.Height / 2, Frame.Width, 1f);
            borderLayer.BorderColor = view.BorderColor.ToCGColor ();
            borderLayer.BorderWidth = 1.0f;

            Control.Layer.AddSublayer (borderLayer);
            Control.BorderStyle = UITextBorderStyle.None;
        }

        void SetFontSize (LineEntry view)
        {
            if (view.FontSize != Font.Default.FontSize)
                Control.Font = UIFont.SystemFontOfSize ((System.nfloat)view.FontSize);
            else if (view.FontSize == Font.Default.FontSize)
                Control.Font = UIFont.SystemFontOfSize (17f);
        }

        void SetPlaceholderTextColor (LineEntry view)
        {
            if (string.IsNullOrEmpty (view.Placeholder) == false && view.PlaceholderColor != Color.Default) {
                var placeholderString = new NSAttributedString (view.Placeholder, 
                                            new UIStringAttributes { ForegroundColor = view.PlaceholderColor.ToUIColor () });
                Control.AttributedPlaceholder = placeholderString;
            }
        }
    }
}

However when I apply the same logic to an Editor (a multiline UITextField) the border appears where it should, however on typing multiple lines, the border moves up the editor with the text on pressing order. How can I avoid this so that the bottom border simply stays in the same place?

1

1 Answers

4
votes

Solution

In your custom renderer for a Xamarin Forms Editor, you need to do two things:

  1. private CALayer borderLayer;
    int sublayerNumber = 0;
    protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
    {
        base.OnElementChanged(e);
    
        if (Control != null)
        {
            borderLayer = new CALayer();
            Control.Layer.AddSublayer(borderLayer);
            sublayerNumber = Control.Layer.Sublayers.Length - 1;
    

You must make the borderLayer global, then make another global variable called sublayerNumber and then inside of OnElementChanged, after checking if the control exists, instantiate the CALayer and then add the blank layer to the sublayers and get its index in the sublayers array.

2.

 public override void LayoutSubviews()
        {
            base.LayoutSubviews();
            Control.Layer.Sublayers[sublayerNumber].MasksToBounds = true;
            Control.Layer.Sublayers[sublayerNumber].Frame = new CoreGraphics.CGRect(0f, Frame.Height - 5, Frame.Width, 1f);
            Control.Layer.Sublayers[sublayerNumber].BorderColor = Color.FromHex("YOUR-HEX-CODE-HERE").ToCGColor();
            Control.Layer.Sublayers[sublayerNumber].BorderWidth = 1.0f;
        }

Override the method LayoutSubviews and then edit the specific layer that we created in OnElementChanged with the most recent frame height and width etc.

Reason

Before we were adding the subview before the layoutsubviews method was called causing it to be displayed incorrectly.

The reason we create one CALayer and then add it to the sublayers and then edit it every time LayoutSubviews is called is so that if your Editor (UITextField in iOS) expands in size or contracts in size the border will follow the container itself. This is important if the user has added this additional functionality to allow this.

Hope this helps anyone who has had the same problem as I was pulling my hair out for at least a day!