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()