iOS
Swift
constraints

A content-wrapping, scrollable multiline UITextView with maximum size in Swift

Conditions

Assume the following, common requirements:

  • A UITextView with text appears at center of screen
  • The textView height must adjust to fit the text height, but is scrollable if beyond a specified maximum height
  • The textView width must adjust to fit the text width, up to a maximum width
  • The textView must use center justified text if only one line, natural justified text other otherwise

Solution

  • Initialize the UITextView with the following properties
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isScrollEnabled = true
textView.showsVerticalScrollIndicator = true
textView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
  • Set the textView properties to multiline:
textView.numberOfLines = 0
textView.lineBreakMode = .byWordWrapping
  • Set vertical content compression on the textView, with a value which is less than the default 1000:
textView.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 500), for: UILayoutConstraintAxis.vertical)
  • Put the textView inside a custom container view, and constrain their edges to be equal

  • Set fixed constraints on the container width and height, so that they are no more than some fraction of a parent view size. These constraints will adopt the default priority of 1000, and are therefore prioritized ahead of constraints set by the textView content.

    NSLayoutConstraint(item: containerView,
                       attribute: .height,
                       relatedBy: .lessThanOrEqual,
                       toItem: parentView,
                       attribute: .height,
                       multiplier: containerSizeFraction, // e.g. 0.7
                       constant: 0.0),

  • Override "layoutSubviews" of the container view to perform the remainder of the fitting - it will be called when adding the the UITextView to the container view. We summarize the fitting with the following lines:

First, ensure the textView is always showing the top line when its layout is rearranged.

override func layoutSubviews() {
        super.layoutSubviews()

    textView.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1), animated: false)

Then compute the textView size based its content

    let size = textView.sizeThatFits(CGSize(width: textView.contentSize.width, height: CGFloat.greatestFiniteMagnitude))

Constrain the textView width and height to size.width and size.height respectively. For each constraint, set the priority as:

    widthConstraint.priority = UILayoutPriority(rawValue: 750) }
    heightConstraint.priority = UILayoutPriority(rawValue: 750) }

Set the text alignment according to the current number of lines.

    textView.textAlignment = ( textView.contentSize.height >= font.lineHeight) ? .natural : .center
    textView.setNeedsDisplay()

It is helpful to briefly show the scroll indicator to highlight if the textView is now scrollable

    textView.flashScrollIndicators()