UITextView height to change dynamically when typin

2020-06-04 04:12发布

问题:

I have this UITextView & I want it's height to change dynamically when the user is typing on it. I want to do it programmatically. I have the UITextView above another UIView. The constraints are set as below:

 addtextview.leadingAnchor.constraint(equalTo: addtasktextview.leadingAnchor, constant: 8).isActive = true
      addtextview.trailingAnchor.constraint(equalTo: addtasktextview.trailingAnchor, constant: -8).isActive = true
      addtextview.topAnchor.constraint(equalTo: addtasktextview.topAnchor, constant: 8).isActive = true
      addtextview.bottomAnchor.constraint(equalTo: addtasktextview.bottomAnchor, constant: -40).isActive = true

Interestingly enough, I am using textview in tableViewCells as well and dynamically changing the height by using just this constraint method, but over here it is not working. I want the textview's height to increase in such a way that it moves upward. So when a new line starts, the top part should move maintaining the spacing below.

How can I do it? Help will be appreciated it.

UPDATE: I was able to get it working with @upholder-of-truth 's answer below. I was also able to dynamically change the parent UIView container height by finding the difference between the textview normal height and the newSize.height and then adding that difference to the container's height.

回答1:

First make sure your class adopts the UITextViewDelegate protocol so you can be informed when the text changes like this:

class MyClass: UIViewContoller, UITextViewDelegate

Next define this variable somewhere in your class so that you can keep track of the height in a constraint:

var textHeightConstraint: NSLayoutConstraint!

Next add the following constraint and activate it:

    self.textHeightConstraint = addtextview.heightAnchor.constraint(equalToConstant: 40)
    self.textHeightConstraint.isActive = true

(if you don't do this in viewDidLoad you need to make textHeightConstraint an optional)

Next subscribe to the delegate (if not already done):

addTextView.delegate = self

Add this function which recalculates the height constraint:

func adjustTextViewHeight() {
    let fixedWidth = addtextview.frame.size.width
    let newSize = addtextview.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
    self.textHeightConstraint.constant = newSize.height
    self.view.layoutIfNeeded()
}

Next add a call to that function after the constraints are created to set the initial size:

self.adjustTextViewHeight()

Finally add this method to adjust the height whenever the text changes:

func textViewDidChange(_ textView: UITextView) {
    self.adjustTextViewHeight()
}

Just in case that is all confusing here is a minimal example in a sub class of a UIViewController:

class ViewController: UIViewController, UITextViewDelegate {
    @IBOutlet var textView: UITextView!
    @IBOutlet var textHolder: UIView!

    var textHeightConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        textView.leadingAnchor.constraint(equalTo: textHolder.leadingAnchor, constant: 8).isActive = true
        textView.trailingAnchor.constraint(equalTo: textHolder.trailingAnchor, constant: -8).isActive = true
        textView.topAnchor.constraint(equalTo: textHolder.topAnchor, constant: 8).isActive = true
        textView.bottomAnchor.constraint(equalTo: textHolder.bottomAnchor, constant: -40).isActive = true

        self.textHeightConstraint = textView.heightAnchor.constraint(equalToConstant: 40)
        self.textHeightConstraint.isActive = true

        self.adjustTextViewHeight()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func textViewDidChange(_ textView: UITextView) {
        self.adjustTextViewHeight()
    }

    func adjustTextViewHeight() {
        let fixedWidth = textView.frame.size.width
        let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
        self.textHeightConstraint.constant = newSize.height
        self.view.layoutIfNeeded()
    }
}


回答2:

There's a clever answer here in objective-c where you can detect a newline character in your textView, and adjust your textView's frame appropriately. Here is the swift version:

var previousPosition:CGRect = CGRect.zero
func textViewDidChange(_ textView: UITextView) {
    let position:UITextPosition = textView.endOfDocument
    let currentPosition:CGRect = textView.caretRect(for: position)
    if(currentPosition.origin.y > previousPosition.origin.y){
        /*Update your textView's height here*/
        previousPosition = currentPosition
    }   
}

Make sure that the textView's view controller adopts the UITextViewDelegate and sets self.textView.delegate = self

Demo Here the textView grows down, but having it grow up is a matter of your constraints.



回答3:

After implementing the selected answer's solution from "Upholder Of Truth", I was wondering how to get rid of the weird text bottom offset that is brought by the changing of the height constraint.

A simple solution is setting this at the start of your initialisations:

textView.isScrollEnabled = false

Finally, if you need your textview to stop growing after a specific height, you can change the "adjustTextViewHeight" function to this:

func adjustTextViewHeight() {
    let fixedWidth = growingTextView.frame.size.width
    let newSize = growingTextView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
    if newSize.height > 100 {
       growingTextView.isScrollEnabled = true
    }
    else {
       growingTextView.isScrollEnabled = false
       textViewHeightConstraint.constant = newSize.height
    }
   view.layoutSubviews()
}

Hope this helps :)