19

I have spent several days on this with no solution in sight.

I have an inputAccessoryView which consists of a UIView containing a textView and two buttons. The behaviour of the inputAccessoryView is as expected and works fine in all cases except one.

When the height of the textView increases, I am trying to increase the height of the inputAccessoryView by the same amount. When I redefine the height of the inputAccessoryView in textViewDidChange, the inputAccessoryView increases height downwards over the keyboard instead of upwards.

I have tried many different suggestions from SO but nothing has worked. I guess it is the automatically added NSLayoutConstraint of the inputAccessoryView but I have no idea how to change that value in swift and iOS 8.3.

func textViewDidChange(textView: UITextView) {

    var contentSize = messageTextView.sizeThatFits(CGSizeMake(messageTextView.frame.size.width, CGFloat.max))

    inputAccessoryView.frame.size.height = contentSize.height + 16

}

adding

inputAccessoryView.setTranslatesAutoresizingMaskIntoConstraints(true)

to the above code helps and the inputAccessoryView height increases upwards correctly however I get Unable to simultaneously satisfy constraints for several constraints and it is very difficult to identify the offenders. Also I get an odd effect of the textView creating extra space below on every second instance of a new line.

thanks.

liushuaikobe
  • 2,152
  • 1
  • 23
  • 26
alionthego
  • 8,508
  • 9
  • 52
  • 125
  • i've noticed the first call of textViewDidChange does correctly resize the inputAccessoryView without the setTranslatesAutoresizingMaskIntoConstraints line, but subsequent calls do not have any effect. I wonder why that is and how i can restore it to that initial state everytime – alionthego Aug 05 '15 at 12:58

2 Answers2

53

To make input accessory view grow vertically you just set its autoresizingMask = .flexibleHeight, calculate its intrinsicContentSize and let the framework do the rest.

The code:

class InputAccessoryView: UIView, UITextViewDelegate {

    let textView = UITextView()

    override init(frame: CGRect) {
        super.init(frame: frame)

        // This is required to make the view grow vertically
        self.autoresizingMask = UIView.AutoresizingMask.flexibleHeight

        // Setup textView as needed
        self.addSubview(self.textView)
        self.textView.translatesAutoresizingMaskIntoConstraints = false
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[textView]|", options: [], metrics: nil, views: ["textView": self.textView]))
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[textView]|", options: [], metrics: nil, views: ["textView": self.textView]))

        self.textView.delegate = self

        // Disabling textView scrolling prevents some undesired effects,
        // like incorrect contentOffset when adding new line,
        // and makes the textView behave similar to Apple's Messages app
        self.textView.isScrollEnabled = false
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var intrinsicContentSize: CGSize {
        // Calculate intrinsicContentSize that will fit all the text
        let textSize = self.textView.sizeThatFits(CGSize(width: self.textView.bounds.width, height: CGFloat.greatestFiniteMagnitude))
        return CGSize(width: self.bounds.width, height: textSize.height)
    }

    // MARK: UITextViewDelegate

    func textViewDidChange(_ textView: UITextView) {
        // Re-calculate intrinsicContentSize when text changes
        self.invalidateIntrinsicContentSize()
    }

}
maxkonovalov
  • 3,651
  • 34
  • 36
  • Thank you for a very concise answer. – alionthego Sep 18 '15 at 10:45
  • How did you know how to do this? It's like magic how it just works! I'd love to know the process you went through to learn this technique. – Xavier L. Feb 08 '18 at 12:44
  • 1
    in iOS 11, this method only works when the text entered causes the `UITextBox` to grow. When shrinking, it stays the same size. – Albert Bori Aug 28 '18 at 22:58
  • Just in case anyone is new here, it's `override var intrinsicContentSize: CGSize { ... }` and `autoresizingMask = .flexibleHeight` in the current swift version (4.2). Great answer BTW – gujci Dec 14 '18 at 17:32
3

Fast forward to 2020, you can just do the following, everything else the same as in maxkonovalov's answer

override var intrinsicContentSize: CGSize {
    return .zero
}

// MARK: UITextViewDelegate

func textViewDidChange(_ textView: UITextView) {
    sizeToFit()
}
Jack Guo
  • 3,959
  • 8
  • 39
  • 60