UILabel text margin [duplicate]
Asked Answered
M

38

334

I'm looking to set the left inset/margin of a UILabel and can't find a method to do so. The label has a background set so just changing its origin won't do the trick. It would be ideal to inset the text by 10px or so on the left hand side.

Miskolc answered 13/8, 2010 at 12:12 Comment(3)
once you subclass, for the insets it is simply, https://mcmap.net/q/23771/-can-i-adjust-a-cgrect-with-a-uiedgeinsetsNigrosine
Another approach may be embedding your label in a horizontal stackview and add uiview left / right of any width you wish.Mannino
We have finally, finally completely solved this problem. You must call super LAST in textRect: https://mcmap.net/q/23674/-adding-space-padding-to-a-uilabelNigrosine
C
470

I solved this by subclassing UILabel and overriding drawTextInRect: like this:

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

Swift 3.1:

override func drawText(in rect: CGRect) {
    let insets = UIEdgeInsets.init(top: 0, left: 5, bottom: 0, right: 5)
    super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}

Swift 4.2.1:

override func drawText(in rect: CGRect) {
    let insets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
    super.drawText(in: rect.inset(by: insets))
}

As you might have gathered, this is an adaptation of tc.'s answer. It has two advantages over that one:

  1. there's no need to trigger it by sending a sizeToFit message
  2. it leaves the label frame alone - handy if your label has a background and you don't want that to shrink
Centuple answered 1/3, 2011 at 13:50 Comment(16)
what is the point of "return" here?Returnee
You might want to check out this answer which properly deals with sizeToFit and auto layout.Pulmonate
I'm finding that this approach doesn't play nice with sizeWithFont:constrainedToSize:... frame resizing; is that anyone else's experience?Deena
You will also need to subclass editingRectForBounds: if you want to have the inset while entering text.Implement
It could be useful to add the insets as a property to the new class, so the user could adjust them as needed. e.g. @property (nonatomic) UIEdgeInsets edgeInsets;Dorotheadorothee
I had to override -sizeThatFits:(CGSize)size as well, since my subclass does some calculations for font size etc. Thanks, that's a brilliant solution!Mcneely
You should also override intrinsicContentSize so that it works with auto layout. I've added that to the sample code in @Brody's answer.Coven
I don't understand how this answer could get so many upvotes?! This approach will most probably cause many issue regarding lineBreakingMode and placement of ellipsis. The computed needed size for the string isn't equal the size given to drawing it, or am I wrong?Bloodthirsty
drawTextInRect is public so must be override public func drawTextInRectQuinta
I am getting runtime exception "can not cast..." . I want to do the same thing with a UILabel obtained from viewWithTag method of UITableViewCell. Anyone can help ?Lucius
Its not working for me, here is my code: https://mcmap.net/q/23772/-unable-to-add-margin-padding-to-text-in-uilabel-ios-swift/2652541Erivan
overriding drawTextInRect cannot work correctly for NSAttributedStringDehumidifier
I think the most simple way is adding some spaces to that particular string:[UILabel setText:[NSString stringWithFormat:@" %@ ", text]]; XDConn
This is good without constraints. When I apply auto layout constraints, this won't work..... because in the given width and height, it will apply the padding and text will be truncated.Yeager
This is quite wrong. The full answer: https://mcmap.net/q/23674/-adding-space-padding-to-a-uilabelNigrosine
Failed to set (topInset) user defined inspected property on (SLMobile.TextTopLeftLabel): [<SLMobile.TextTopLeftLabel 0x7f7f847e0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key topInset. Doesn't work.Ecthyma
A
154

For multiline text the left and the right margin can be set by using NSAttributedString.

NSMutableParagraphStyle *style =  [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.alignment = NSTextAlignmentJustified;
style.firstLineHeadIndent = 10.0f;
style.headIndent = 10.0f;
style.tailIndent = -10.0f;   

NSAttributedString *attrText = [[NSAttributedString alloc] initWithString:title attributes:@{ NSParagraphStyleAttributeName : style}];  

UILabel * label = [[UILabel alloc] initWithFrame:someFrame];
label.numberOfLines = 0;
label.attributedText = attrText;

Here is the above example adopted to Swift 5:

extension UILabel {
    func setMargins(margin: CGFloat = 10) {
        if let textString = self.text {
            var paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.firstLineHeadIndent = margin
            paragraphStyle.headIndent = margin
            paragraphStyle.tailIndent = -margin
            let attributedString = NSMutableAttributedString(string: textString)
            attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: attributedString.length))
            attributedText = attributedString
        }
    }
}
Angulate answered 16/1, 2014 at 7:19 Comment(5)
style.tailIndent should be set to -10.0fDespite
Can't add tailIndent = -10 in IB, allows only positive values :/Fiorenza
Need a top and bottom solution too.Stilly
Thank you very much.Alcina
Thanks but that extension doesn't work if you're already using attributedText on the label. However it contains the lines of the code needed to modify the attributed text in place.Drove
M
96

The best approach to add padding to a UILabel is to subclass UILabel and add an edgeInsets property. You then set the desired insets and the label will be drawn accordingly.

OSLabel.h

#import <UIKit/UIKit.h>

@interface OSLabel : UILabel

@property (nonatomic, assign) UIEdgeInsets edgeInsets;

@end

OSLabel.m

#import "OSLabel.h"

@implementation OSLabel

- (id)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.edgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
    }
    return self;
}

- (void)drawTextInRect:(CGRect)rect {
    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.edgeInsets)];
}

- (CGSize)intrinsicContentSize
{
    CGSize size = [super intrinsicContentSize];
    size.width  += self.edgeInsets.left + self.edgeInsets.right;
    size.height += self.edgeInsets.top + self.edgeInsets.bottom;
    return size;
}

@end
Mairamaire answered 9/7, 2013 at 20:24 Comment(7)
Or use TTTAttributedLabel (github.com/mattt/TTTAttributedLabel)Orthognathous
There's a problem with this solution - the last line of the label text will be cut off if the text is sufficiently long enough and if the insets are large enough. Just tried with the latest iOS 7.Sucrose
You should also override intrinsicContentSize to increase the intrinsic size to include the insets so that auto layout will work properly.Coven
It truncates the text when all four edgeInsets are set.Elusion
It truncates my text if I set numberOfLines = 0 :(Otherworldly
@AsifBilal you need to also override the textRectForBounds: method.Meggy
This is not quite the full answer. Note the image example showing the problem here: https://mcmap.net/q/23674/-adding-space-padding-to-a-uilabelNigrosine
S
76

Subclassing is a little cumbersome for such a simple case. An alternative is to simply add the UILabel with no background set to a UIView with the background set. Set the label's x to 10 and make the outer view's size 20 pixels wider than the label.

Sporocyte answered 15/6, 2011 at 20:2 Comment(4)
cough the outer view would need to be 20 points wider than the label. 10 on each side.Rattlehead
While subclassing would create a reusable component, this approach did save me time. Thank you, PeterLoftin
Keep in mind that the UILabel already is already subclassing a UIView so doing this would be a bit redundant, but it does achieve the goal.Galasyn
Oftentimes in XCode, we look for the complex answers. This the simplest and most effective and layering views can solve many more problems. In fact, back in the day, NS (NextStep) designed views for this very purpose. With the advent of constraints, many of us have forgotten just how simple (and fast) things can be by just using views.Plutonium
A
50

With Swift 3, you can have the desired effect by creating a subclass of UILabel. In this subclass, you will have to add a UIEdgeInsets property with the required insets and override drawText(in:) method, intrinsicContentSize property (for Auto layout code) and/or sizeThatFits(_:) method (for Springs & Struts code).

import UIKit

class PaddingLabel: UILabel {

    let padding: UIEdgeInsets

    // Create a new PaddingLabel instance programamtically with the desired insets
    required init(padding: UIEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)) {
        self.padding = padding
        super.init(frame: CGRect.zero)
    }

    // Create a new PaddingLabel instance programamtically with default insets
    override init(frame: CGRect) {
        padding = UIEdgeInsets.zero // set desired insets value according to your needs
        super.init(frame: frame)
    }

    // Create a new PaddingLabel instance from Storyboard with default insets
    required init?(coder aDecoder: NSCoder) {
        padding = UIEdgeInsets.zero // set desired insets value according to your needs
        super.init(coder: aDecoder)
    }

    override func drawText(in rect: CGRect) {
        super.drawText(in: UIEdgeInsetsInsetRect(rect, padding))
    }

    // Override `intrinsicContentSize` property for Auto layout code
    override var intrinsicContentSize: CGSize {
        let superContentSize = super.intrinsicContentSize
        let width = superContentSize.width + padding.left + padding.right
        let height = superContentSize.height + padding.top + padding.bottom
        return CGSize(width: width, height: height)
    }

    // Override `sizeThatFits(_:)` method for Springs & Struts code
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        let superSizeThatFits = super.sizeThatFits(size)
        let width = superSizeThatFits.width + padding.left + padding.right
        let heigth = superSizeThatFits.height + padding.top + padding.bottom
        return CGSize(width: width, height: heigth)
    }

}

The following example shows how to use PaddingLabel instances in a UIViewController:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var storyboardAutoLayoutLabel: PaddingLabel!
    let autoLayoutLabel = PaddingLabel(padding: UIEdgeInsets(top: 20, left: 40, bottom: 20, right: 40))
    let springsAndStructsLabel = PaddingLabel(frame: CGRect.zero)
    var textToDisplay = "Lorem ipsum dolor sit er elit lamet."

    override func viewDidLoad() {
        super.viewDidLoad()

        // Set autoLayoutLabel
        autoLayoutLabel.text = textToDisplay
        autoLayoutLabel.backgroundColor = .red
        autoLayoutLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(autoLayoutLabel)
        autoLayoutLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
        autoLayoutLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        // Set springsAndStructsLabel
        springsAndStructsLabel.text = textToDisplay
        springsAndStructsLabel.backgroundColor = .green
        view.addSubview(springsAndStructsLabel)
        springsAndStructsLabel.frame.origin = CGPoint(x: 30, y: 90)
        springsAndStructsLabel.sizeToFit()

        // Set storyboardAutoLayoutLabel
        storyboardAutoLayoutLabel.text = textToDisplay
        storyboardAutoLayoutLabel.backgroundColor = .blue
    }

    // Link this IBAction to a UIButton or a UIBarButtonItem in Storyboard
    @IBAction func updateLabelText(_ sender: Any) {
        textToDisplay = textToDisplay == "Lorem ipsum dolor sit er elit lamet." ? "Lorem ipsum." : "Lorem ipsum dolor sit er elit lamet."

        // autoLayoutLabel
        autoLayoutLabel.text = textToDisplay

        // springsAndStructsLabel
        springsAndStructsLabel.text = textToDisplay
        springsAndStructsLabel.sizeToFit()

        // storyboardAutoLayoutLabel
        storyboardAutoLayoutLabel.text = textToDisplay
    }

}
Antiworld answered 25/9, 2015 at 15:30 Comment(2)
The implementation is missing override for textRect(forBounds:limitedToNumberOfLines:) with call to super with bounds set to UIEdgeInsetsInsetRect(bounds, padding), otherwise the text might get truncated - size calculation is not correct when the view size constrained (so intrinsicContentSize is not used).Berry
can you add properties so we can use it in storyboard and not programmatically?Bedding
A
48

Swift version of Recycled Steel's answer + intrinsizeContentSize().

It supports a more traditional style of setting insets for other view objects with insets while being able to set insets in Interface Builder, i.e. insets are set like so programmatically:

label.insets = UIEdgeInsetsMake(0, 0, 5, 0)

Please let me know if there are any bugs.

Swift 5

@IBInspectable var topInset: CGFloat = 0.0
@IBInspectable var leftInset: CGFloat = 0.0
@IBInspectable var bottomInset: CGFloat = 0.0
@IBInspectable var rightInset: CGFloat = 0.0

var insets: UIEdgeInsets {
    get {
        return UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
    }
    set {
        topInset = newValue.top
        leftInset = newValue.left
        bottomInset = newValue.bottom
        rightInset = newValue.right
    }
}

override func drawText(in rect: CGRect) {
    super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}

override func sizeThatFits(_ size: CGSize) -> CGSize {
    var adjSize = super.sizeThatFits(size)
    adjSize.width += leftInset + rightInset
    adjSize.height += topInset + bottomInset
    
    return adjSize
}

override var intrinsicContentSize: CGSize {
    var contentSize = super.intrinsicContentSize
    contentSize.width += leftInset + rightInset
    contentSize.height += topInset + bottomInset
    
    return contentSize
}

Swift 4.2

@IBDesignable class InsetLabel: UILabel {
    @IBInspectable var topInset: CGFloat = 0.0
    @IBInspectable var leftInset: CGFloat = 0.0
    @IBInspectable var bottomInset: CGFloat = 0.0
    @IBInspectable var rightInset: CGFloat = 0.0
    
    var insets: UIEdgeInsets {
        get {
            return UIEdgeInsetsMake(topInset, leftInset, bottomInset, rightInset)
        }
        set {
            topInset = newValue.top
            leftInset = newValue.left
            bottomInset = newValue.bottom
            rightInset = newValue.right
        }
    }
    
    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: insets))
    }
    
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        var adjSize = super.sizeThatFits(size)
        adjSize.width += leftInset + rightInset
        adjSize.height += topInset + bottomInset
        
        return adjSize
    }
    
    override var intrinsicContentSize: CGSize {
        var contentSize = super.intrinsicContentSize
        contentSize.width += leftInset + rightInset
        contentSize.height += topInset + bottomInset
        
        return contentSize
    }
}

Swift 3

@IBDesignable class InsetLabel: UILabel {
    @IBInspectable var topInset: CGFloat = 0.0
    @IBInspectable var leftInset: CGFloat = 0.0
    @IBInspectable var bottomInset: CGFloat = 0.0
    @IBInspectable var rightInset: CGFloat = 0.0
    
    var insets: UIEdgeInsets {
        get {
            return UIEdgeInsetsMake(topInset, leftInset, bottomInset, rightInset)
        }
        set {
            topInset = newValue.top
            leftInset = newValue.left
            bottomInset = newValue.bottom
            rightInset = newValue.right
        }
    }
    
    override func drawText(in rect: CGRect) {
        super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
    }
    
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        var adjSize = super.sizeThatFits(size)
        adjSize.width += leftInset + rightInset
        adjSize.height += topInset + bottomInset
        
        return adjSize
    }
    
    override var intrinsicContentSize: CGSize {
        var contentSize = super.intrinsicContentSize
        contentSize.width += leftInset + rightInset
        contentSize.height += topInset + bottomInset
        
        return contentSize
    }
}

Swift 2.2

@IBDesignable class InsetLabel: UILabel {
    @IBInspectable var topInset: CGFloat = 0.0
    @IBInspectable var leftInset: CGFloat = 0.0
    @IBInspectable var bottomInset: CGFloat = 0.0
    @IBInspectable var rightInset: CGFloat = 0.0
    
    var insets: UIEdgeInsets {
        get {
            return UIEdgeInsetsMake(topInset, leftInset, bottomInset, rightInset)
        }
        set {
            topInset = newValue.top
            leftInset = newValue.left
            bottomInset = newValue.bottom
            rightInset = newValue.right
        }
    }
        
    override func drawTextInRect(rect: CGRect) {
        super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
    }
        
    override func sizeThatFits(size: CGSize) -> CGSize {
        var adjSize = super.sizeThatFits(size)
        adjSize.width += leftInset + rightInset
        adjSize.height += topInset + bottomInset
        
        return adjSize
    }
    
    override func intrinsicContentSize() -> CGSize {
        var contentSize = super.intrinsicContentSize()
        contentSize.width += leftInset + rightInset
        contentSize.height += topInset + bottomInset
        
        return contentSize
    }
}
Annulose answered 20/4, 2016 at 0:51 Comment(3)
I would suggest adding invalidateIntrinsicContentSize() and setNeedsDisplay() to the insets setter.Tilden
it works like charm for swift 4 too! Thank you @AnnuloseCaritta
label.insets = UIEdgeInsetsMake(0, 0, 5, 0) NOT label.inset = UIEdgeInsetsMake(0, 0, 5, 0)Lexicon
F
42

Edit: This is really old. There are better solutions above.

I ended up just adding some spaces to the text:

self.titleLabel.text = [NSString stringWithFormat:@"    %@", self.titleLabel.text];

Ugly yet effective, and no subclassing required.

You can try "\t" as well. For a generic solution please refer to the accepted answer

Farland answered 23/1, 2013 at 21:23 Comment(9)
do you think this is useful for multi line label :(Resistor
Spacing depends on font. I find this is a dirty hack.Cloverleaf
On single line, it's easy and works for me.Changsha
Sadly had to downvote this as it's a trick that works until it doesn't. It's no different than a hacky function that returns a hardcoded value, which wouldn't typically pass the SO sniff test.Mansized
@Mansized sniff test?Farland
@YarivNissim sorry, just a saying, not an actual test. I just mean I don't think the answer is up to typical Stack Overflow standards. Nice you've gotten many upvotes and it's helped people though. Cheers.Mansized
This is totally wrong. If you can notice it if you try to add equal spaced before and after the text. You'll see that the spaces after the text will be cut off.Dela
@MatteoGobbi this is from 2013Farland
Yes, even the most correct answer just below this one.Dela
C
29

You can also solve this by initializing your UILabel with a custom frame.

    CGRect initialFrame = CGRectMake(0, 0, 100, 100);
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0, 10, 0, 0);
    CGRect paddedFrame = UIEdgeInsetsInsetRect(initialFrame, contentInsets);

    self.label = [[UILabel alloc] initWithFrame:paddedFrame];

Nod to CGRect Tricks.

Cureton answered 30/10, 2013 at 0:9 Comment(2)
yeah but if the label has a background this is uselessReturnee
The initial frame is pretty much ignored in auto-layout.Tetracaine
A
19

and an @IBDesignable that make it work with Interface Builder

Swift 4

//
//  PaddedLabel.swift
//  TrainCentric
//
//  Created by Arsonik
//  https://mcmap.net/q/23723/-uilabel-text-margin-duplicate
//

import UIKit

@IBDesignable
class PaddedLabel: UILabel {

    @IBInspectable var inset:CGSize = CGSize(width: 0, height: 0)

    var padding: UIEdgeInsets {
        var hasText:Bool = false
        if let t = self.text?.count, t > 0 {
            hasText = true
        }
        else if let t = attributedText?.length, t > 0 {
            hasText = true
        }

        return hasText ? UIEdgeInsets(top: inset.height, left: inset.width, bottom: inset.height, right: inset.width) : UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }

    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: padding))
    }

    override var intrinsicContentSize: CGSize {
        let superContentSize = super.intrinsicContentSize
        let p = padding
        let width = superContentSize.width + p.left + p.right
        let heigth = superContentSize.height + p.top + p.bottom
        return CGSize(width: width, height: heigth)
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        let superSizeThatFits = super.sizeThatFits(size)
        let p = padding
        let width = superSizeThatFits.width + p.left + p.right
        let heigth = superSizeThatFits.height + p.top + p.bottom
        return CGSize(width: width, height: heigth)
    }
}

Swift 2

@IBDesignable
class PaddedLabel: UILabel {

    @IBInspectable var inset:CGSize = CGSize(width: 0, height: 0)

    var padding: UIEdgeInsets {
        var hasText:Bool = false
        if let t = text?.length where t > 0 {
            hasText = true
        }
        else if let t = attributedText?.length where t > 0 {
            hasText = true
        }

        return hasText ? UIEdgeInsets(top: inset.height, left: inset.width, bottom: inset.height, right: inset.width) : UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }

    override func drawTextInRect(rect: CGRect) {
        super.drawTextInRect(UIEdgeInsetsInsetRect(rect, padding))
    }

    override func intrinsicContentSize() -> CGSize {
        let superContentSize = super.intrinsicContentSize()
        let p = padding
        let width = superContentSize.width + p.left + p.right
        let heigth = superContentSize.height + p.top + p.bottom
        return CGSize(width: width, height: heigth)
    }

    override func sizeThatFits(size: CGSize) -> CGSize {
        let superSizeThatFits = super.sizeThatFits(size)
        let p = padding
        let width = superSizeThatFits.width + p.left + p.right
        let heigth = superSizeThatFits.height + p.top + p.bottom
        return CGSize(width: width, height: heigth)
    }
}
Alitaalitha answered 20/10, 2015 at 18:40 Comment(0)
K
18

For Xamarin users (using Unified API):

class UIMarginLabel : UILabel
{
    public UIMarginLabel()
    {
    }

    public UIMarginLabel( CGRect frame ) : base( frame )
    {
    }

    public UIEdgeInsets Insets { get; set; }

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

And for those using the original MonoTouch API:

public class UIMarginLabel : UILabel
{
    public UIEdgeInsets Insets { get; set; }

    public UIMarginLabel() : base()
    {
        Insets = new UIEdgeInsets(0, 0, 0, 0);
    }
    public UIMarginLabel(RectangleF frame) : base(frame)
    {
        Insets = new UIEdgeInsets(0, 0, 0, 0);
    }

    public override void DrawText(RectangleF frame)
    {
        base.DrawText(new RectangleF(
            frame.X + Insets.Left,
            frame.Y + Insets.Top,
            frame.Width - Insets.Left - Insets.Right,
            frame.Height - Insets.Top - Insets.Bottom));
    }
}
Kibe answered 6/3, 2014 at 23:2 Comment(1)
In the unified API example, RectangleF is used in the ctor; it should be CGRect in order to workMetalinguistic
R
14

If you don't want to use an extra parent view to set the background, you can subclass UILabel and override textRectForBounds:limitedToNumberOfLines:. I'd add a textEdgeInsets property or similar and then do

- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{
  return [super textRectForBounds:UIEdgeInsetsInsetRect(bounds,textEdgeInsets) limitedToNumberOfLines:numberOfLines];
}

For robustness, you might also want to call [self setNeedsDisplay] in setTextEdgeInsets:, but I usually don't bother.

Rotogravure answered 13/8, 2010 at 12:40 Comment(4)
Note that, from the documentation, "For this method to be called, there must be a prior call to the sizeToFit or sizeThatFits: method."Chipper
@mvds: That's fine: textRectForBounds:limitedToNumberOfLines: is being called, so it's up to whoever called it to ensure that there's been a prior call to -sizeToFit/-sizeThatFits:.Rotogravure
This doesn't appear to be working in Xcode 9.3/Swift 4.1/iOS 11.3. textRectForBounds() is being called and my edge insets are present, but the label doesn't have any of this padding. I am seeing odd values for the width and the height. This is a description of bounds from textRectForBounds(): Printing description of bounds: ▿ (0.0, 0.0, 3.40282346638529e+38, 3.40282346638529e+38). The label is being created in viewDidLoad() in a view controller.Twittery
Adding this code to Brody Robertson's answer finally worked for me. Without overriding textRectForBounds:limitedToNumberOfLines: the last line of my string (a multiline attributed string converted from html) would be cut off. I am using Xcode 10.0 and iOS 12.Stilly
S
13

To expand on the answer provided by Brody Robertson you can add the IB Designable bits. This means you can adjust the label from within Storyboard.

enter image description here

In your subclassed UILabel do

#import <UIKit/UIKit.h>

IB_DESIGNABLE

@interface insetLabel : UILabel

@property (nonatomic, assign) IBInspectable CGFloat leftEdge;
@property (nonatomic, assign) IBInspectable CGFloat rightEdge;
@property (nonatomic, assign) IBInspectable CGFloat topEdge;
@property (nonatomic, assign) IBInspectable CGFloat bottomEdge;

@property (nonatomic, assign) UIEdgeInsets edgeInsets;

@end

Then do;

#import "insetLabel.h"

@implementation insetLabel

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.edgeInsets = UIEdgeInsetsMake(self.topEdge, self.leftEdge, self.bottomEdge, self.rightEdge);
    }
    return self;
}

- (void)drawTextInRect:(CGRect)rect
{
    self.edgeInsets = UIEdgeInsetsMake(self.topEdge, self.leftEdge, self.bottomEdge, self.rightEdge);

    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.edgeInsets)];
}

- (CGSize)intrinsicContentSize
{
    CGSize size = [super intrinsicContentSize];
    size.width  += self.edgeInsets.left + self.edgeInsets.right;
    size.height += self.edgeInsets.top + self.edgeInsets.bottom;
    return size;
}

@end

EDIT

You should probably add a setter method for edgeInsets.

Supersaturate answered 16/7, 2015 at 16:29 Comment(4)
Please add this to your response so it actually works: - (void)awakeFromNib { self.edgeInsets = UIEdgeInsetsMake(self.topEdge, self.leftEdge, self.bottomEdge, self.rightEdge); }Grooms
Autolayout should be fine, does it show up in Storyboard? Also I have just seen Pauls answer above, have you added the awakeFromNib?Supersaturate
Also, have you changed your Label class to the custom class? Third icon along in the top right.Supersaturate
@RecycledSteel I am having some problem, the above method works, but Its not resizing itself! I have set constraints on horizontally and vertically center. What should I do so that It gets update in size with the change in the Label TextAlburnum
I
11

Maybe later for the party, but the following just works. Just subclass UILabel.

#import "UITagLabel.h"

#define padding UIEdgeInsetsMake(5, 10, 5, 10)

@implementation UITagLabel

- (void)drawTextInRect:(CGRect)rect {
    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, padding)];
}

- (CGSize) intrinsicContentSize {
    CGSize superContentSize = [super intrinsicContentSize];
    CGFloat width = superContentSize.width + padding.left + padding.right;
    CGFloat height = superContentSize.height + padding.top + padding.bottom;
    return CGSizeMake(width, height);
}

- (CGSize) sizeThatFits:(CGSize)size {
    CGSize superSizeThatFits = [super sizeThatFits:size];
    CGFloat width = superSizeThatFits.width + padding.left + padding.right;
    CGFloat height = superSizeThatFits.height + padding.top + padding.bottom;
    return CGSizeMake(width, height);
}

@end
Interpellation answered 17/12, 2015 at 11:39 Comment(0)
S
9

Here is a swift solution. Just add this custom class on the bottom of your file (or create a new file for it) and use MyLabel instead of UILabel when creating your label.

class MyLabel: UILabel{
    override func drawTextInRect(rect: CGRect) {
        super.drawTextInRect(UIEdgeInsetsInsetRect(rect, UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)))
    }
}
Spondaic answered 7/1, 2016 at 19:36 Comment(3)
This is the only thing I've found that actually works. Note you'll need to change the class of the Label in your storyboard if you're using storyboards. I had to set the top value to a negative number to push it up as high as I wanted as well. Thank you!Shantung
This is what you want. Great answer in a sea of taking the long way.Douce
But is gives problems for multiple linesAzoth
E
8

I didn't find the suggestion to use UIButton in the answers above. So I will try to prove that this is a good choice.

button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)

In my situation using UIButton was the best solution because:

  • I had a simple single-line text
  • I didn't want to use UIView as a container for UILabel (i.e. I wanted to simplify math calculations for Autolayout in my cell)
  • I didn't want to use NSParagraphStyle (because tailIndent works incorrect with Autolayout – width of UILabel is smaller than expected)
  • I didn't want to use UITextView (because of possible side effects)
  • I didn't want to subclass UILabel (less code fewer bugs)

That's why using the contentEdgeInsets from UIButton in my situation became the easiest way to add text margins.

Hope this will help someone.

Electrophysiology answered 3/7, 2018 at 12:11 Comment(1)
Always provide code that can be understood easily.Yeager
D
7

If you're using autolayout in iOS 6+, you can do this by adjusting the intrinsicContentSize in a subclass of UILabel.

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.textAlignment = NSTextAlignmentRight;
    }
    return self;
}

- (CGSize)intrinsicContentSize 
{
    CGSize size = [super intrinsicContentSize];
    return CGSizeMake(size.width + 10.0, size.height);
}
Dreibund answered 31/3, 2014 at 21:33 Comment(0)
L
7

blyabtroi's asnwer converted in Swift (No Subclassing required)

let style: NSMutableParagraphStyle = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
style.alignment = .Justified
style.firstLineHeadIndent = 10.0
style.headIndent = 10.0
style.tailIndent = -10.0
let attrText: NSAttributedString = NSAttributedString(string: title, attributes: [NSParagraphStyleAttributeName:style])
let label: UILabel = UILabel(frame: someFrame)
label.numberOfLines = 0
label.attributedText = attrText
Lucius answered 14/9, 2016 at 15:12 Comment(0)
K
7

Swift 4 version of blyabtroi solution

let leadingMargin: CGFloat = 10
let trailingMargin: CGFloat = 10

let style = NSMutableParagraphStyle()
style.alignment = .justified
style.firstLineHeadIndent = leadingMargin
style.headIndent = leadingMargin
style.tailIndent = trailingMargin

label.attributedText = NSAttributedString(string: "Label with margins", 
                                          attributes: [NSAttributedStringKey.paragraphStyle: style])
Kleon answered 19/7, 2018 at 7:17 Comment(1)
Does this work for top and bottom margin as well?Alongshore
P
6

Instead of UILabel perhaps use https://github.com/mattt/TTTAttributedLabel

BITAttributedLabel *label = [BITAttributedLabel new];
label.font = font;
label.text = @"hello";
label.textInsets = UIEdgeInsetsMake(10, 10, 10, 10);
[label sizeToFit];
Pisarik answered 23/5, 2014 at 10:45 Comment(0)
H
5

In Swift it solves like this.

class Label: UILabel {
    override func drawTextInRect(rect: CGRect) {
        super.drawTextInRect(UIEdgeInsetsInsetRect(rect, UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)))
    }
}
Hernia answered 4/2, 2015 at 12:14 Comment(0)
M
5

This works correctly with multi-line labels:

class PaddedLabel: UILabel {
    var verticalPadding: CGFloat = 0
    var horizontalPadding: CGFloat = 0

    override func drawText(in rect: CGRect) {
        let insets = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
        super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
    }

    override var intrinsicContentSize: CGSize {
        get {
            let textWidth = super.intrinsicContentSize.width - horizontalPadding * 2
            let textHeight = sizeThatFits(CGSize(width: textWidth, height: .greatestFiniteMagnitude)).height
            let width = textWidth + horizontalPadding * 2
            let height = textHeight + verticalPadding * 2
            return CGSize(width: frame.width, height: height)
        }
    }
}
Meritorious answered 22/2, 2018 at 17:0 Comment(0)
N
4

A lot of the answers are missing the override of sizeThatFits. With this subclass you can just create the label, set the padding, and then say label.SizeToFit() and voila.

import UIKit

class UILabelEx : UILabel
{
    var padding : UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

    override func drawTextInRect(rect: CGRect) {

        super.drawTextInRect(UIEdgeInsetsInsetRect(rect, padding))
    }

    override func sizeThatFits(size: CGSize) -> CGSize
    {

        var adjSize = super.sizeThatFits(size)
        adjSize.width += padding.left + padding.right
        adjSize.height += padding.top + padding.bottom

        return adjSize
    }
}
Nahshun answered 20/3, 2015 at 22:4 Comment(0)
A
4

Swift 3 & AutoLayout compatible version:

class InsetLabel: UILabel {

    var insets = UIEdgeInsets()

    convenience init(insets: UIEdgeInsets) {
        self.init(frame: CGRect.zero)
        self.insets = insets
    }

    convenience init(dx: CGFloat, dy: CGFloat) {
        let insets = UIEdgeInsets(top: dy, left: dx, bottom: dy, right: dx)
        self.init(insets: insets)
    }

    override func drawText(in rect: CGRect) {
        super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
    }

    override var intrinsicContentSize: CGSize  {
        var size = super.intrinsicContentSize
        size.width += insets.left + insets.right
        size.height += insets.top + insets.bottom
        return size
    }
}
Aurlie answered 8/10, 2016 at 17:55 Comment(0)
G
3

This is the easiest solution I found for this:

Swift 4

class CustomLabel: UILabel{
    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)))
    }
}

Make sure you set your labels to the CustomLabel class in code as well as in storyboard.

Gamophyllous answered 4/3, 2019 at 4:27 Comment(1)
This wont work when constraints are applied.Yeager
M
2

Xcode 6.1.1 Swift solution using a extension.

The file name could be something like "UILabel+AddInsetMargin.swift":

import UIKit

extension UILabel
{
    public override func drawRect(rect: CGRect)
    {
        self.drawTextInRect(UIEdgeInsetsInsetRect(rect, UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)))
    }
}
Mispickel answered 18/3, 2015 at 9:24 Comment(2)
Using extension to override an existing method in the main part of a class is not a good practice, except that UILabel does NOT and will NEVER write the method.Wherefore
Whoa, hang on... you want to change the behaviour of every single UILabel? Potentially even for objects in frameworks you import, or other code you use? Someone else comes to use your code and can't figure out why a normal UILabel has an inset? Please, never ever do this. Only 'add' functionality with extensions, never 'change' functionality.Atiptoe
S
2

without subclassing and all that jazz.. i did this dynamically:

[cell.textLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[cell.textLabel constraintTrailingEqualTo:cell.contentView constant:-100];

the constraint part is just a simple code sugar wrapper (we have the same methods for adding a padding from top/bottom/left/right).. i'll open source the whole wrapper if i get enough love here:

- (id)constraintTrailingEqualTo:(UIView *)toView constant:(CGFloat)constant
{
    NSLayoutConstraint *cn = [NSLayoutConstraint constraintWithItem:self
                                                          attribute:NSLayoutAttributeTrailing
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:toView
                                                          attribute:NSLayoutAttributeTrailing
                                                         multiplier:1 constant:constant];

    [toView addConstraint:cn];
    return self;
}

(note i did this in the context of

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath;

you may have to call [self setNeedsLayout]; depending on your context.

Steve answered 19/2, 2016 at 9:38 Comment(0)
S
2
#import "E_LabelWithPadding.h"
#define padding UIEdgeInsetsMake(2, 0, 2, 0)
#define padding1 UIEdgeInsetsMake(0, 0, 0, 0)
@implementation E_LabelWithPadding
- (void)drawTextInRect:(CGRect)rect {
if (![self.text isEqualToString:@""]) {
    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, padding)];
}else {
    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, padding1)];
}

}

- (CGSize) intrinsicContentSize {
if (![self.text isEqualToString:@""]) {
    CGSize superContentSize = [super intrinsicContentSize];
    CGFloat width = superContentSize.width + padding.left + padding.right;
    CGFloat height = superContentSize.height + padding.top + padding.bottom;
    return CGSizeMake(width, height);
}else {
    CGSize superContentSize = [super intrinsicContentSize];
    CGFloat width = superContentSize.width + padding1.left + padding1.right;
    CGFloat height = superContentSize.height + padding1.top + padding1.bottom;
    return CGSizeMake(width, height);
}

}

- (CGSize) sizeThatFits:(CGSize)size {
if (![self.text isEqualToString:@""]) {
    CGSize superSizeThatFits = [super sizeThatFits:size];
    CGFloat width = superSizeThatFits.width + padding.left + padding.right;
    CGFloat height = superSizeThatFits.height + padding.top + padding.bottom;
    return CGSizeMake(width, height);
}else {
    CGSize superSizeThatFits = [super sizeThatFits:size];
    CGFloat width = superSizeThatFits.width + padding1.left + padding1.right;
    CGFloat height = superSizeThatFits.height + padding1.top + padding1.bottom;
    return CGSizeMake(width, height);
}

}

@end
Strata answered 9/5, 2017 at 10:11 Comment(0)
P
1

If label is created programmatically, padding can be calculated using the sizeThatFits method. If using more than one line, the text will be line broken at the max width value.

let text = UILabel()
let padding = 10
text.layer.cornerRadius = 5
text.layer.masksToBounds = true
text.text = "Hello"
text.font = UIFont(name: text.font.fontName, size: 18)
text.textAlignment = NSTextAlignment.center
text.numberOfLines = 1

let maxSize = CGSize(width: 100, height: 100)
var size = text.sizeThatFits(maxSize)
size.width = size.width + padding * 2
size.height = size.height + padding * 2
text.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
Practise answered 29/6, 2018 at 11:49 Comment(0)
R
1

I had solved it using xcode builder (but surely this could be achieved with swift using constraints):

Just create a UIView of the relevant size And add a child of type UILabel to that view

and there you go, you have padding :)

xcode builder solution

Ruthanneruthe answered 7/11, 2018 at 14:42 Comment(0)
L
0

I think UILabel class have no method for setting margin. Why you not set the position of Label at required place?

See below code:

UILabel *label = [[UILabel alloc] init];
label.text = @"This is label";
label.frame = CGRectMake(0,0,100,100);

if from interface builder then just position Label by following:

yourLabel.frame = CGRectMake(0,0,100,100);
Luthanen answered 13/8, 2010 at 12:26 Comment(0)
L
0

Maybe you could give this code a try

CGRect frame = btn.titleLabel.frame;
int indent = 20;
int inset = 20;
[btn.titleLabel setFrame:CGRectMake(frame.origin.x+inset,frame.origin.y,frame.size.width+indent,frame.size.height)];
Lexicostatistics answered 4/9, 2012 at 12:18 Comment(2)
Use frame instead of *frame, use inset, not indent (typo). But the effect is nearly the same like moving the label 20 points to the right. That means, if I give a background color, the color is moved as well. So the solution is (at least for me) not useful.Rigger
Note the question was about UILabels, UILabels do not contain a titleLabel, this fix is for UIButtons, just my two cents.Franklinfranklinite
T
0

To get rid of vertical padding for a single line label I did:

// I have a category method setFrameHeight; you'll likely need to modify the frame.
[label setFrameHeight:font.pointSize];

OR, without the category, use:

CGRect frame = label.frame;
frame.size.height = font.pointSize;
label.frame = frame;
Theresatherese answered 1/4, 2014 at 20:24 Comment(0)
R
0

Just add spaces to the left if it's a single line, more than 1 line will have 0 padding again.

[self.myLabel setText:[NSString stringWithFormat:@"   %@", self.myShortString]];
Reorientation answered 30/12, 2016 at 8:48 Comment(1)
how silly this is the easiest solution one can haveMutualism
N
0

You need to calculate UILabel size when you put insets. It can have different number of lines because of text alignment, line break mode.

override func drawText(in rect: CGRect) {

    let size = self.sizeThatFits(UIEdgeInsetsInsetRect(rect, insets).size);
    super.drawText(in: CGRect.init(origin: CGPoint.init(x: insets.left, y: insets.top), size: size));
}

override var intrinsicContentSize: CGSize {

    var size = super.intrinsicContentSize;

    if text == nil || text?.count == 0 {
        return size;
    }

    size = self.sizeThatFits(UIEdgeInsetsInsetRect(CGRect.init(origin: CGPoint.zero, size: size), insets).size);
    size.width  += self.insets.left + self.insets.right;
    size.height += self.insets.top + self.insets.bottom;

    return size;
}

try iEun/InsetLabel

Neritic answered 7/3, 2019 at 5:47 Comment(0)
B
-1

This is the easiest way I found. It works like a charm for me.

UIView *titleSection = [[UIView alloc] initWithFrame:CGRectMake(0, 0, screenWidth, 100)];
[titleSection addSubview:titleSection];

UILabel *label = [[UILabel alloc] initWithFrame:CGRectInset(titleSection.frame, PADDING, 0)];
[titleSection addSubview:label];
Besse answered 11/2, 2015 at 23:58 Comment(0)
G
-2

A lot of these answers are complicated. In some cases, that's necessary. However, if you're reading this, your label has no left/right margin, and you just want a little padding, here's the whole solution:

Step 1: Add spaces at the end (literally, hit the spacebar a few times)

Step 2: Set the text alignment of the label to centered

Done

Galliot answered 4/2, 2018 at 1:59 Comment(1)
This is a terrible way to do it since if you change the text length, or font size, or font style, you'll be constantly updating the amount of spaces. Also, this means you can never Localise your app (different languages) since the text will be different.Kilroy
E
-4

Set the label's textAlignment property to NSTextAlignmentRight and augment its width.

Ewall answered 25/9, 2013 at 0:41 Comment(0)
G
-5

Don't code, Xcode !

Instead of using UILabel for this specific matter, I suggest you to take a look at UIButton. It gives, out of the box, the ability to set Content Insets (top, left, bottom, right) in the Size inspector. Set the desired margins, after that disable the button right in Xcode and done.

Gerontology answered 4/1, 2017 at 22:59 Comment(2)
You're being downvoted because this question is about UILabel and you replied about UIButton.Yahrzeit
@Yahrzeit I get it, no worries. However, for those looking for a solution that really works and with no overhead, they can try this and move on.Gerontology

© 2022 - 2024 — McMap. All rights reserved.