Fix: UITextView Does Not Grow to Fit Its Text in UIKit

Fix: UITextView Does Not Grow to Fit Its Text in UIKit

Written by

in

Quick answer: To resize a UITextView as its text changes, measure it with sizeThatFits using its current width, then update its height constraint. Recalculate after edits and once the view has a nonzero layout width; avoid assigning its frame when Auto Layout controls it.

Why this happens

A UITextView is a scroll view, so it can display more text by scrolling without changing its height. If you want the view—and possibly its parent—to grow instead, your layout must give the text view a height that matches its content.

The height depends on the text view’s available width. A sentence that fits on one line at a wide width may wrap to several lines when the view narrows. Measure only after the text view has a usable width, and recalculate when the text or width changes.

With Auto Layout, changing frame.size.height directly is not a lasting fix: constraints determine the view’s final size during layout. Measure the required height and update a height constraint instead. The contentSize property can also be useful, but its value depends on the text view’s layout state. sizeThatFits(_:) is a straightforward way to ask UIKit how much space the current content needs at a specific width.

How to fix it

1. Update the height constraint with sizeThatFits

In Interface Builder, add a height constraint to the text view and connect it to an outlet. Make sure the text view also has constraints that establish its width. Then measure the text view at its current width and assign the measured height to the constraint.

import UIKit

final class NotesViewController: UIViewController, UITextViewDelegate {
    @IBOutlet private weak var textView: UITextView!
    @IBOutlet private weak var textViewHeightConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
        textView.isScrollEnabled = false
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        updateTextViewHeight()
    }

    func textViewDidChange(_ textView: UITextView) {
        updateTextViewHeight()
    }

    private func updateTextViewHeight() {
        let width = textView.bounds.width
        guard width > 0 else { return }

        let fittingSize = textView.sizeThatFits(
            CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
        )
        let height = ceil(fittingSize.height)

        // Avoid triggering another layout pass when the height is unchanged.
        if abs(textViewHeightConstraint.constant - height) > 0.5 {
            textViewHeightConstraint.constant = height
        }
    }
}

The text view’s delegate calls the measurement method after each edit. viewDidLayoutSubviews() also measures after the first layout, when Auto Layout has established the width, and after later layout changes. The width check prevents measuring before that first layout has produced a usable size.

Setting isScrollEnabled to false is appropriate when the text view should expand rather than scroll. If the design has a maximum height and should scroll after reaching it, keep scrolling enabled and cap the measured height instead. In either case, make sure the vertical constraints allow the text view and its parent to grow; a fixed-height parent or conflicting constraints can prevent the visible expansion.

2. Recalculate when the available width changes

The measurement is valid for the width passed to sizeThatFits. If the device rotates, a window is resized, or another layout change alters the text view’s width, measure it again. The viewDidLayoutSubviews() call in the example handles ordinary UIKit layout changes; the equality check prevents repeated updates when the result has not changed.

If the text view is inside a table view cell, its height constraint can determine the text view’s height, while the cell’s other constraints determine the cell’s overall height. Configure the table view to use self-sizing rows and provide a complete vertical constraint chain through the cell’s content view. Updating the text view constraint alone cannot make a row expand if the cell is still given a fixed row height.

3. Use contentSize only when managing the frame yourself

For a view laid out without Auto Layout, you can set its frame height from contentSize after the text view has been added to the view hierarchy and laid out:

var frame = textView.frame
frame.size.height = textView.contentSize.height
textView.frame = frame

Run this after assigning the text and after the text view’s width is known. When text changes, repeat the update. This frame-based approach is an alternative for a frame-based layout; do not combine it with a height constraint that also controls the text view’s size.

4. Check the surrounding constraints if it still stays short

If the measured height changes but the interface does not, inspect the layout around the text view. Confirm that its height constraint is the one being updated, that it is not pinned into a fixed-height container, and that the parent has room to expand. In a stack view, for example, check that the stack view and its parent are not constrained to a height that prevents growth.

Also verify that the measurement uses the text view’s actual laid-out width—not the screen width or a width from before rotation. Let sizeThatFits account for the text view’s text layout, including its text-container insets, rather than estimating height from a character count or line count.

How to prevent it

  • Measure using the text view’s current width, after layout has established that width.
  • Update the height constraint when text changes and when layout changes can affect the width.
  • Use either Auto Layout constraints or manual frame sizing for the text view, not both.
  • Check that parent views and table view cells are allowed to expand.
  • Choose scrolling behavior deliberately: disable scrolling for an expanding field, or keep it enabled when the field has a maximum height.

FAQ

Should I use sizeThatFits or contentSize?

For an Auto Layout interface, use sizeThatFits to measure the required height and update the height constraint. contentSize is useful for frame-based sizing, but make sure the text view has been added and laid out before relying on it.

Why does the text view have the wrong height on first display?

It may be measured before Auto Layout has assigned its final width. Defer the first measurement until layout has run, then calculate again when text changes.

Does this work when the text view is inside a table view cell?

Yes, provided the cell uses self-sizing and its constraints allow the content view to grow. Update the text view’s height constraint and make sure the cell does not have a conflicting fixed row height.

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.