10

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.

enter image description here

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.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Osama Naeem
  • 1,830
  • 5
  • 16
  • 34

3 Answers3

17

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()
    }
}
Upholder Of Truth
  • 4,643
  • 2
  • 13
  • 23
  • BINGO! It works but there are couple of issues: 1. Firstly, I had to remove the bottom constraint and then the text view started changing height dynamically. 2. The textview is changing height but is moving downwards. I want it to move upward and maintain the bottom spacing. – Osama Naeem Jan 08 '18 at 21:14
  • Is the addtasktextview constrained in anyway that might stop it changing height as they would also prevent the addtextview changing height. It's difficult to say without seeing more of your project. – Upholder Of Truth Jan 08 '18 at 21:16
  • Nice answer bro – reza_khalafi May 15 '18 at 12:08
  • @UpholderOfTruth, Work for me , Nice answer . – Jaywant Khedkar Apr 28 '20 at 04:26
  • @UpholderOfTruth, thanks for the splendid solution, works great. But once nce you have a UITableView or UIScrollView in the same ViewController with constraints linked to the UITextView, self.adjustTextViewHeight() should be run viewDidAppear(), otherwise you will get an error message, that UITableView can't be set up properly – carlson Feb 15 '23 at 09:07
8

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 :)

nCr78
  • 460
  • 7
  • 9
  • to stop my textView from growing to a certain height this was the perfect solution, exactly what I was looking for. I used 200 and view.layoutIfNeeded() though :) – Lance Samaria Mar 12 '19 at 12:42
  • you should add your answer to this question https://stackoverflow.com/q/38415847/4833705. Your answer here is a very simple solution there – Lance Samaria Mar 12 '19 at 12:48
4

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. Demo

Miket25
  • 1,895
  • 3
  • 15
  • 29
  • not working still. Do I have to tweak my constraints? I am stuck in this problem for weeks now still have no idea how to go about it? – Osama Naeem Jan 08 '18 at 20:40
  • @OsamaNaeem You'll have to be more specific as to what is not working. How did you update your frame's height? Did you set your constrains properly? – Miket25 Jan 08 '18 at 20:42
  • The constraints that I am using is posted above in the main post. As for changing the height: I am measuring the content size and the height and applying it to the textview height. Following this code [link](https://stackoverflow.com/questions/46132752/swift-3-uitextview-dynanmic-height-programmatically) – Osama Naeem Jan 08 '18 at 20:47