I have this code in a UIViewController that changes the height of the view when the keyboard opens.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Subscribe to keyboard events.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
let keyboardHeight = notification.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue.height
// UIView.animateWithDuration(0.5) {
self.view.frame.size.height -= keyboardHeight
self.view.layoutIfNeeded()
// }
}
I notice that the view animates even without UIView.animateWithDuration. Why is this so?
The notification is send out as part of an animation block. If you extract the information from the notification related to the animation, you can have other views animate alongside with it. In your case, all the changes to any view you make between keyboardWillShow and keyboardDidShow are going to be animated as part of that keyboard-animation.
Related
I have an input accessory view called chatBoxView, which contains a custom growingTextView and couple of buttons to send the message. And this inputAccessoryView is setup in my main view controller.
override var inputAccessoryView: UIView? {
get {
return chatBoxView
}
}
override var canBecomeFirstResponder: Bool {
return true
}
So every time I go to this viewController, I see the chatBoxView at the bottom of my screen, which is wanted. However, the keyboard isn't visible yet (only the chatBoxView), but why does my KeyBoardWillShow notification keep getting called?
Also, when the keyboard is up, then disappears when I tap the outside, KeyboardWillDisappear function gets called, but KeyBoardWillShow function gets called right after.
Here are the function for the Keyboard notifications.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(notification: Notification) {
if let keyboardHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
print("Notification: Keyboard will show")
DispatchQueue.main.async {
self.tableView.setBottomInset(to: keyboardHeight - self.chatBoxView.frame.height)
self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .bottom, animated: true)
}
}
}
#objc func keyboardWillHide(notification: Notification) {
print("Notification: Keyboard will hide")
tableView.setBottomInset(to: 0.0)
}
So basically, I get the log "Notification: Keyboard will show", when the keyboard isn't showing and only the accessory view. Any ideas?
The problem is that you are throwing away important information in the UIResponder.keyboardFrameEndUserInfoKey. It is not enough just to look at the keyboard's height. You need to look at where the keyboard will be — look at its entire frame information. If the top of the keyboard is not going to be above the bottom of the screen, clearly you should not be doing anything. Similarly, if the keyboard frame is not going to be changing from where it was, you should not be doing anything.
In simulator, make sure you show software keyboard.
Click here or ⌘ + K
I create multiple UITextFields in my storyboard. Some text field are edited using keyboard while others using picker view. In my view controller, for the fields using pickerView, I created separate picker views for each of the text fields. For eg.
aTextField.inputView = aPickerView
Now for the text fields which are at the bottom of the screen, when I tap on one of them, picker view corresponding to that text field opens at the bottom and it hides the textfield. I want to shift the currently selected text field up when the picker view opens.
In case of keyboard, shifting of views can be done by responding to UIResponder.keyboardWillShowNotification. How to do this in case of picker view?
One solution that I can think of is observing keyboard height. When you press the picker view that has to show up the bottom textField, use keyboard height and some extra points to move it over the keyboard. Here are some helper methods to achieve that:
override func viewDidLoad() {
super.viewDidLoad()
// Add keyboard observers
addObservers()
}
private func addObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
// keyboard will be active
#objc private func keyboardWillShow(notification: NSNotification) {
// Get keyboard Size
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
// update constraints / footerview height with keyboard height "keyboardSize.height"
}
}
// keyboard will dismiss
#objc private func keyboardWillHide(notification: NSNotification) {
if (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue != nil {
// update constraints / footerview height when keyboard is hidden,
}
}
I'm stuck with problem "keyboardWillShow" fires twice, but the "keyboardWillHide" called once.
Here is an example where I'm printing the keyboard sizes as soon as "keyboardWillShow" fires.
I've also put breakpoint in "viewDidLoad" and the observer registers only once.
I've added two elements "UITextField" and "UITextView" and for both it is the same behaviour.
I'm using iOS 9.2, swift lang., xcode 7
Below my ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
print("keyboardWillShow sizes: \(keyboardSize)")
}
}
func keyboardWillHide(notification: NSNotification) {
print("HideHideHide")
}
}
UPDATE
First time it fires once with sizes:
keyboardWillShow sizes: (0.0, 568.0, 320.0, 253.0)
for the rest it twice with different sizes:(second y position is changed also the height changed)
keyboardWillShow sizes: (0.0, 568.0, 320.0, 216.0)
keyboardWillShow sizes: (0.0, 352.0, 320.0, 216.0)
Probably you subscribe to more than one UIKeyboardWillShowNotification and forgot to unsubscribe from them.
Try to add observer in viewWillAppear and remove it in viewWillDisappear.
Issue is connected to simulator
On the real device it fires once as supposed to be.
Are you only entering this ViewController or are you navigating through several ViewControllers? Right now I can't see any code to unsubscribe from the notifications which means than once you enter this ViewController again it will subscribe again (providing its viewDidLoad method runs again). Weird that only one of them fires twice though. Good practice is to subscribe and unsubscribe in the respective opposite methods. If you subscribe in ViewDidLoad then unsubscribe in deinit. If you subscribe in viewWillAppear, unsubscribe in viewWillDisappear etc.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Check so that deinit runs when leaving the ViewController.
I remove all added keyboards and leave only the system's , then the method will fire only one time.If add a new keyboard ,the method still fire two times. Maybe it's a system bug. System Keyboard
Are setting the Text Input Traits - Keyboard Type?
Example: If you set the Keyboard Type as "Number Pad", ideally it should call once, but it gets called twice. Please check that and be sure.
Resolution: You can maintain a bool to check if the keyboard is already up or not, and check its value while executing the selector code block.
I know how to detect a normal keyboard being shown in Swift but I'm wondering if it's possible to detect the events inside a WKWebView because... if the app gets in the background, inputs lose focus but the "blur" event isn't triggered.
The idea is that I have a "navigation bar" in the app which gets pushed out of the view (upwards) when the keyboard is being shown, and I'd like to keep showing it. Knowing the keyboard is about 216px tall I'd just like to narrow the height of the content wrapper which is flex based by 216px but that's not really working since it is an animation which I can't really reproduce so it's smooth. I also can't detect when user went into emoji tab. I got rid of the autocorrect since I just set it to off for the input tag.
Try this code.
override viewWillAppear(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide"), name:UIKeyboardWillHideNotification, object: nil);
}
func keyboardWillHide(){
navigationController?.setNavigationBarHidden(false, animated: true)
}
func keyboardWillShow(notification: NSNotification) {
navigationController?.setNavigationBarHidden(true, animated: true)
var info = notification.userInfo!
var keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
// handle your layout according to frame
}
I am having a weird problem with moving my views to make room for an incoming keyboard. Basically, in my app, there is a button that performs a segue which pushes a new instance of a view controller that is embedded inside of a navigation controller modally. Within this first instance, my keyboard code works perfectly. The code is as follows:
func keyboardWillShow(sender: NSNotification) {
if !keyBoard {
self.view.frame.origin.y -= 200
}
keyBoard = true
}
func keyboardWillHide(sender: NSNotification) {
if keyBoard {
self.view.frame.origin.y += 200
}
keyBoard = false
}
with the following in viewDidLoad:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
I also added a tapGestureRecognizer so that when the keyboard is showing and the user taps anywhere on the screen, the keyboard closes:
let tapped = UITapGestureRecognizer(target: self, action: "closeKeyboard")
tapped.numberOfTapsRequired = 1
self.view.addGestureRecognizer(tapped)
and
func closeKeyboard() {
self.view.endEditing(true)
}
So in the first instance, this code works perfectly. However, after I go back, calling self.dismissViewControllerAnimated, and, once I am on the original screen, click the button that calls performSegue again and push a new instance of this same view controller, the code breaks and the view no longer moves out of the way of the keyboard but just sort of stutters and bounces a little. I have no idea why this is happening and any help would be very much appreciated. Thanks!
Try putting a breakpoint on keyboardWillShow and keyboardWillHide, and tell me if both the methods are called when you go back to the second ViewController.