How to Add Text Margins to a UILabel in Swift

How to Add Text Margins to a UILabel in Swift

Written by

in

Quick answer: UILabel lacks a built-in contentInsets property. To add margins, create a UILabel subclass that stores a UIEdgeInsets property, overrides drawText(in:) using rect.inset(by: insets), and expands intrinsicContentSize and sizeThatFits(_:) so Auto Layout calculates the correct padded frame without clipping text.

Unlike UIButton or UITextView, UIKit’s standard UILabel does not provide an edgeInsets or contentInsets property to control the spacing between the text and the label boundary.

Setting a background color or border on a standard UILabel highlights this limitation: text hugs the outer border directly, with no visual breathing room. Adjusting the label frame or origin moves the entire view, including its background color, rather than shifting the text inside it.

Why this happens

UILabel is designed as a lightweight text-rendering view. In standard UIKit implementations, UILabel draws its string directly into its full bounding rectangle (bounds) through an internal text renderer. Because the class does not expose layout margins for its text drawing pass, it draws glyphs edge-to-edge.

Additionally, UILabel calculates its layout size using intrinsicContentSize and sizeThatFits(_:). Both of these methods calculate the exact dimensions required to fit the glyphs under the current font and line-breaking settings. If you attempt to offset or inset drawing inside drawText(in:) without also overriding these size calculation methods, Auto Layout will compress the label frame to the raw text dimensions, causing the text to truncate or clip.

To inset text cleanly while retaining background colors, rounded corners, and proper Auto Layout sizing, you must adjust both the drawing rectangle and the measured intrinsic size, or format the text layout attributes directly.

How to fix it

1. Create a custom PaddingLabel subclass (Recommended)

The most flexible and reusable solution is to subclass UILabel, expose a UIEdgeInsets property, and override both drawText(in:) and intrinsicContentSize.

import UIKit

class PaddingLabel: UILabel {
    var textInsets = UIEdgeInsets.zero {
        didSet {
            invalidateIntrinsicContentSize()
            setNeedsDisplay()
        }
    }

    override func drawText(in rect: CGRect) {
        let insetRect = rect.inset(by: textInsets)
        super.drawText(in: insetRect)
    }

    override var intrinsicContentSize: CGSize {
        let size = super.intrinsicContentSize
        let width = size.width + textInsets.left + textInsets.right
        let height = size.height + textInsets.top + textInsets.bottom
        return CGSize(width: width, height: height)
    }

    override func sizeThatFits(_ size: CGSize) {
        let horizontalInsets = textInsets.left + textInsets.right
        let verticalInsets = textInsets.top + textInsets.bottom

        let adjustedSize = CGSize(
            width: max(0, size.width - horizontalInsets),
            height: max(0, size.height - verticalInsets)
        )

        let baseSize = super.sizeThatFits(adjustedSize)
        return CGSize(
            width: baseSize.width + horizontalInsets,
            height: baseSize.height + verticalInsets
        )
    }
}

You can use this subclass in Interface Builder by setting the custom class of any label to PaddingLabel, or instantiate it in code:

let badgeLabel = PaddingLabel()
badgeLabel.text = "New Feature"
badgeLabel.backgroundColor = .systemBlue
badgeLabel.textColor = .white
badgeLabel.layer.cornerRadius = 6
badgeLabel.clipsToBounds = true

// Set 6 points of vertical padding and 12 points of horizontal padding
badgeLabel.textInsets = UIEdgeInsets(top: 6, left: 12, bottom: 6, right: 12)

Because invalidateIntrinsicContentSize() is called inside didSet, updating textInsets dynamically will cause Auto Layout constraints to update without manual frame recalculations.

2. Apply horizontal margins using NSMutableParagraphStyle

If you only need left and right margins (indents) on a standard UILabel without subclassing, configure an attributed string with paragraph indents. This is especially useful for multi-line labels.

import UIKit

extension UILabel {
    func setTextWithHorizontalMargins(text: String, left: CGFloat = 10.0, right: CGFloat = 10.0) {
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.firstLineHeadIndent = left
        paragraphStyle.headIndent = left
        paragraphStyle.tailIndent = -right

        let attributes: [NSAttributedString.Key: Any] = [
            .paragraphStyle: paragraphStyle,
            .font: self.font ?? UIFont.systemFont(ofSize: 17),
            .foregroundColor: self.textColor ?? UIColor.label
        ]

        self.attributedText = NSAttributedString(string: text, attributes: attributes)
    }
}

Apply the method directly:

let summaryLabel = UILabel()
summaryLabel.numberOfLines = 0
summaryLabel.backgroundColor = .secondarySystemBackground
summaryLabel.setTextWithHorizontalMargins(text: "This text has 12 points of left and right padding.", left: 12, right: 12)

Note that tailIndent uses a negative offset to represent spacing from the trailing boundary. This technique affects only horizontal text alignment and does not provide vertical (top or bottom) padding.

3. Embed the UILabel in a container UIView with layout margins

If your label is configured entirely in Interface Builder or part of a complex layout, wrap the label inside an empty UIView and constrain the label to the container’s edges with constants representing your desired padding.

import UIKit

func makePaddedContainer(for label: UILabel, insets: UIEdgeInsets) -> UIView {
    let container = UIView()
    container.translatesAutoresizingMaskIntoConstraints = false
    label.translatesAutoresizingMaskIntoConstraints = false

    container.addSubview(label)

    NSLayoutConstraint.activate([
        label.topAnchor.constraint(equalTo: container.topAnchor, constant: insets.top),
        label.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -insets.bottom),
        label.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: insets.left),
        label.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -insets.right)
    ])

    return container
}

Move the background color and corner radius to the container view rather than the label. This cleanly isolates visual styling from text rendering without requiring any custom drawing overrides.

How to prevent it

  • Subclass early: Avoid applying ad-hoc empty space characters (" Text ") to strings to fake margins. White space padding fails localization and renders inconsistently across fonts.
  • Account for intrinsic sizing: When overriding drawText(in:), always override intrinsicContentSize and sizeThatFits(_:) to prevent Auto Layout from truncating strings.
  • Consider UIButton configuration: For pill badges or single-line tags that require background colors and insets, consider using a non-interactive UIButton with a UIButton.Configuration that supports contentInsets out of the box.
Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.