iosswiftuitextviewnslayoutconstraintuitextviewdelegate

Toggle Enabling UITextView Scroll After Max Number of Lines


I have a uitextview subview in an inputContainerView as it may appear in a messaging app.

Programmatically, on load, the height anchor of the uitextview is set when the inputContainerView is initialized. This constraint works great in increasing the container height as more lines of text are typed.

My goal is to cap the height of the uitextview after reaching X number of lines where any lines typed thereafter are scrollable. And likewise, when the number of lines falls below the max, it returns to its original form - not scrollable and auto-sizing height based on content.

After multiple trials and research, I've managed to get the height to fixate once hitting the max number of lines, but I cannot seem to figure out how to return it to its original form once the number of lines have fallen below the max. It appears the uitextview height stays stuck at the height the number of lines have maxed on.

Below is relevant code:

//Container View Initialization, onLoad Frame Height is set to 60
override init(frame: CGRect) {
super.init(frame: frame)
autoresizingMask = .flexibleHeight

addSubview(chatTextView)
textView.heightAnchor.constraint(greaterThanOrEqualToConstant: frame.height - 20).isActive = true
}

//TextView Delegate

var isTextViewOverMaxHeight = false
var textViewMaxHeight: CGFloat = 0.0

func textViewDidChange(_ textView: UITextView) {
    let numberOfLines = textView.contentSize.height/(textView.font?.lineHeight)!

    if Int(numberOfLines) > 5 {

        if !isTextViewOverMaxHeight {
            containerView.textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = false
            containerView.textView.heightAnchor.constraint(equalToConstant: 
            containerView.textView.frame.height).isActive = true
            containerView.textView.isScrollEnabled = true                

            textViewMaxHeight = containerView.textView.frame.height
            isTextViewOverMaxHeight = true
        }
    } else {

        if isTextViewOverMaxHeight {
            containerView.textView.heightAnchor.constraint(equalToConstant: textViewMaxHeight).isActive = false
            containerView.textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
            containerView.textView.isScrollEnabled = false                

            isTextViewOverMaxHeight = false
        }
    }
}

Solution

  • You have overcomplicated a simple problem at hand, here is how can you achieve what you want

    class ViewController: UIViewController {
        var heightConstraint: NSLayoutConstraint!
        var heightFor5Lines: CGFloat = 0
    
        override func viewDidLoad() {
            super.viewDidLoad()
            let textView = UITextView(frame: CGRect.zero)
            textView.delegate = self
            textView.backgroundColor = UIColor.red
            textView.translatesAutoresizingMaskIntoConstraints = false
            self.view.addSubview(textView)
            textView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
            textView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
            textView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
            heightConstraint = textView.heightAnchor.constraint(equalToConstant: 30.0)
            heightConstraint.isActive = true
        }
    }
    
    
    extension ViewController: UITextViewDelegate {
        func textViewDidChange(_ textView: UITextView) {
            let numberOfLines = textView.contentSize.height/(textView.font?.lineHeight)!
    
            if Int(numberOfLines) > 5 {
                self.heightConstraint.constant = heightFor5Lines
            } else {
                if Int(numberOfLines) == 5 {
                    self.heightFor5Lines = textView.contentSize.height
                }
                self.heightConstraint.constant = textView.contentSize.height
            }
            textView.layoutIfNeeded()
        }
    }
    

    enter image description here

    How this works?:

    Its simple, your textView starts off with some height (height constraint is necessary here because I haven't neither disabled its scroll nor have I provided bottom constraint, so it does not have enough data to evaluate its intrinsic height) and every time text changes you check for number of lines, as long as number of lines is less than threshold number of lines you keep increasing the height constraint of your textView so that its frame matches the contentSize (hence no scrolling) and once it hits the expected number of lines, you restrict height, so that textView frame is less than the actual content size, hence scrolls automatically

    Hope this helps