12

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?

poupou
  • 43,413
  • 6
  • 77
  • 174
Josh Earl
  • 18,151
  • 15
  • 62
  • 91

6 Answers6

18

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));
}
poupou
  • 43,413
  • 6
  • 77
  • 174
  • Thanks! This appears to be the correct translation of the Objective-C version. It shifts the text over as expected, but the text is clipped to the right. In other words, the `UILabel` doesn't resize to accommodate the insets. That might be a separate issue, though. – Josh Earl Jan 16 '14 at 14:51
  • That code only change the drawing, so the `UILabel` is not aware of the inset (and won't change it's behaviour). – poupou Jan 16 '14 at 15:08
  • That makes sense. Any suggestions as to what I should look at to take the insets into consideration when determining the width of the label? I'd like it to resize dynamically if possible. – Josh Earl Jan 16 '14 at 15:12
  • I'm not sure what exact result you're looking for - but there's many similar questions on SO, e.g. http://stackoverflow.com/q/6854991/220643 that you might want to look into (and convert to C#). If nothing match your need then ask another (more precise, e.g. picture) question for help. – poupou Jan 16 '14 at 15:42
  • 1
    Thanks--I posted a follow-up with a screen shot: http://stackoverflow.com/questions/21167226/resizing-a-uilabel-to-accomodate-insets – Josh Earl Jan 16 '14 at 16:23
  • This seems to be depreciated now, see my answer below – jnel899 Dec 01 '17 at 19:56
7

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));
    }
}
CoyBit
  • 1,592
  • 1
  • 17
  • 19
  • This worked great, except I had to add a constructor that accepts a CGRect and passes it to the base constructor of that type. – Jim Wilcox May 27 '22 at 14:28
2

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
    );
}
masterwok
  • 4,868
  • 4
  • 34
  • 41
1

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(); }
    }
}
spaceMonster
  • 149
  • 3
  • 4
0

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));
}
jnel899
  • 563
  • 2
  • 8
  • 21
0

poupou's solution marked as the best answer doesn't work. I found another way to add padding to label.

    public class PaddedLabel : UILabel
    {
        public PaddedLabel(IntPtr intPtr) : base(intPtr)
        {
            TextAlignment = UITextAlignment.Center;
        }

        public override CGSize IntrinsicContentSize
        {
            get
            {
                var size = base.IntrinsicContentSize;

                return new CGSize(size + 16, size);
            }
        }
    }
matekome
  • 33
  • 6
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 24 '21 at 09:27