I've built a growing UITextView attached to the keyboard, similar to the stock Messages app, by loading a .xib into the keyboard's inputAccessoryView as such:
self.keyboardAccessoryView = [[[NSBundle mainBundle]
loadNibNamed:@"KeyboardAccessoryView"
owner:self options:nil]
firstObject];
The .xib looks like this, and is using layout constraints so that the textView grows vertically when the user enters more lines of text:
This is all working great, with rotation and everything, except one big bug -- when the text is multiple lines, only the very bottom line handles touch events. This means that a user cannot scroll inside the UITextView because their touch events are being passed to the (dark gray) view in the back and scrolling that instead. They also cannot select and edit their text on the top 3 lines.
I think I could do a workaround by capturing the coordinates of all tap events and checking if the keyboard is open and how tall the UITextView is, then selecting the correct element to receive the touch event. But this is brittle solution that is more complicated with rotation. Is there something I'm missing in my auto-growing text view approach, or some easier fix?
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 = UIViewAutoresizing.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.scrollEnabled = 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.max))
return CGSize(width: self.bounds.width, height: textSize.height)
}
// MARK: UITextViewDelegate
func textViewDidChange(_ textView: UITextView) {
// Re-calculate intrinsicContentSize when text changes
self.invalidateIntrinsicContentSize()
}
}
This approach is quite straightforward and reliable, as it doesn't require hacking constraints or recreating the view each time its size changes.
I figured out that even though the keyboard accessory input grows vertically with auto layout, its frame does not. So you have to adjust the keyboard accessory's frame each time the height of the uitextview grows, shrinks, and rotates. This introduces some complications as UITextView's in iOS7 are notoriously buggy -- I noticed behavior was not consistent across iPhone, iPad, and the Simulator.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With