UIScrollView that adapts to fit content

So I have to say, I have had some difficulty getting UIScrollView to play nicely with Autolayout. There are posts such as here that look to be the solution to many people’s problems. I didn’t really get it working for me.

So I thought I’d write what did. To outline what I’d like to do, I’d like to avoid re-purposing UITableView and cells to display content. It is a lot more boilerplate and it adds complexity. That said, you can have content that is infinitely long and it will be displayed efficiently.

Basically I’d like to create a content view that is potentially larger or shorter than the iPhone screen. Using a UIScrollView is a good candidate for this.

I’ll just get to the summary of the approach. I’ll create a contentView that has its width pinned to the scroll view’s (ultimately the screen’s) width, pinned at the top, but be flexible to allow this contentView to be as large or as small as it needs to be (for example because you have a text view).

I did this by making sure that I pin the scrollView to the superview’s edges, then I add a contentView to the scrollView and pin its top, left, right edges, and pin its width to be the scrollView’s width.

Assuming the component in my contentView is a UITextView, and thus has content that is dynamic, whenever I add this to my storyboard, I make sure that scrolling is disabled on the text view. Then I add an outlet for a NSLayoutConstraint for the text view’s height. I use the delegate callback of UITextView (textViewDidChange) to calculate the ideal height of the text view, then use that height to set the constant of the layout constraint I just mentioned. This resizes the text view to its dynamic content, and thus the size of the contentView.

    func textViewDidChange(_ textView: UITextView) {
        resizeTextViewToFitContent()
    }
    
    func resizeTextViewToFitContent() {
        self.textViewHeightConstraint.constant = self.textView.sizeThatFits(CGSize(width: self.textView.frame.size.width, height: CGFloat.greatestFiniteMagnitude)).height
    }



If I set this up in interface builder and don’t pin the bottom of the contentView, I will get an error about the scrollView having ambiguous content height. This can be suppressed by setting the Ambiguity to “never verify”.

Now all I need is a custom scroll view that will update its content size whenever the contentView changes size:

class ContentScrollView: UIScrollView {
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // alternatively, you can set an IBOutlet weak var contentView: UIView! instead of assuming one subview.
        if let contentView = self.subviews.first {
            self.contentSize = contentView.bounds.size
        }
    }
}

And that’s it! How to make dynamic content be scrollable where required. You’ll notice that if your contentSize is less than the screen height, no scrolling, no scroll indicators; you’ll never even know it’s embedded in a scrollView.

To summarize:

  1. Add UIScrollView inside the main view in Storyboard
  2. Add UIView inside the UIScrollView
  3. Add UITextView inside the UIView (the view added in step 2)
  4. Make sure “Scrolling Enabled” of UITextView is unchecked
  5. Add 4 constraints (leading, trailing, top, bottom) on UIScrollView
  6. Add 3 constraints (leading, trailing, top) on UIView (the view added in step 2)
  7. Add “Width Equally” constraint on UIView (the view added in step 2) and the main view
  8. Add 5 constraints (leading, trailing, top, bottom, height) on UITextView. After this step you shouldn’t get any errors and warnings on constraints.
  9. Add UITextView height constraint IBOutlet on the ViewController. @IBOutlet weak var textViewHeightConstraint: NSLayoutConstraint! and connect it in Storyboard
  10. Make your view controller conform to UITextViewDelegate and implement methods listed above, and make sure the text view’s delegate is this view controller.
  11. If you set the text programmatically, call resizeTextViewToFitContent
  12. In your storyboard, for the scrollView, set Ambiguity to “never verify”
  13. Make sure the UIScrollView is the ContentScrollView given above.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s