Search code examples
iosswiftuiscrollviewuitextview

Dynamically adjust UIScrollView contentSize depending on the overall size of a UIView and UITextView


I have a project where it has a viewController and inside of it there is a Scroll view that has a UILabel and a UITextView as subviews. Every time I open the app I retrieve data from a Google Firestore database and therefore updates the UILabel as the Title and the UITextView as the text body. I have set the constraints and everything is working perfectly. However, I don't know how to calculate the contentView height of UIScrollView if the size of the textView exceeds the boundaries of the UIViewController's view so that you can scroll down to read the rest of the text. I have a tabBar at the bottom as well which means that the scroll view should stop above that.

Here is my code.

    import UIKit

class StoryViewController: UIViewController {

    var storyTitle = String()
    var storyBody = ""


    let titleLabel: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        label.numberOfLines = 0
        label.font = UIFont(name: "Arial-BoldMT", size: 28)
        label.textColor = UIColor.black
        label.translatesAutoresizingMaskIntoConstraints = false

        return label
    }()

    let textView: UITextView = {
        let textView = UITextView()
        textView.textAlignment = .left
        textView.isEditable = false
        textView.isSelectable = false
        textView.font = UIFont(name: "ArialMT", size: 19)
        textView.isScrollEnabled = false
        textView.translatesAutoresizingMaskIntoConstraints = false

        return textView
    }()

    let scrollView: UIScrollView = {
        let scrollingView = UIScrollView()
        scrollingView.translatesAutoresizingMaskIntoConstraints = false
        return scrollingView
    }()


    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationController?.navigationBar.shadowImage = UIImage()
        self.view.backgroundColor = UIColor.white


        titleLabel.text = storyTitle
        textView.text = storyBody.replacingOccurrences(of: "NL", with: "\n\n")
        textView.sizeToFit()

        scrollView.contentSize = CGSize(width: self.view.frame.width, height: textView.contentSize.height)

        view.addSubview(scrollView)

        scrollView.addSubview(titleLabel)
        scrollView.addSubview(textView)

        NSLayoutConstraint.activate([

        scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),


        titleLabel.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor),
        titleLabel.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor),
        titleLabel.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16),
        titleLabel.bottomAnchor.constraint(equalTo: textView.topAnchor, constant: -16),
        titleLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),


        textView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
        textView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
        textView.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor),
        textView.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor),


        ])

I have tried many things but nothing seems to work.

Thank you all for your time and responses in advance.


Solution

  • You are close... but you don't need to worry about setting the .contentSize of the scroll view. Let auto-layout handle that for you.

    The key point you were missing was a bottom constraint for your scroll view's subviews.

    Here is your class (with example title and body text), only slightly modified to work. The comments I added should make it clear:

    class StoryViewController: UIViewController {
    
        var storyTitle = String()
        var storyBody = ""
    
        let titleLabel: UILabel = {
            let label = UILabel()
            label.textAlignment = .center
            label.numberOfLines = 0
            label.font = UIFont(name: "Arial-BoldMT", size: 28)
            label.textColor = UIColor.black
            label.translatesAutoresizingMaskIntoConstraints = false
    
            return label
        }()
    
        let textView: UITextView = {
            let textView = UITextView()
            textView.textAlignment = .left
            textView.isEditable = false
            textView.isSelectable = false
            textView.font = UIFont(name: "ArialMT", size: 19)
            textView.isScrollEnabled = false
            textView.translatesAutoresizingMaskIntoConstraints = false
    
            return textView
        }()
    
        let scrollView: UIScrollView = {
            let scrollingView = UIScrollView()
            scrollingView.translatesAutoresizingMaskIntoConstraints = false
            return scrollingView
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.navigationController?.navigationBar.shadowImage = UIImage()
            self.view.backgroundColor = UIColor.white
    
            // sample Title and Body
            storyTitle = "Example\nTitle Label"
    
            storyBody = ""
            storyBody += "UITextView:"
            storyBody += "NL"
            storyBody += "When a user taps a text view, a keyboard appears; when a user taps Return in the keyboard, the keyboard disappears and the text view can handle the input in an application-specific way. You can specify attributes, such as font, color, and alignment, that apply to all text in a text view."
            storyBody += "NL"
            storyBody += "UIScrollView:"
            storyBody += "NL"
            storyBody += "UIScrollView provides a mechanism to display content that is larger than the size of the application’s window and enables users to scroll within that content by making swiping gestures."
            storyBody += "NL"
            storyBody += "UILabel:"
            storyBody += "NL"
            storyBody += "A label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label."
            storyBody += "NL"
            storyBody += "UIButton:"
            storyBody += "NL"
            storyBody += "You can set the title, image, and other appearance properties of a button. In addition, you can specify a different appearance for each button state."
    
            titleLabel.text = storyTitle
            textView.text = storyBody.replacingOccurrences(of: "NL", with: "\n\n")
    
            // you do not need either of these two lines
            //textView.sizeToFit()
            //scrollView.contentSize = CGSize(width: self.view.frame.width, height: textView.contentSize.height)
    
            view.addSubview(scrollView)
    
            scrollView.addSubview(titleLabel)
            scrollView.addSubview(textView)
    
            NSLayoutConstraint.activate([
    
                scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
                scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    
                // don't use .centerXAnchor constraints
                //titleLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
                //textView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
    
                // don't need to set titleLabel bottom constraint
                //titleLabel.bottomAnchor.constraint(equalTo: textView.topAnchor, constant: -16),
    
                titleLabel.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16),
                titleLabel.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor),
                titleLabel.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor),
    
                textView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
                textView.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor),
                textView.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor),
    
                // set the textView's bottomAnchor to let auto-layout determine content size
                textView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
    
            ])
    
        }
    }