How to Set Custom Line Spacing on UILabel in UIKit

How to Set Custom Line Spacing on UILabel in UIKit

Written by

in

Quick answer: UILabel does not offer a direct lineSpacing property for plain text. To change line spacing, assign an NSAttributedString configured with an NSMutableParagraphStyle whose lineSpacing property is set to your desired point value.

Why this happens

A standard UILabel relies on Core Text and standard UIKit typography metrics. By default, UILabel.text accepts a plain String, which renders text strictly according to the font’s internal ascender, descender, and default leading values. Because of this architecture, Apple did not provide a standalone label.lineSpacing = 4 property on UILabel.

When a multi-line string renders inside a label with numberOfLines set to 0, lines may appear too close together or too far apart depending on the typeface. If you try to change spacing solely by altering the label’s frame, font size, or content insets, the inter-line distance remains fixed to the font’s default metrics.

Controlling the leading or spacing between wrapped lines requires an attributed string. UIKit exposes typography layout rules through NSParagraphStyle, which must be attached to the text before rendering.

How to fix it

1. Apply line spacing using NSAttributedString

The standard programmatic approach creates an NSMutableAttributedString and attaches an NSMutableParagraphStyle with the desired lineSpacing.

import UIKit

let label = UILabel()
label.numberOfLines = 0

let text = "This is a multi-line string where we want to customize the gap between each line of text cleanly."
let font = UIFont.systemFont(ofSize: 16, weight: .regular)

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 6 // Adjust extra points between lines
paragraphStyle.alignment = .left

let attributes: [NSAttributedString.Key: Any] = [
    .font: font,
    .foregroundColor: UIColor.label,
    .paragraphStyle: paragraphStyle
]

label.attributedText = NSAttributedString(string: text, attributes: attributes)

Notice that .font and .foregroundColor are defined inside the attribute dictionary alongside .paragraphStyle. If you configure label.font separately on the label instance before assigning attributedText, the attributed string’s internal attributes might take precedence or cause inconsistent baseline positioning.

2. Build a reusable UILabel helper extension

Manually constructing attributed strings across multiple view controllers leads to repetitive boilerplate code. You can encapsulate line spacing adjustments in an extension on UILabel:

import UIKit

extension UILabel {
    func setText(_ text: String, lineSpacing: CGFloat, textAlignment: NSTextAlignment = .natural) {
        let existingFont = self.font ?? UIFont.systemFont(ofSize: UIFont.labelFontSize)
        let existingColor = self.textColor ?? UIColor.label

        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = lineSpacing
        paragraphStyle.alignment = textAlignment

        let attributes: [NSAttributedString.Key: Any] = [
            .font: existingFont,
            .foregroundColor: existingColor,
            .paragraphStyle: paragraphStyle
        ]

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

Call this helper on any multi-line label:

descriptionLabel.numberOfLines = 0
descriptionLabel.setText("Updated descriptive text spanning several lines.", lineSpacing: 4)

3. Adjust line height explicitly with lineHeightMultiple

Sometimes, tweaking raw lineSpacing by a fixed point value causes awkward clipping or baseline shifts when supporting dynamic type. Alternatively, you can use lineHeightMultiple or clamp explicit boundaries using minimumLineHeight and maximumLineHeight:

import UIKit

let paragraphStyle = NSMutableParagraphStyle()
// Scales proportional to the font's standard line height (e.g., 1.25 = 125%)
paragraphStyle.lineHeightMultiple = 1.2
paragraphStyle.alignment = .natural

let attributes: [NSAttributedString.Key: Any] = [
    .font: UIFont.preferredFont(forTextStyle: .body),
    .paragraphStyle: paragraphStyle
]

bodyLabel.attributedText = NSAttributedString(string: content, attributes: attributes)

Using lineHeightMultiple ensures the line gap scales smoothly if the user adjusts system accessibility text sizes under Dynamic Type.

4. Configure line spacing in Interface Builder (Storyboards / XIBs)

If you prefer configuring labels visually without Swift code, use the Xcode Attributes Inspector:

  1. Select the UILabel on your Storyboard or XIB canvas.
  2. In the Attributes Inspector (top-right utility panel), find the Text dropdown.
  3. Change the dropdown from Plain to Attributed.
  4. Select the text inside the text area, or click the small alignment/spacing icon.
  5. Locate the Spacing field and enter your required point value (e.g., 4 or 6).
  6. Ensure Lines is set to 0 (or your desired maximum) to allow multi-line wrapping.

Note: If you later assign to label.text = "New string" programmatically, UIKit converts the label back into a plain string and discards your storyboard attributed spacing. You must continue updating the label via label.attributedText.

How to prevent it

  • Avoid assigning to label.text after setting attributedText: Setting label.text clears previously assigned paragraph styles and attributes.
  • Always specify numberOfLines = 0: Line spacing has no effect if the label is constrained to a single line.
  • Include the font in your attributes dictionary: Do not rely on label.font after applying attributed text; define .font alongside .paragraphStyle to prevent baseline calculation errors.
  • Account for Dynamic Type: When using custom line spacing, test larger accessibility text sizes to ensure text lines do not overlap or truncate unexpectedly.

FAQ

Can I set negative line spacing to reduce the gap between lines?

Yes, you can set lineSpacing to a small negative value (such as -2), but doing so can cause diacritics or descenders (like the tail of “g” or “y”) to collide with the line below. A safer way to reduce line height proportionally is to set lineHeightMultiple to a value below 1.0 (such as 0.85).

Why did my custom line spacing disappear after updating the text?

If you set text using label.text = newValue, UIKit converts the label’s storage to a plain string and strips the existing NSParagraphStyle. You must construct and assign a new NSAttributedString to label.attributedText whenever the string content changes.

Does line spacing apply to the last line of text?

No, lineSpacing only applies to the breaks between lines within a multi-line paragraph. It does not add extra padding below the final line or above the first line of the label.

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.