266

I have a UILabel where I want to add space in the top and in the bottom. With the minimum height in constraints, I've modified it to:

Enter image description here

To do this I've used:

override func drawTextInRect(rect: CGRect) { var insets: UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0) super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets)) } 

But I've to find a different method because if I write more than two lines, the problem is the same:

Enter image description here

1
  • We finally finally figured out exactly how to do this properly, in all dynamic cases, as a perfect drop-in replacement for UILabel with no need to re-layout or any other issues. PHEW. stackoverflow.com/a/58876988/294884 Commented Nov 15, 2019 at 12:29

38 Answers 38

311

I have tried with it on Swift 4.2, hopefully it work for you!

@IBDesignable class PaddingLabel: UILabel { @IBInspectable var topInset: CGFloat = 5.0 @IBInspectable var bottomInset: CGFloat = 5.0 @IBInspectable var leftInset: CGFloat = 7.0 @IBInspectable var rightInset: CGFloat = 7.0 override func drawText(in rect: CGRect) { let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) super.drawText(in: rect.inset(by: insets)) } override var intrinsicContentSize: CGSize { let size = super.intrinsicContentSize return CGSize(width: size.width + leftInset + rightInset, height: size.height + topInset + bottomInset) } override var bounds: CGRect { didSet { // ensures this works within stack views if multi-line preferredMaxLayoutWidth = bounds.width - (leftInset + rightInset) } } } 

Or you can use CocoaPods here https://github.com/levantAJ/PaddingLabel

pod 'PaddingLabel', '1.2'

enter image description here

Sign up to request clarification or add additional context in comments.

17 Comments

The uilabel width is not changing, causing the text become "..."
@Tai Le , thanks for sharing, I have used it in tableview, I don't know why its triming the text , eg. student becomes studen,
@Tim maybe you've meant to use min
A word of warning here. I've been using this solution in a UILabel subclass. When using these labels in multiline mode, in a vertical UIStackView, there's an issue. Sometimes the label seems to wrap the text without correctly sizing the label - so a word or 2 ends up missing from the end of the string. I don't have a solution right now. I'll write it up here if I make one. I spent hours poking at this issue, before proving it was here.
To make it work in those situations, you need to override "setBounds" and set self.preferredMaxLayoutWidth to the bounds' width, minus your left and right insets
|
150

If you want to stick with UILabel, without subclassing it, Mundi has given you a clear solution.

If alternatively, you would be willing to avoid wrapping the UILabel with a UIView, you could use UITextView to enable the use of UIEdgeInsets (padding) or subclass UILabel to support UIEdgeInsets.

Using a UITextView would only need to provide the insets (Objective-C):

textView.textContainerInset = UIEdgeInsetsMake(10, 0, 10, 0); 

Alternative, if you subclass UILabel, an example to this approach would be overriding the drawTextInRect method
(Objective-C)

- (void)drawTextInRect:(CGRect)uiLabelRect { UIEdgeInsets myLabelInsets = {10, 0, 10, 0}; [super drawTextInRect:UIEdgeInsetsInsetRect(uiLabelRect, myLabelInsets)]; } 

You could additionally provide your new subclassed UILabel with insets variables for TOP, LEFT, BOTTOM and RIGHT.

An example code could be:

In .h (Objective-C)

float topInset, leftInset,bottomInset, rightInset; 

In .m (Objective-C)

- (void)drawTextInRect:(CGRect)uiLabelRect { [super drawTextInRect:UIEdgeInsetsInsetRect(uiLabelRect, UIEdgeInsetsMake(topInset,leftInset,bottomInset,rightInset))]; } 

From what I have seen, it seems you have to override the intrinsicContentSize of the UILabel when subclassing it.

So you should override intrinsicContentSize like:

- (CGSize) intrinsicContentSize { CGSize intrinsicSuperViewContentSize = [super intrinsicContentSize] ; intrinsicSuperViewContentSize.height += topInset + bottomInset ; intrinsicSuperViewContentSize.width += leftInset + rightInset ; return intrinsicSuperViewContentSize ; } 

And add the following method to edit your insets, instead of editing them individually:

- (void) setContentEdgeInsets:(UIEdgeInsets)edgeInsets { topInset = edgeInsets.top; leftInset = edgeInsets.left; rightInset = edgeInsets.right; bottomInset = edgeInsets.bottom; [self invalidateIntrinsicContentSize] ; } 

It will update the size of your UILabel to match the edge insets and cover the multiline necessity you referred to.

After searching a bit I have found this Gist with an IPInsetLabel. If none of those solutions work you could try it out.

There was a similar question (duplicate) about this matter.
For a full list of available solutions, see this answer: UILabel text margin

13 Comments

Sorry but I've alreay used: ` override func drawTextInRect(rect: CGRect) { var insets: UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0) super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets)) } ` it doesn't work because the result is the same, don't work dynamically..
Did you tried with a UITextView instead of a UILabel? Or do you really need to use an UILabel?
@Annachiara check the edit I've made. See if it works.
Ok. Did it work the textview? Sorry for not writing in Swift but I am still in Obj-C mode. My goal with that code was to help you reach some conclusion. Hope it did.
Using TextView and some storyboard settings and self.textview.textContainerInset = UIEdgeInsetsMake(0, 10, 10, 10); It finally works ! Thanks !
|
94

Swift 3

import UIKit class PaddingLabel: UILabel { @IBInspectable var topInset: CGFloat = 5.0 @IBInspectable var bottomInset: CGFloat = 5.0 @IBInspectable var leftInset: CGFloat = 5.0 @IBInspectable var rightInset: CGFloat = 5.0 override func drawText(in rect: CGRect) { let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) } override var intrinsicContentSize: CGSize { get { var contentSize = super.intrinsicContentSize contentSize.height += topInset + bottomInset contentSize.width += leftInset + rightInset return contentSize } } } 

2 Comments

just a little comment: set this class to label in identity inspector(custom class) and use new attribute in attribute inspector named label padding. also below 5 padding is effectless
This doesn't always work correctly with multiline labels, because when the label calculates its height, it assumes zero padding.
93

You can do it properly from IB :

  1. change the text to attributed

attributed text

  1. go to dropdown list with "..."

enter image description here

  1. you will see some padding properties for the lines, paragraphs and text change indent first line or anything you want

enter image description here

  1. check the result

enter image description here

7 Comments

In my storyboard I can see the text change but when I run the app. The text doesn´t show the change... T_T.. my label is inside of an custom cell, are there any problem?
@A.Trejo May be your custom cell set the label property at run time.
Changes can appear on storyboard but when you run the app there are no changes.
This is not applicable when you set text programatically.
This is not the answer. You have only control over the first line indent, bot not the padding in all directions.
|
74

Just use a UIButton, its already built in. Turn off all the extra button features and you have a label that you can set edge instets on.

let button = UIButton() button.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) button.setTitle("title", for: .normal) button.tintColor = .white // this will be the textColor button.isUserInteractionEnabled = false 

9 Comments

Hey this is a great tip! No extensions required! :-D
Setting isUserInteractionEnabled = false is handy to disable it.
Nice tip, with the big advantage that it can also be done in the Interface Builder
The best solution without subclassing and etc.
A UITextView is better than this solution when it comes to not needing to subclass. This is because Voice Over will treat it as a button instead. Please care about users with accessibility needs - one day it could be you.
|
67

Just use a UIView as a superview and define a fixed margin to the label with auto layout.

6 Comments

drawTextInRect works only for 1 line, intrinsicContentSize does not work with horizontal padding. Wrap UILabel inside UIView is the good way to go
If you're in IB, now is the time to remember the menu Editor -> Embed In -> View. Just select your UILabel first :)
This is by far I see as the easiest solution. One just needs to make sure to align label in the center (horizontally and vertically) over the view and also update the view's bg color to match the bg color of your UILabel.
If you need to update and resize the UILabel dynamically, create the UIView independently of the label (not as a super view), then Auto Layout will adjust UIView as the label resizes.
The proposition is interesting, just to take into account that in UITesting a label will be inside an 'other' element, and secondly those constraints between label and container have 1000 priority so when compressing a view for small screens this can be something to hack
|
59

SWIFT 4

Easy to use solution, available for all UILabel child in project.

Example:

let label = UILabel() label.<Do something> label.padding = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0) 

UILabel Extension

import UIKit extension UILabel { private struct AssociatedKeys { static var padding = UIEdgeInsets() } public var padding: UIEdgeInsets? { get { return objc_getAssociatedObject(self, &AssociatedKeys.padding) as? UIEdgeInsets } set { if let newValue = newValue { objc_setAssociatedObject(self, &AssociatedKeys.padding, newValue as UIEdgeInsets?, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } override open func draw(_ rect: CGRect) { if let insets = padding { self.drawText(in: rect.inset(by: insets)) } else { self.drawText(in: rect) } } override open var intrinsicContentSize: CGSize { guard let text = self.text else { return super.intrinsicContentSize } var contentSize = super.intrinsicContentSize var textWidth: CGFloat = frame.size.width var insetsHeight: CGFloat = 0.0 var insetsWidth: CGFloat = 0.0 if let insets = padding { insetsWidth += insets.left + insets.right insetsHeight += insets.top + insets.bottom textWidth -= insetsWidth } let newSize = text.boundingRect(with: CGSize(width: textWidth, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: self.font], context: nil) contentSize.height = ceil(newSize.size.height) + insetsHeight contentSize.width = ceil(newSize.size.width) + insetsWidth return contentSize } } 

20 Comments

please briefly explain your answer, and do not only post code.
Your extension cancel the 0 value to numberOfLines
This is great, but Im having issues with multiple lines, even thought I add numbers of lines 2 or leave at 0, it always shows one. you know how to solve it?
@AntoineBodart did you manage to solve the numberOfLines problem?
Do not override methods using an extension. This is very bad practice. See here for more info: stackoverflow.com/a/38274660/4698501
|
35

We finally figured a full and correct solution which works in all cases, including stack views, dynamic cells, dynamic number of lines, collection views, animated padding, any character count, and every other situation.

Padding a UILabel, full solution. Updated for 2024.

Turns out there are three things:

1. Must call textRect#forBounds with the new smaller size

2. Must override drawText with the new smaller size

3. If a dynamically sized cell, must adjust intrinsicContentSize

In the visual example below the text unit is in a table view or similar which gives it a fixed width.

We take the "existing" intrinsicContentSize and actually add to the height.

You have to literally "get" the height calculated "so far" by the engine, and change that value.

(IMO it would be clearer if Apple exposed something like "preliminary height calculation", but that's not how it is.)

Secondly we have to actually use the textRect#forBounds call with our new smaller size.

So in textRect#forBounds we first make the size smaller and ONLY THEN call super.

Alert! You must call super after, not before!

Notice some solutions seem to USUALLY work. If you call super "in the wrong order", it USUALLY works, but fails for CERTAIN SPECIFIC text lengths.

Here is an exact visual example of "incorrectly doing super first":

enter image description here

The margins are correct BUT the size calculation is actually wrong, because it was done with the "super first" in textRect#forBounds.

Fixed:

Only now does the textRect#forBounds engine know how to do the calculation properly:

enter image description here

Finally!

Phew!

class PaddyLabel: UILabel { // as desired: let UIEI = UIEdgeInsets(top: 60, left: 20, bottom: 20, right: 24) override var intrinsicContentSize:CGSize { numberOfLines = 0 var s = super.intrinsicContentSize s.height = s.height + UIEI.top + UIEI.bottom s.width = s.width + UIEI.left + UIEI.right return s } override func drawText(in rect:CGRect) { let r = rect.inset(by: UIEI) super.drawText(in: r) } override func textRect(forBounds bounds:CGRect, limitedToNumberOfLines n:Int) -> CGRect { let b = bounds let tr = b.inset(by: UIEI) let ctr = super.textRect(forBounds: tr, limitedToNumberOfLines: n) // that line of code MUST be LAST in this function, NOT first return ctr } } 

Again, note that the answers on this and other QA that are "USUALLY" correct suffer the problem in the first image above - the "super is in the wrong place".

Summary: you must "call super last" in textRect#forBounds

That's the secret.

Note that you do not need to additionally call invalidate, sizeThatFits, needsLayout or any other forcing call. The correct solution must and does work properly in the normal draw cycle.

8 Comments

Great solution. Could you please expand on why you are overwriting the numberOfLines?
I used this and the IBInspectable stuff from the highest-voted (but seemingly not as correct) answer (stackoverflow.com/a/32368958/826946) and added support for border (color, thickness, cornerRadius) also using IBInspectable. It's over here: stackoverflow.com/a/67317976/826946
Love how in 2021, you still aren't able to set a property on the UILabel to do this automatically.
I have tried this approach, but I'm unable to dynamically set the font size afterwards? It seems to shrink to 10 despite me setting it to something much higher, like 24. And when I disable this code, the size is correct. I should add that I'm in a tableview with a cell and label that I think is having it's instrinsic size adjusted, by the padding and by linespacing.
Unfortunately this overrides my numberOfLines setting. I only need to show 2 lines (and ... after that), but this just sets it to 0 and showed all text instead. If I remove the numberOfLines setting, it shows the correct lines set, but the spacing is wrong.
|
24

Without Storyboard:

class PaddingLabel: UILabel { var topInset: CGFloat var bottomInset: CGFloat var leftInset: CGFloat var rightInset: CGFloat required init(withInsets top: CGFloat, _ bottom: CGFloat,_ left: CGFloat,_ right: CGFloat) { self.topInset = top self.bottomInset = bottom self.leftInset = left self.rightInset = right super.init(frame: CGRect.zero) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func drawText(in rect: CGRect) { let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) } override var intrinsicContentSize: CGSize { get { var contentSize = super.intrinsicContentSize contentSize.height += topInset + bottomInset contentSize.width += leftInset + rightInset return contentSize } } } 

Usage:

let label = PaddingLabel(8, 8, 16, 16) label.font = .boldSystemFont(ofSize: 16) label.text = "Hello World" label.backgroundColor = .black label.textColor = .white label.textAlignment = .center label.layer.cornerRadius = 8 label.clipsToBounds = true label.sizeToFit() view.addSubview(label) 

Result:

1 Comment

Works, but do you know how to make it accept multiple lines? Just change init with "PaddingLabel(withInsets: 8, 8, 16, 16)"
21

Swift 4+

class EdgeInsetLabel: UILabel { var textInsets = UIEdgeInsets.zero { didSet { invalidateIntrinsicContentSize() } } override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines) let invertedInsets = UIEdgeInsets(top: -textInsets.top, left: -textInsets.left, bottom: -textInsets.bottom, right: -textInsets.right) return textRect.inset(by: invertedInsets) } override func drawText(in rect: CGRect) { super.drawText(in: rect.inset(by: textInsets)) } } 

Usage:

let label = EdgeInsetLabel() label.textInsets = UIEdgeInsets(top: 2, left: 6, bottom: 2, right: 6) 

2 Comments

WAIT - there's actually a problem I found with this in some cases. Previously this was the most correct answer. I've put in the correct answer below.
I have included an image in my answer showing the problem
16

Just use autolayout:

let paddedWidth = myLabel.intrinsicContentSize.width + 2 * padding myLabel.widthAnchor.constraint(equalToConstant: paddedWidth).isActive = true 

Done.

1 Comment

You can also do the same with height.
9

As per Swift 4.2 (Xcode 10 beta 6) "UIEdgeInsetsInsetRect" being deprecated. I've also declared the class public to make it more useful.

public class UIPaddedLabel: UILabel { @IBInspectable var topInset: CGFloat = 5.0 @IBInspectable var bottomInset: CGFloat = 5.0 @IBInspectable var leftInset: CGFloat = 7.0 @IBInspectable var rightInset: CGFloat = 7.0 public override func drawText(in rect: CGRect) { let insets = UIEdgeInsets.init(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) super.drawText(in: rect.inset(by: insets)) } public override var intrinsicContentSize: CGSize { let size = super.intrinsicContentSize return CGSize(width: size.width + leftInset + rightInset, height: size.height + topInset + bottomInset) } public override func sizeToFit() { super.sizeThatFits(intrinsicContentSize) } } 

3 Comments

It works well. But I try to use it inside a CollectionViewCell and it doesn't resize well after reuse (event after sizeToFit and layoutIfNeeded). Any id how to resize it?
I did update sizeToFit() to make it work with reusable view
sizeToFit() should be public as: "Overriding instance method must be as accessible as its enclosing type"
9

Just like the other answers, but it fixes a bug:

When label.width is controlled by auto layout, sometimes text will be cropped.

@IBDesignable class InsetLabel: UILabel { @IBInspectable var topInset: CGFloat = 4.0 @IBInspectable var leftInset: CGFloat = 4.0 @IBInspectable var bottomInset: CGFloat = 4.0 @IBInspectable var rightInset: CGFloat = 4.0 var insets: UIEdgeInsets { get { return UIEdgeInsets.init(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) } set { topInset = newValue.top leftInset = newValue.left bottomInset = newValue.bottom rightInset = newValue.right } } 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 { let systemContentSize = super.intrinsicContentSize let adjustSize = CGSize(width: systemContentSize.width + leftInset + rightInset, height: systemContentSize.height + topInset + bottomInset) if adjustSize.width > preferredMaxLayoutWidth && preferredMaxLayoutWidth != 0 { let constraintSize = CGSize(width: bounds.width - (leftInset + rightInset), height: .greatestFiniteMagnitude) let newSize = super.sizeThatFits(constraintSize) return CGSize(width: systemContentSize.width, height: ceil(newSize.height) + topInset + bottomInset) } else { return adjustSize } } override func drawText(in rect: CGRect) { super.drawText(in: rect.inset(by: insets)) } } 

2 Comments

An explanation would be in order. E.g., what is the idea/gist of the fix? What was changed or added? Please respond by editing (changing) your answer, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).
this fails completely in all but the most trivial fixed cases
8

Swift 3 Code with Implementation Example

class UIMarginLabel: UILabel { var topInset: CGFloat = 0 var rightInset: CGFloat = 0 var bottomInset: CGFloat = 0 var leftInset: CGFloat = 0 override func drawText(in rect: CGRect) { let insets: UIEdgeInsets = UIEdgeInsets(top: self.topInset, left: self.leftInset, bottom: self.bottomInset, right: self.rightInset) self.setNeedsLayout() return super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) } } class LabelVC: UIViewController { //Outlets @IBOutlet weak var labelWithMargin: UIMarginLabel! override func viewDidLoad() { super.viewDidLoad() //Label settings. labelWithMargin.leftInset = 10 view.layoutIfNeeded() } } 

Don't forget to add class name UIMarginLabel in storyboard label object. Happy Coding!

Comments

8

In Swift 3

best and simple way

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

2 Comments

How do you use this code for your UILabel please?
Hi @David_2877 it's a class, just add this class into your project, and when you want UILabel use UILabelPadded. Let me know if you need more help.
7

I edited a little in the accepted answer. There is a problem when leftInset and rightInset increase, a part of text will be disappeared, b/c the width of label will be narrowed but the height does not increase as figure:

padding label with wrong intrinsic content size

To resolve this problem you need to re-calculate height of text as follow:

@IBDesignable class PaddingLabel: UILabel { @IBInspectable var topInset: CGFloat = 20.0 @IBInspectable var bottomInset: CGFloat = 20.0 @IBInspectable var leftInset: CGFloat = 20.0 @IBInspectable var rightInset: CGFloat = 20.0 override func drawTextInRect(rect: CGRect) { let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets)) } override func intrinsicContentSize() -> CGSize { var intrinsicSuperViewContentSize = super.intrinsicContentSize() let textWidth = frame.size.width - (self.leftInset + self.rightInset) let newSize = self.text!.boundingRectWithSize(CGSizeMake(textWidth, CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: self.font], context: nil) intrinsicSuperViewContentSize.height = ceil(newSize.size.height) + self.topInset + self.bottomInset return intrinsicSuperViewContentSize } } 

and result:

padding label with right intrinsic content size

I hope to help some people in the same situation as me.

2 Comments

If you plan to use Swift 3.0, you must change function names, as new Apple language completely breaks previous func definition. So, override func drawTextInRect(rect: CGRect) becomes override func drawText(in rect: CGRect) and override func intrinsicContentSize() -> CGSize becomes override var intrinsicContentSize : CGSize Enjoy!
unfortunately I did not make it work. I tried with our code swift 5 override var intrinsicContentSize: CGSize { // .. return intrinsicSuperViewContentSize }
7

Another option without subclassing would be to:

  1. Set label text
  2. sizeToFit()
  3. then increase label height a little to simulate padding

    label.text = "someText" label.textAlignment = .center label.sizeToFit() label.frame = CGRect( x: label.frame.x, y: label.frame.y,width: label.frame.width + 20,height: label.frame.height + 8) 

1 Comment

Surprisingly, this was all I needed, just modified a little to this: label.frame = CGRect( x: label.frame.origin.x - 10, y: label.frame.origin.y - 4, width: label.frame.width + 20,height: label.frame.height + 8) the -10 and -4 for centralizing
6

Swift 3, iOS10 solution:

open class UIInsetLabel: UILabel { open var insets : UIEdgeInsets = UIEdgeInsets() { didSet { super.invalidateIntrinsicContentSize() } } open override var intrinsicContentSize: CGSize { var size = super.intrinsicContentSize size.width += insets.left + insets.right size.height += insets.top + insets.bottom return size } override open func drawText(in rect: CGRect) { return super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) } } 

Comments

5

Swift 5 Example with UILabel Extension

With the code below setting your margins is as easy as label.setMargins(15).

extension UILabel { func setMargins(_ margin: CGFloat = 10) { if let textString = self.text { let 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 } } } 

4 Comments

This is not working.
@VladimirSukanica it is working on my end. I think the solution is just fine.
totally completely wrong unfortunately :) only works in the simplest of cases
This does only indent my paragraph on the left (and maybe right, too). A label with a single line is not indented at the top and the bottom and neither a multiline label.
3

Subclass UILabel. (File-New-File- CocoaTouchClass-make Subclass of UILabel).

// sampleLabel.swift import UIKit class sampleLabel: UILabel { let topInset = CGFloat(5.0), bottomInset = CGFloat(5.0), leftInset = CGFloat(8.0), rightInset = CGFloat(8.0) override func drawTextInRect(rect: CGRect) { let insets: UIEdgeInsets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets)) } override func intrinsicContentSize() -> CGSize { var intrinsicSuperViewContentSize = super.intrinsicContentSize() intrinsicSuperViewContentSize.height += topInset + bottomInset intrinsicSuperViewContentSize.width += leftInset + rightInset return intrinsicSuperViewContentSize } } 

On ViewController:

override func viewDidLoad() { super.viewDidLoad() let labelName = sampleLabel(frame: CGRectMake(0, 100, 300, 25)) labelName.text = "Sample Label" labelName.backgroundColor = UIColor.grayColor() labelName.textColor = UIColor.redColor() labelName.shadowColor = UIColor.blackColor() labelName.font = UIFont(name: "HelveticaNeue", size: CGFloat(22)) self.view.addSubview(labelName) } 

OR Associate custom UILabel class on Storyboard as Label's class.

3 Comments

i would up vote if you change those hardcoded values into class properties, i am already using this code.
@ Juan : drawTextInRect is a default class property of UILabel which we are unable to override using code. The best practice to subclass UILabel and add required frame change. Anyhow, it is convenient as Inheritance feature.
this is correct, however as of Swift 3 at least, intrinsicContentSize is not a function but rather a property, so should be "override var intrinsicContentSize: CGFloat {}" instead of "override func intrinsicContentSize", just a note.
3

If you want to use UILabel

class UILabel : UIKit.UILabel { var insets = UIEdgeInsets.zero { didSet { invalidateIntrinsicContentSize() } } override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines) let invertedInsets = UIEdgeInsets(top: -insets.top, left: -insets.left, bottom: -insets.bottom, right: -insets.right) return textRect.inset(by: invertedInsets) } override func drawText(in rect: CGRect) { super.drawText(in: rect.inset(by: insets)) } } 

Comments

3

An elaboration on Mundi's answer.

I.e., embedding a label in a UIView and enforcing padding through Auto Layout. Example:

It looks like a padded UILabel

Overview:

  1. Create a UIView ("panel"), and set its appearance.

  2. Create a UILabel and add it to the panel.

  3. Add constraints to enforce padding.

  4. Add the panel to your view hierarchy, and then position the panel.

Details:

  1. Create the panel view.

    let panel = UIView() panel.backgroundColor = .green panel.layer.cornerRadius = 12

  2. Create the label, add it to the panel as a subview.

    let label = UILabel() panel.addSubview(label)

  3. Add constraints between the edges of the label and the panel. This forces the panel to keep a distance from the label. I.e., "padding".

Editorial: doing all this by hand is super-tedious, verbose and error-prone. I suggest you pick an Auto Layout wrapper from GitHub or write one yourself

label.panel.translatesAutoresizingMaskIntoConstraints = false label.topAnchor.constraint(equalTo: panel.topAnchor, constant: vPadding).isActive = true label.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -vPadding).isActive = true label.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: hPadding).isActive = true label.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -hPadding).isActive = true label.textAlignment = .center 
  1. Add the panel to your view hierarchy and then add positioning constraints. E.g., hug the right-hand side of a tableViewCell, as in the example image.

Note: you only need to add positional constraints, not dimensional constraints: Auto Layout will solve the layout based on both the intrinsicContentSize of the label and the constraints added earlier.

hostView.addSubview(panel) panel.translatesAutoresizingMaskIntoConstraints = false panel.trailingAnchor.constraint(equalTo: hostView.trailingAnchor, constant: -16).isActive = true panel.centerYAnchor.constraint(equalTo: hostView.centerYAnchor).isActive = true 

Comments

3

Use this code if you are facing a text trimming problem while applying padding.

@IBDesignable class PaddingLabel: UILabel { @IBInspectable var topInset: CGFloat = 5.0 @IBInspectable var bottomInset: CGFloat = 5.0 @IBInspectable var leftInset: CGFloat = 5.0 @IBInspectable var rightInset: CGFloat = 5.0 override func drawText(in rect: CGRect) { let insets = UIEdgeInsets.init(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) } override var intrinsicContentSize: CGSize { var intrinsicSuperViewContentSize = super.intrinsicContentSize let textWidth = frame.size.width - (self.leftInset + self.rightInset) let newSize = self.text!.boundingRect(with: CGSize(textWidth, CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: self.font], context: nil) intrinsicSuperViewContentSize.height = ceil(newSize.size.height) + self.topInset + self.bottomInset return intrinsicSuperViewContentSize } } extension CGSize{ init(_ width:CGFloat,_ height:CGFloat) { self.init(width:width,height:height) } } 

2 Comments

Thank you for posting, I am looking for a solution regardin padding + trimming. It seems to me your solution breaks label.numberOfLines = 0 which I need. Any workaround?
@Don the only correct solution in all cases: stackoverflow.com/a/58876988/294884
3

Strictly for SINGLE-LINE labels: (2021 syntax)

For anyone googling here who needs padding on a STRICTLY SINGLE LINE label (such as a section heading or other list item),

The syntax has changed a lot. Beware out of date info on the internet.

Here's the exact class to copy and paste:

// add 100 above, 50 padding below a SINGLE-LINE label import UIKit class SingleLineLabelWithSpacing: UILabel { // STRICTLY for SINGLE LINE labels // only works with SINGLE LINE labels override func drawText(in rect: CGRect) { let insets: UIEdgeInsets = UIEdgeInsets( top: 100, left: 0, bottom: 50, right: 0) super.drawText(in: rect.inset(by: insets)) } override var intrinsicContentSize: CGSize { var ic = super.intrinsicContentSize ic.height = ic.height + 150 return ic } } 

enter image description here

In the example, padding above/below of 100/50.

This is the usual thing to do when you have any sort of scrolling list, feed, or other list.

In this way you never have to think about the spacing above/below the headline, username, etc - you just drop it in the stack view or whatever the case is.

Also of course you can change the two values everywhere all at once when the designers want to tweak it.

Reminder: if you want to truly pad a UILabel so that it works perfectly regardless of number of lines of text, dynamic sizing cells, animations, etc etc etc, it is very complicated. The only correct answer is: https://stackoverflow.com/a/58876988/294884

Comments

2

Easy padding (Swift 3.0, Alvin George answer):

 class NewLabel: UILabel { override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { return self.bounds.insetBy(dx: CGFloat(15.0), dy: CGFloat(15.0)) } override func draw(_ rect: CGRect) { super.drawText(in: self.bounds.insetBy(dx: CGFloat(5.0), dy: CGFloat(5.0))) } } 

Comments

2

One pragmatic solution is to add blank labels of the same height and color as the main label. Set the leading/trailing space to the main label to zero, align vertical centers, and make the width your desired margin.

Comments

2

If you don't want or need to use an @IBInspectable / @IBDesignable UILabel in Storyboard (I think those are rendered too slow anyway), then it is cleaner to use UIEdgeInsets instead of 4 different CGFloats.

Code example for Swift 4.2:

class UIPaddedLabel: UILabel { var padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) public override func drawText(in rect: CGRect) { super.drawText(in: rect.inset(by: padding)) } public override var intrinsicContentSize: CGSize { let size = super.intrinsicContentSize return CGSize(width: size.width + padding.left + padding.right, height: size.height + padding.top + padding.bottom) } } 

Comments

1

Similar to other answers, but with a func class to setup the padding dinamically:

class UILabelExtendedView: UILabel { var topInset: CGFloat = 4.0 var bottomInset: CGFloat = 4.0 var leftInset: CGFloat = 8.0 var rightInset: CGFloat = 8.0 override func drawText(in rect: CGRect) { let insets: UIEdgeInsets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) } override public var intrinsicContentSize: CGSize { var contentSize = super.intrinsicContentSize contentSize.height += topInset + bottomInset contentSize.width += leftInset + rightInset return contentSize } func setPadding(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat){ self.topInset = top self.bottomInset = bottom self.leftInset = left self.rightInset = right let insets: UIEdgeInsets = UIEdgeInsets(top: top, left: left, bottom: bottom, right: right) super.drawText(in: UIEdgeInsetsInsetRect(self.frame, insets)) } } 

Comments

1

My solution is similar to what people answered but adds sizeThatFits to help UIKit to figure out the right size.

class InsetLabel : UILabel { @objc var textInsets: UIEdgeInsets = .zero override func drawText(in rect: CGRect) { super.drawText(in: rect.inset(by: textInsets)) } override func sizeThatFits(_ size: CGSize) -> CGSize { var s = super.sizeThatFits(CGSize(width: size.width - (textInsets.left + textInsets.right), height: size.height - (textInsets.top + textInsets.bottom))) s.height += textInsets.top + textInsets.bottom return s } } 

Comments

0

Easy way

import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. self.view.addSubview(makeLabel("my title",x: 0, y: 100, w: 320, h: 30)) } func makeLabel(title:String, x:CGFloat, y:CGFloat, w:CGFloat, h:CGFloat)->UILabel{ var myLabel : UILabel = UILabel(frame: CGRectMake(x,y,w,h)) myLabel.textAlignment = NSTextAlignment.Right // inser last char to right var titlePlus1char = "\(title)1" myLabel.text = titlePlus1char var titleSize:Int = count(titlePlus1char)-1 myLabel.textColor = UIColor(red:1.0, green:1.0,blue:1.0,alpha:1.0) myLabel.backgroundColor = UIColor(red: 214/255, green: 167/255, blue: 0/255,alpha:1.0) // create myMutable String var myMutableString = NSMutableAttributedString() // create myMutable font myMutableString = NSMutableAttributedString(string: titlePlus1char, attributes: [NSFontAttributeName:UIFont(name: "HelveticaNeue", size: 20)!]) // set margin size myMutableString.addAttribute(NSFontAttributeName, value: UIFont(name: "HelveticaNeue", size: 10)!, range: NSRange(location: titleSize,length: 1)) // set last char to alpha 0 myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor(red:1.0, green:1.0,blue:1.0,alpha:0), range: NSRange(location: titleSize,length: 1)) myLabel.attributedText = myMutableString return myLabel } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.