Search code examples
iosswiftxcodeuitextviewsizetofit

Expanding UITextView with UIButton instead of automatically based on content


I have a UITextView which is embedded in a UIView amongst a number of other UIViews which are all in a UIScrollView (a form essentially). Instead of the textView automatically expanding with the content, I have a button underneath which I would like the user to be able to click and expand/colapse the textView.

Here is what I have:

var textViewIsExpanded: Bool = false {
    didSet {
        if self.textViewIsExpanded {
            self.expandTextViewButton.isSelected = true
            guard self.myTextView.contentSize.height > 70 else { return }
            self.myTextView.isScrollEnabled = false
            self.myTextView.translatesAutoresizingMaskIntoConstraints = true
            self.myTextView.sizeThatFits(CGSize(width: self.scrollView.width - 24, height: CGFloat.greatestFiniteMagnitude))
        } else {
            self.expandTextViewButton.isSelected = false
            self.myTextView.isScrollEnabled = true
            self.myTextView.translatesAutoresizingMaskIntoConstraints = false
        }
    }
}

@IBAction func expandTextViewButtonTapped(_ sender: UIButton) {
    textViewIsExpanded.toggle()
}

I have tried .sizeToFit() in place of .sizeThatFits(...) which sort of worked but it resized the width along with the height and I am only looking to expand/colapse the height. I'm guessing it is a matter of correctly implementing the CGSize and/or IB constraints but I am not able to land on a solution that works how I want.


Solution

  • First, it's a bad idea to toggle .translatesAutoresizingMaskIntoConstraints between true and false.

    What you probably want is to give your text view a Height constraint of 70... connect it to an @IBOutlet... and then toggle .isActive on that constraint.

    Second, if you have only one line of text, so the content size height is, maybe, 30, and then you call

    textViewIsExpanded = true
    

    your code as-is will set textViewIsExpanded to true but will leave .isScrollEnabled true --- so it won't really be "expanded".

    Third, you need to let auto-layout know that you're changing the sizing behavior of the text view by calling:

    self.myTextView.invalidateIntrinsicContentSize()
    

    after toggling .isScrollEnabled.

    So, add and connect a property for your text view's height constraint:

    @IBOutlet var textViewHeightConstraint: NSLayoutConstraint!
    

    and try changing your code to this:

    var textViewIsExpanded: Bool = false {
        didSet {
            if self.textViewIsExpanded {
                // if contentSize.height is less-than 71
                //  reset to false
                if self.myTextView.contentSize.height < 71 {
                    self.textViewIsExpanded = false
                    return
                } else {
                    self.expandTextViewButton.isSelected = true
                    self.myTextView.isScrollEnabled = false
                    self.textViewHeightConstraint.isActive = false
                }
            } else {
                self.expandTextViewButton.isSelected = false
                self.myTextView.isScrollEnabled = true
                self.textViewHeightConstraint.isActive = true
            }
            self.myTextView.invalidateIntrinsicContentSize()
        }
    }