I am adding a keyboard observer to adjust the view height when the keyboard shows. But when the text field is at the top of the view and the keyboard shows, the text field goes up even more. Can someone please guide me on how to fix this properly because after performing lots of searches, they all suggest the same thing. I am using iOS 9 on an iPhone 5.
Here is my code. In viewDidLoad():
//SET OBSERVER WHEN KEYBOARD IS SHOWN
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: self.view.window)
//SET OBSERVER WHEN KEYBOARD IS HIDDEN
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: self.view.window)
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: self.view.window)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: self.view.window)
}
//HANDLING KEYBOARD APPEARING AND DISAPPEARING
func keyboardWillHide(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
self.view.frame.origin.y += keyboardSize.height
}
func keyboardWillShow(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
let offset: CGSize = userInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue.size
print(("\(keyboardSize.height)---\(offset.height)")) **//this is always showing the values 216.0 for both**
if keyboardSize.height == offset.height {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y -= keyboardSize.height
})
} else {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y += keyboardSize.height - offset.height
})
}
}
You're taking a complicated approach to what isn't a very difficult problem. Please remember that phones come in a variety of sizes, so trying to move a view around might work great for one device (e.g. iPhone 5) but be a poor experience on others (e.g. iPhone 4, iPhone 6s).
The solution you're looking for is to place your controls inside a UIScrollView. You then don't move anything when the keyboard shows or hides – you just adjust the content inset of your scroll view, and let iOS do the rest.
The keyboard being shown and hidden is complicated by the fact that there are also hardware keyboards that can be connected and unplugged at will; in my (extremely extensive!) testing, I've managed to pull together code that seems to behave properly in all conditions, no matter what happens with the keyboard. I've tried to trim it down smaller and have always found an edge case where it stopped working, so I've settled with this fixed approach and it works great. If you're able to use a UIScrollView, this exact approach will work for you too.
Step 1: Register for the right notifications. Specifically, put this into viewDidLoad():
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "adjustForKeyboard:", name: UIKeyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: "adjustForKeyboard:", name: UIKeyboardWillChangeFrameNotification, object: nil)
Step 2: Add this method to your view controller:
func adjustForKeyboard(notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
let keyboardViewEndFrame = view.convertRect(keyboardScreenEndFrame, fromView: view.window)
if notification.name == UIKeyboardWillHideNotification {
yourScrollView.contentInset = UIEdgeInsetsZero
} else {
yourScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
yourScrollView.scrollIndicatorInsets = yourScrollView.contentInset
}
Step 3: Anyone using a UITextView should also add these two lines to the end of that method so the user doesn't lose their place:
let selectedRange = yourScrollView.selectedRange
yourScrollView.scrollRangeToVisible(selectedRange)
…although presumably in that case it would be yourTextView or similar.
Related
I have bottomView (the one with black border in the images) its bottom anchor has a constraint to bottom view safeAreaLayoutGuide, but when keyboard will show the bottomView became toolbar to the keyboard.
Here every thing was working as expected in iOS 14 (like image 1 and 3) but when running the project in iOS 13 I found that safeAreaInsets changes its value after the keyboard dismissed (red space increases its height like image 2)
I've tried some of the solutions that mentioned here but it didn't help
can anyone suggest a solution for this?
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
bottomViewConstraint?.constant = 0
NotesText.contentInset = .zero
} else {
bottomViewConstraint?.constant = -(keyboardViewEndFrame.height - self.view.safeAreaInsets.bottom)
NotesText.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: -(keyboardViewEndFrame.height + bottomViewConstraint!.constant), right: 0)
//FIXME:- force unwrap
}
NotesText.scrollIndicatorInsets = NotesText.contentInset
let selectedRange = NotesText.selectedRange
NotesText.scrollRangeToVisible(selectedRange)
}
and called this function in viewDidAppear also tried to called it at viewSafeAreaInsetsDidChange but the behavior didn't change
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addKeyboardNotification()
// Show keyboard by default
NotesText.becomeFirstResponder()
}
Keyboard notification function:
fileprivate func addKeyboardNotification() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
In my application I need to get keyboard size for after move the others components to correctly position, now i'm using that code to get height
let info:NSDictionary = aNotification.userInfo! as NSDictionary
let kbSize:CGSize = (info.object(forKey: UIResponder.keyboardFrameEndUserInfoKey)! as AnyObject).cgRectValue.size
print(kbSize.height)
In the first time when keyboard is open the result is 260.0, but this value is size default, without the suggestions/passwords bar height, when I click out and click in the input again the result is 304.0 the value I want...
How I can get 304 since the first click in the input?
You should be listening for changes in the keyboard's size and adjusting the rest of your content that way, since, as you found out, iOS keyboards can change size. You should subscribe to UIResponder.keyboardWillChangeFrameNotification and/or UIResponder.keyboardDidChangeFrameNotification which are notifications which trigger when the keyboard's frame will, or did, change. Here's an example of it in use:
// Somewhere in set up code
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidChangeFrame),
name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
// Function elsewhere in your class
#objc func keyboardDidChangeFrame(_ notification: Notification) {
guard let kbSize = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey]
as? CGRect else {
return;
}
// Use kbRect as you initially did
}
Note that you can use either the Will or the Did notifications depending on how you want the layout change to look. You can also query both UIResponder.keyboardFrameBeginUserInfoKey UIResponder.keyboardFrameEndUserInfoKey to get the keyboard frame before and after the size change, which may be useful if you want to animate your layout along with the keyboard.
You can try this;
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(UIResponder.keyboardWillShowNotification)
NotificationCenter.default.removeObserver(UIResponder.keyboardWillHideNotification)
NotificationCenter.default.removeObserver(UIResponder.keyboardWillChangeFrameNotification)
}
#objc func keyboardWillChange(notification: Notification) {
guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return }
if notification.name == UIResponder.keyboardWillChangeFrameNotification || notification.name == UIResponder.keyboardWillShowNotification {
view.frame.origin.y = -keyboardSize.height
} else {
view.frame.origin.y = 0
}
}
}
HERE is the Github gist
Although I've searched, I'm confused about how best to approach this.
I have a tableView where the bottom cell is an input to the list, in the same way apple reminders work. When there are too many items on the list, the keyboard covers the list and I can't see what I'm typing.
My thought it I need to change the physical size of the table view and ensure it is scrolled to the bottom when the keyboard shows.
Some have said keyboard observers but the majority of code I've found for this is out of date and errors when put into Xcode.
NSNotificationCenter Swift 3.0 on keyboard show and hide
This is the best I can find but I'm also hearing about contraints, using a UITableViewController instead of embedding a UITableView and so on ...
This is the code I have so far:
NotificationCenter.default.addObserver(self, selector: #selector(EntryViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(EntryViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
#objc func keyboardWillShow(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
print("notification: Keyboard will show")
if self.view.frame.origin.y == 0{
self.view.frame.origin.y -= keyboardSize.height
}
}
}
#objc func keyboardWillHide(notification: Notification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y += keyboardSize.height
}
}
}
This moves the whole view up I think, which means safeareas (such as navigation bar and so on) have the TableView underneath them. Do I make the navigationview non-transparent in this approach?
One solution (which I sometimes use) is simply change the content offset of the tableView when the keyboard appears/disappears. I believe this would work in your instance as opposed to varying the tableView's constraints as you mentioned your UIViewController is a UITableViewController. Please see the below code for my suggestion, hope this helps!
Handle Notifications:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(EntryViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(EntryViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
Actions:
#objc func keyboardWillShow(notification: Notification) {
if let keyboardHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
print("Notification: Keyboard will show")
tableView.setBottomInset(to: keyboardHeight)
}
}
#objc func keyboardWillHide(notification: Notification) {
print("Notification: Keyboard will hide")
tableView.setBottomInset(to: 0.0)
}
Extensions:
extension UITableView {
func setBottomInset(to value: CGFloat) {
let edgeInset = UIEdgeInsets(top: 0, left: 0, bottom: value, right: 0)
self.contentInset = edgeInset
self.scrollIndicatorInsets = edgeInset
}
}
Like so many other threads here I need to move a view up so its visible while they keyboard is visible and the correct answer seems to be this in most cases:
var isKeyboardVisible = false
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
if !isKeyboardVisible {
if let keyboardRectValue = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardRectValue.height
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardHeight
}
}
isKeyboardVisible = true
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if isKeyboardVisible {
if let keyboardRectValue = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardRectValue.height
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y += keyboardHeight
}
}
isKeyboardVisible = false
}
}
This however doesn't work if the user uses a custom keyboard.
keyboardHeight is 0 if a custom keyboard is used. But if the stock keyboard is used the height seems correct.
Can be reproduced if you install Google GBoard and try to get the height of the keyboard.
I'm quite certain I'm not the only one using a custom keyboard, so I guess there should be some kind of solution for this problem which is dynamic and doesn't include a long if/else list.
Addition 1: I found this answer which suggests to add an inputAccessoryView to the textfield. This makes perfect sense to me as this view will always be exactly above the keyboard. There is how ever two problems with this.
One, content is still being hidden behind the keyboard.
Two, if the textfield which is currently focused is behind the keyboard, should I add a duplicate of it to the ´ inputAccessoryView´ so whatever is written is shown in both? This seems like a lot of extra work.
What does apps like whatsapp do to solve this? There must be a universal way to adapt the content to fit above any keyboard (stock or custom) and also not disappear above the top part of the screen!?!?
So this is the cleanest way I've found.
Swift 4.2
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShowHide), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShowHide), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShowHide), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
#objc func keyboardShowHide(notification: NSNotification) {
if let keyboardRectValue = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.view.frame.size.height = UIScreen.main.bounds.height - keyboardRectValue.height
}
}
Many solutions for getting the the necessary view above the keyboard is to move all the content up and beyond the screen limit. I consider this bad practice there the will be content totally inaccessible to the user outside of the screen as long as the keyboard is visible.
As in my solution above I prefer to adjust the size of the content instead of moving it. So if the content is in a tableview or a scrollview everything will still be available while the keyboard is visible.
If you however prefer that approach just change this line:
self.view.frame.size.height = UIScreen.main.bounds.height - keyboardRectValue.height
for this:
self.view.frame.origin.y = -keyboardRectValue.height
I have a view which is resize a constraint when the keyboard appear. So I have notifications when the keyboard appears and disappears.
The above behaviour occurs when the keyboard is already shown and I rotate the screen. Then the next actions occurs:
UIKeyboardWillHideNotification called
UIKeyboardWillShowNotification called (with the old height of the keyboard)
UIKeyboardWillShowNotification called (with the new height of the keyboard)
So, the updateView function receives first one height and later a different height. This results in a weird movements of the view adjusting twice the value.
override func viewDidLoad() {
super.viewDidLoad()
// Creates notification when keyboard appears and disappears
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, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
self.adjustingHeight(true, notification: notification)
}
func keyboardWillHide(notification: NSNotification) {
self.adjustingHeight(false, notification: notification)
}
private func adjustingHeight(show: Bool, notification: NSNotification) {
// Gets notification information in an dictionary
var userInfo = notification.userInfo!
// From information dictionary gets keyboard’s size
let keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
// Gets the time required for keyboard pop up animation
let animationDurarion = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSTimeInterval
// Animation moving constraint at same speed of moving keyboard & change bottom constraint accordingly.
if show {
self.bottomConstraint.constant = (CGRectGetHeight(keyboardFrame) + self.bottomConstraintConstantDefault / 2)
} else {
self.bottomConstraint.constant = self.bottomConstraintConstantDefault
}
UIView.animateWithDuration(animationDurarion) {
self.view.layoutIfNeeded()
}
self.hideLogoIfSmall()
}
Finally I found the solution. When I get keyboardFrame, I was using UIKeyboardFrameBeginUserInfoKey which returns the frame of the keyboard before the animation begins. The correct way of do that is with UIKeyboardFrameEndUserInfoKeywhich returns the frame of the keyboard after the animation has completed.
let keyboardFrame: CGRect = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()