UITextView
Auto Layout
iOS Development
Dynamic Text
Interface Builder

UITextView that expands to text using auto layout

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A UITextView that grows to fit its content is a common requirement in iOS apps — chat bubbles, comment fields, and form inputs all need this behavior. The key is disabling scrolling (isScrollEnabled = false), which tells the text view to use its intrinsic content size instead of a fixed scrollable frame. Combined with Auto Layout constraints that do not fix the height, the text view automatically expands as the user types. For dynamic height in table or collection view cells, you also need to invalidate the cell layout when the text changes.

Basic Self-Sizing UITextView

swift
1class ViewController: UIViewController {
2    let textView = UITextView()
3
4    override func viewDidLoad() {
5        super.viewDidLoad()
6
7        textView.translatesAutoresizingMaskIntoConstraints = false
8        textView.isScrollEnabled = false  // Critical: enables intrinsic content size
9        textView.font = UIFont.systemFont(ofSize: 16)
10        textView.layer.borderColor = UIColor.systemGray4.cgColor
11        textView.layer.borderWidth = 1
12        textView.layer.cornerRadius = 8
13        view.addSubview(textView)
14
15        NSLayoutConstraint.activate([
16            textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
17            textView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
18            textView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
19            // No height constraint — the text view sizes itself
20        ])
21    }
22}

When isScrollEnabled is false, UITextView reports an intrinsic content size equal to its text content. Auto Layout uses this to determine the height automatically.

Setting a Maximum Height

To prevent the text view from growing beyond a maximum height, add a height constraint and re-enable scrolling when the content exceeds it:

swift
1class ExpandingTextView: UITextView {
2    var maxHeight: CGFloat = 200
3
4    override var intrinsicContentSize: CGSize {
5        let size = super.intrinsicContentSize
6        if size.height > maxHeight {
7            isScrollEnabled = true
8            return CGSize(width: size.width, height: maxHeight)
9        } else {
10            isScrollEnabled = false
11            return size
12        }
13    }
14
15    override var text: String! {
16        didSet { invalidateIntrinsicContentSize() }
17    }
18}
19
20// Usage
21let textView = ExpandingTextView()
22textView.maxHeight = 150
23textView.font = UIFont.systemFont(ofSize: 16)
24textView.translatesAutoresizingMaskIntoConstraints = false

When text exceeds maxHeight, scrolling is enabled and the text view stops growing. When text shrinks below the threshold, it resizes back down.

Using UITextViewDelegate for Dynamic Updates

swift
1class ViewController: UIViewController, UITextViewDelegate {
2    let textView = UITextView()
3    var heightConstraint: NSLayoutConstraint!
4    let maxHeight: CGFloat = 200
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8        textView.delegate = self
9        textView.isScrollEnabled = false
10        textView.font = UIFont.systemFont(ofSize: 16)
11        textView.translatesAutoresizingMaskIntoConstraints = false
12        view.addSubview(textView)
13
14        heightConstraint = textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40)
15
16        NSLayoutConstraint.activate([
17            textView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
18            textView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
19            textView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: -8),
20            heightConstraint
21        ])
22    }
23
24    func textViewDidChange(_ textView: UITextView) {
25        let size = textView.sizeThatFits(CGSize(
26            width: textView.frame.width,
27            height: .greatestFiniteMagnitude
28        ))
29
30        if size.height >= maxHeight {
31            textView.isScrollEnabled = true
32            heightConstraint.constant = maxHeight
33        } else {
34            textView.isScrollEnabled = false
35            heightConstraint.constant = size.height
36        }
37    }
38}

Self-Sizing Table View Cells

For expanding text views inside UITableViewCell:

swift
1class TextViewCell: UITableViewCell {
2    let textView = UITextView()
3    weak var tableView: UITableView?
4
5    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
6        super.init(style: style, reuseIdentifier: reuseIdentifier)
7
8        textView.isScrollEnabled = false
9        textView.font = UIFont.systemFont(ofSize: 16)
10        textView.translatesAutoresizingMaskIntoConstraints = false
11        contentView.addSubview(textView)
12
13        NSLayoutConstraint.activate([
14            textView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
15            textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
16            textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
17            textView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)
18        ])
19    }
20
21    required init?(coder: NSCoder) { fatalError() }
22}
23
24// In UITableViewDelegate
25func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
26    let cell = tableView.dequeueReusableCell(withIdentifier: "TextViewCell", for: indexPath) as! TextViewCell
27    cell.tableView = tableView
28    cell.textView.text = data[indexPath.row]
29    return cell
30}

Set tableView.rowHeight = UITableView.automaticDimension and tableView.estimatedRowHeight = 60 for automatic cell sizing.

SwiftUI Equivalent

In SwiftUI, TextEditor automatically sizes with the .fixedSize(horizontal:vertical:) modifier:

swift
1struct ExpandingTextEditor: View {
2    @State private var text = ""
3
4    var body: some View {
5        TextEditor(text: $text)
6            .frame(minHeight: 40, maxHeight: 200)
7            .fixedSize(horizontal: false, vertical: true)
8            .padding(4)
9            .overlay(
10                RoundedRectangle(cornerRadius: 8)
11                    .stroke(Color.gray.opacity(0.3))
12            )
13    }
14}

In iOS 16+, TextField also supports multi-line input with the axis: .vertical parameter:

swift
TextField("Enter text...", text: $text, axis: .vertical)
    .lineLimit(1...5)

Common Pitfalls

  • Forgetting to set isScrollEnabled = false: This is the single most important step. When scrolling is enabled (the default), UITextView uses a fixed frame and scrolls its content instead of expanding. The intrinsic content size is not reported to Auto Layout.
  • Adding a fixed height constraint: A fixed-height constraint (heightAnchor.constraint(equalToConstant: 100)) prevents the text view from expanding. Use greaterThanOrEqualToConstant for a minimum height, or omit the height constraint entirely.
  • Not calling invalidateIntrinsicContentSize() in custom subclasses: When you override properties like text or attributedText in a UITextView subclass, call invalidateIntrinsicContentSize() to trigger Auto Layout to recalculate the height.
  • Table view cells not resizing dynamically: When text changes in a cell, the table view does not automatically recalculate row heights. Call tableView.beginUpdates() / tableView.endUpdates() in textViewDidChange(_:) to trigger a height recalculation without reloading the cell.
  • Text view jumping when toggling isScrollEnabled: Switching isScrollEnabled between true and false can cause the text view to scroll to the top. Set contentOffset = .zero or preserve the scroll position when toggling to avoid a visual jump.

Summary

  • Set isScrollEnabled = false to make UITextView report its intrinsic content size to Auto Layout
  • Do not add a fixed height constraint — use greaterThanOrEqualToConstant for a minimum height or omit height entirely
  • For a maximum height, switch isScrollEnabled back to true and cap the height when content exceeds the limit
  • In table view cells, use UITableView.automaticDimension for row height and call beginUpdates()/endUpdates() when text changes
  • In SwiftUI, use TextEditor with .fixedSize(horizontal: false, vertical: true) or TextField with axis: .vertical

Course illustration
Course illustration

All Rights Reserved.