Hello.
I think, I know why I had issues with my code. Looks like the textViewDidChange method was also influenced by didChangeSize(oldSize:) method which was also called every time I typed in a character and which I needed to handle rotation of my device.
Anyways, I think I have come up with a solution how to handle expanding a textView which also gets adjusted with a rotation. Here is the code for the peer review.
import UIKit
class ViewController: UIViewController, UITextViewDelegate
{
var textView: UITextView!
var btn: UIButton!
var btnSend: UIButton!
var keyboardHeight : CGFloat = 0
var isKeyboardShown = false
var textViewHeight : CGFloat = 0
var textView_inset : CGFloat = 10
var textView_insetBottom : CGFloat = 5
let btnSendWidth : CGFloat = 50
let btnSendHeight : CGFloat = 50
override func viewDidLoad()
{
super.viewDidLoad()
textViewHeight = 35
textView = UITextView(frame: CGRect(x: textView_inset+self.view.safeAreaInsets.left,
y: self.view!.bounds.height,
width: self.view.frame.width-textView_inset*3-self.view.safeAreaInsets.left-self.view.safeAreaInsets.right-btnSendWidth,
height: textViewHeight))
textView.textAlignment = NSTextAlignment.justified
textView.backgroundColor = UIColor.lightGray
// Use RGB colour
textView.backgroundColor = UIColor(red: 39/255, green: 53/255, blue: 182/255, alpha: 1)
// Update UITextView font size and colour
textView.font = UIFont(name: "Verdana", size: 17)
textView.textColor = UIColor.white
// Make UITextView web links clickable
textView.isSelectable = true
textView.isEditable = true
textView.dataDetectorTypes = UIDataDetectorTypes.link
textView.isScrollEnabled = false
// Make UITextView corners rounded
textView.layer.cornerRadius = 10
// Enable auto-correction and Spellcheck
textView.autocorrectionType = UITextAutocorrectionType.yes
textView.spellCheckingType = UITextSpellCheckingType.yes
// myTextView.autocapitalizationType = UITextAutocapitalizationType.None
self.view.addSubview(textView)
textView.delegate = self
registerForKeyboardNotifications()
btn = UIButton()
btn.setTitle("PRESS", for: .normal)
btn.setTitleColor(.red, for: .normal)
btn.frame = CGRect(x: 50, y: 150, width: 100, height: 50)
btn.addTarget(self, action: #selector(self.showTextView(sender:)), for: .touchUpInside)
self.view.addSubview(btn)
btnSend = UIButton()
btnSend.setTitle("SEND", for: .normal)
btnSend.setTitleColor(.white, for: .normal)
btnSend.backgroundColor = .red
btnSend.frame = CGRect(x: self.view.frame.width-self.view.safeAreaInsets.right-btnSendWidth-textView_inset,
y: self.view!.bounds.height,
width: btnSendWidth,
height: btnSendHeight)
btnSend.addTarget(self, action: #selector(self.sendMessage(sender:)), for: .touchUpInside)
self.view.addSubview(btnSend)
}
override func viewWillLayoutSubviews()
{
var textView_y : CGFloat = 0
var btnSend_y : CGFloat = 0
// Required to ensure that the TextView's height is changed dynamically!
if isKeyboardShown
{
textView_y = self.view!.bounds.height-keyboardHeight-textView.frame.height-textView_insetBottom
btnSend_y = self.view!.bounds.height-keyboardHeight-btnSendHeight-textView_insetBottom
}
else
{
textView_y = self.view!.bounds.height
btnSend_y = self.view!.bounds.height
}
textView.frame = CGRect(x: textView_inset+self.view.safeAreaInsets.left,
y: textView_y,
width: self.view.frame.width-textView_inset*3-self.view.safeAreaInsets.left-self.view.safeAreaInsets.right-btnSendWidth,
height: textViewHeight)
btnSend.frame = CGRect(x: self.view.frame.width-self.view.safeAreaInsets.right-self.btnSendWidth-textView_inset,
y: btnSend_y,
width: btnSendWidth,
height: btnSendHeight)
}
@objc func showTextView(sender: UIButton!)
{
textView.becomeFirstResponder()
}
@objc func sendMessage(sender: UIButton!)
{
print("------SEND MESSAGE-------")
}
func textViewDidChange(_ textView: UITextView)
{
let fixedWidth = textView.frame.size.width
// Changing height of the message UITextView
var newSize = textView.sizeThatFits(CGSize.init(width: fixedWidth, height: CGFloat(MAXFLOAT)))
// Limit the height to 100
if newSize.height > 100
{
newSize.height = 100
textView.isScrollEnabled = true
}
else
{
textView.isScrollEnabled = false
}
var newFrame = textView.frame
newFrame.size = CGSize.init(width: CGFloat(fmaxf(Float(newSize.width), Float(fixedWidth))), height: newSize.height)
textViewHeight = newFrame.height
var newOrigin = textView.frame.origin
newOrigin.y -= (newSize.height - newFrame.size.height)
textView.frame = CGRect(origin: textView.frame.origin, size: CGSize(width: newFrame.width, height: newFrame.height))
}
func registerForKeyboardNotifications()
{
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver( self,
selector: #selector(ViewController.keyboardWillShow(_:)),
name: UIResponder.keyboardWillShowNotification,
object: nil )
notificationCenter.addObserver( self,
selector: #selector(ViewController.keyboardWillBeHidden(_:)),
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
func unregisterForKeyboardNotifications()
{
let notificationCenter = NotificationCenter.default
notificationCenter.removeObserver( self,
name: UIResponder.keyboardWillShowNotification,
object: nil)
notificationCenter.removeObserver( self,
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
@objc func keyboardWillShow(_ notification: Notification)
{
isKeyboardShown = true
// Reset the position of the textview to the bottom of the screen
textView.frame = CGRect(x: textView_inset+self.view.safeAreaInsets.left,
y: self.view!.bounds.height,
width: self.view.frame.width-textView_inset*3-self.view.safeAreaInsets.left-self.view.safeAreaInsets.right-btnSendWidth,
height: textViewHeight)
btnSend.frame = CGRect(x: self.view.frame.width-self.view.safeAreaInsets.right-btnSendWidth-textView_inset,
y: self.view!.bounds.height,
width: btnSendWidth,
height: btnSendHeight)
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
{
keyboardHeight = keyboardFrame.cgRectValue.height
// Place the TextView above the keyboard
self.textView.frame.origin.y -= (keyboardHeight+self.textView.frame.height+textView_insetBottom)
// Determine a new TextView height dynamically
let fixedWidth = textView.frame.size.width // Width never changes
var newSize = textView.sizeThatFits(CGSize.init(width: fixedWidth, height: CGFloat(MAXFLOAT))) // Height is based on the number of characters
// Limit the height to 100
if newSize.height > 100
{
newSize.height = 100
textView.isScrollEnabled = true
}
else
{
textView.isScrollEnabled = false
}
var newFrame = textView.frame // A new frame: Fixed width; Dynamically changed height
newFrame.size = CGSize.init(width: CGFloat(fmaxf(Float(newSize.width), Float(fixedWidth))), height: newSize.height)
textViewHeight = newFrame.height // Assign the height value to the object property
textView.frame = CGRect(origin: textView.frame.origin, size: CGSize(width: newFrame.width, height: newFrame.height))
self.textView.layoutIfNeeded()
self.btnSend.frame.origin.y -= (keyboardHeight+self.btnSend.frame.height+textView_insetBottom)
btnSend.frame = CGRect(origin: btnSend.frame.origin, size: CGSize(width: btnSendWidth, height: btnSendHeight))
}
}
@objc func keyboardWillBeHidden(_ notification : NSNotification)
{
isKeyboardShown = false
if let _: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
{
//let newHeight: CGFloat
let duration:TimeInterval = (notification.userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
let animationCurve:UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw)
keyboardHeight = 0
UIView.animate(withDuration: duration,
delay: TimeInterval(0),
options: animationCurve,
animations: {
self.textView.frame.origin.y = self.view!.bounds.height
self.textView.layoutIfNeeded()
self.btnSend.frame.origin.y = self.view!.bounds.height
},
completion: nil)
}
}
func textViewDidBeginEditing(_ textView: UITextView)
{
textView.becomeFirstResponder()
let tapRecognizer = UITapGestureRecognizer(target: self,
action: #selector(tapDetected(_:)))
if self.view != nil {self.view!.addGestureRecognizer(tapRecognizer)}
}
func textViewDidEndEditing(_ textView: UITextView)
{
// Make the active field nil to hide the keyboard
//self.activeField = nil;
}
@objc func tapDetected(_ tapRecognizer: UITapGestureRecognizer)
{
textView?.resignFirstResponder()
if self.view != nil {self.view!.removeGestureRecognizer(tapRecognizer)}
}
}
Thanks!