Reputation: 401
I am trying to place a textview just above the keyboard. The keyboard has two views - one default view and one custom view. A button is there to toggle between these two keyboards.
I am trying to use the following code to position the textview just above the keyboard. But it is behaving very weirdly. The keyboardWillShow notification is not getting called all the times.
Note: I wanted it to support for both portrait and landscape.
class ViewController: UIViewController {
let textView = UITextView()
let button = UIButton()
var keyboardHeight: CGFloat = 0
var keyboardView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
textView.backgroundColor = .lightGray
button.backgroundColor = .red
self.view.addSubview(textView)
self.view.addSubview(button)
}
@objc func buttonAction() {
if self.textView.inputView == nil {
self.textView.inputView = keyboardView
self.textView.inputView?.autoresizingMask = .flexibleHeight
self.textView.reloadInputViews()
textView.frame = CGRect(x: 0, y: self.view.frame.size.height - self.textView.inputView!.frame.size.height - 50, width: self.view.frame.size.width - 50, height: 50)
button.frame = CGRect(x: self.view.frame.size.width - 50, y: self.view.frame.size.height - self.textView.inputView!.frame.size.height - 50, width: 50, height: 50)
} else {
self.textView.inputView = nil
self.textView.reloadInputViews()
textView.frame = CGRect(x: 0, y: self.view.frame.size.height - self.keyboardHeight - 50, width: self.view.frame.size.width - 50, height: 50)
button.frame = CGRect(x: self.view.frame.size.width - 50, y: self.view.frame.size.height - self.keyboardHeight - 50, width: 50, height: 50)
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
textView.frame = CGRect(x: 0, y: self.view.frame.size.height - self.keyboardHeight - 50, width: self.view.frame.size.width - 50, height: 50)
button.frame = CGRect(x: self.view.frame.size.width - 50, y: self.view.frame.size.height - self.keyboardHeight - 50, width: 50, height: 50)
}
@objc func keyboardWillShow(notification: Notification) {
if let userInfo = notification.userInfo {
if let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.keyboardHeight = keyboardFrame.size.height
textView.frame = CGRect(x: 0, y: self.view.frame.size.height - self.keyboardHeight - 50, width: self.view.frame.size.width - 50, height: 50)
button.frame = CGRect(x: self.view.frame.size.width - 50, y: self.view.frame.size.height - self.keyboardHeight - 50, width: 50, height: 50)
}
}
}
}
Upvotes: 2
Views: 2330
Reputation: 20379
If you wanna get the height of Keyboard when keyboard frame changes because of you toggling between custom keyboard and system keyboard, you can achieve it many ways.
I personally dont prefer notifications they always end up giving me a discrete values, though most of the realtime cases they are good enough when you need to modify the UI as keyboard frame changes in real time (Like try dismissing keyboard on scrolling tableView like whats app where keyboard would go down with your finger as u move and u need to align views in realtime when keyboards scrolls down)
Anyway I believe the approach explained below should also help u to get accurate keyboard height when toggled
Step 1:
Create a subclass of UIView which we will be adding as input accessory view to textView/textField later
protocol KeyBoardObserverProtocol : NSObjectProtocol {
func keyboardFrameChanged(frame : CGRect)
}
class KeyboardObserverAccessoryView: UIView {
weak var keyboardDelegate:KeyBoardObserverProtocol? = nil
private var kvoContext: UInt8 = 1
override func willMove(toSuperview newSuperview: UIView?) {
if newSuperview == nil {
self.superview?.removeObserver(self, forKeyPath: "center")
}
else{
newSuperview?.addObserver(self, forKeyPath: "center", options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.initial], context: &kvoContext)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let theChange = change as [NSKeyValueChangeKey : AnyObject]?{
if theChange[NSKeyValueChangeKey.newKey] != nil{
if self.keyboardDelegate != nil && self.superview?.frame != nil {
self.keyboardDelegate?.keyboardFrameChanged(frame: (self.superview?.frame)!)
}
}
}
}
}
Though code looks big and messy whats inside is pretty easy to understand. All it does is it start obseving the parent view frame using KVO once it receives the call willMove(toSuperview newSuperview: UIView?)
because thats when you know ur view is moved to parent and u know that now u need to monitor the parent's frame.
And every time your KVO triggers the new value u inform it to the interred party using the delegate
How to use it?
keyBoardObserverAccessoryView = KeyboardObserverAccessoryView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
keyBoardObserverAccessoryView.keyboardDelegate = self
self.textView.inputAccessoryView = keyBoardObserverAccessoryView
Thats it :) Now if you wanna provide your custom input accessory view in future just make sure u extend from KeyboardObserverAccessoryView
n enjoy the benefits
Hope it helps :)
Upvotes: 4