简体   繁体   中英

UILabel with padding in Xamarin.iOS?

I'm trying to create a UILabel with padding in my Xamarin.iOS app. The most popular solution in native Objective-C apps is overriding drawTextInRect :

- (void)drawTextInRect:(CGRect)rect {
    UIEdgeInsets insets = {0, 5, 0, 5};
    return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}

As simple as this seems, I can't quite figure out how to translate it to C#. Here's my best stab at it:

internal class PaddedLabel : UILabel
{
    public UIEdgeInsets Insets { get; set; }

    public override void DrawText(RectangleF rect)
    {
        var padded = new RectangleF(rect.X + Insets.Left, rect.Y, rext.Width + Insets.Left + Insets.Right, rect.Height);

        base.DrawText(padded);
    }
}

This does seem to move the label's text, but it doesn't resize the label.

I think the main issue is that I can't find the Xamarin equivalent of UIEdgeInsetsInsetRect .

Any suggestions?

The C# equivalent of the ObjC function UIEdgeInsetsInsetRect is a instance method of UIEdgeInsets named InsetRect and it's not identical to your RectangleF calculations (which is likely your problem).

To use it you can do:

public override void DrawText(RectangleF rect)
{
    base.DrawText (Insets.InsetRect (rect));
}

You have to override both DrawText and TextRectForBounds . If you don't override TextRectForBounds , the text will be clipped. Actually, you override this method to compensate the space which is occupied by padding and ask iOS to draw the text in a bigger rectangle.

public partial class LabelWithBorder
    : UILabel
{
    private UIEdgeInsets EdgeInsets = new UIEdgeInsets(5, 5, 5, 5);
    private UIEdgeInsets InverseEdgeInsets = new UIEdgeInsets(-5, -5, -5, -5);

    public LabelWithBorder(IntPtr handle) : base(handle)
    {
    }

    public override CoreGraphics.CGRect TextRectForBounds(CoreGraphics.CGRect bounds, nint numberOfLines)
    {
        var textRect = base.TextRectForBounds(EdgeInsets.InsetRect(bounds), numberOfLines);
        return InverseEdgeInsets.InsetRect(textRect);
    }

    public override void DrawText(CoreGraphics.CGRect rect)
    {
        base.DrawText(EdgeInsets.InsetRect(rect));
    }
}

I have created a generic Padding UIView class that wraps any IOS UI element that is derived from UIView.

Basically it nests the desired UIView into another view and takes care of all the padding work.

usage:

var myPaddedView = new PaddedUIView<UILabel>();
myPaddedView.Frame = TheActualFrame;
myPaddedView.Padding = 15f
myPaddedView.NestedView.Text = "Hello padded world";  // all the label Properties are available without side effects

Here is the class:

public class PaddedUIView<T>: UIView where T : UIView, new()
{
    private float _padding;
    private T _nestedView;

    public PaddedUIView()
    {
        Initialize();
    }

    public PaddedUIView(RectangleF bounds)
        : base(bounds)
    {
        Initialize();
    }

    void Initialize()
    {   
        if(_nestedView == null)
        { 
            _nestedView = new T();
            this.AddSubview(_nestedView);
        }
        _nestedView.Frame =  new RectangleF(_padding,_padding,Frame.Width - 2 * _padding, Frame.Height - 2 * _padding);
    }

    public T NestedView
    { 
        get { return _nestedView; }
    }

    public float Padding
    {
        get { return _padding; }
        set { if(value != _padding) { _padding = value; Initialize(); }}
    }

    public override RectangleF Frame
    {
        get { return base.Frame; }
        set { base.Frame = value; Initialize(); }
    }
}

Rather than overriding DrawText() in the subclass of UILabel, override it's intrinsic content size. This way auto-layout takes the padding into consideration. For example here's my derived class of UILabel:

public class PaddedLabel : UILabel
{
    private readonly float _top;
    private readonly float _left;
    private readonly float _right;
    private readonly float _bottom;

    public PaddedLabel(float top, float left, float right, float bottom)
    {
        _top = top;
        _left = left;
        _right = right;
        _bottom = bottom;
    }

    public override CGSize IntrinsicContentSize => new CGSize(
        base.IntrinsicContentSize.Width + _left + _right,
        base.IntrinsicContentSize.Height + _top + _bottom
    );
}

The correct way to do this seems to have slightly changed since the original answer was given, and it took me awhile to figure out the new correct syntax. Here it is for anyone else who stumbles across this. This code would put padding of 5 on both the left and right sides of the view. This is in Xamarin.iOS

public override void DrawText(CGRect rect)
{
     rect.X = 5; 
     rect.Width = rect.Width - 10; // or whatever padding settings you want
     base.DrawText(AlignmentRectInsets.InsetRect(rect));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM