Swift 4.2 Make bottom of tableView move up when keyboard shows - ios

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
}
}

Related

View is getting shifted every time I re-tap textfield

so I have a small app where I choose an image, get it into an image view
and I have 2 textfield to insert a funny phrase
(Meme editor app)
my problem is since the bottom textfield is covered when the keyboard is shown I had to shift the view upwards every time the keyboard is shown for the bottom texfield and I succeed in doing that, what goes wrong is that every time I re-tap the beginning or the end of an existing text in the text filed the view shifts up again in undesirable behavior
here is a small GIF that shows what happens exactly
here is my code so far:
Function to get Kyboard height:
func getKeyboardHeight(_ notification:Notification) -> CGFloat {
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! NSValue // of CGRect
return keyboardSize.cgRectValue.height
}
Function to shift the view up in the condition that bottom textfield is what the user taps
#objc func keyboardWillShow(_ notification:Notification) {
if bottomTextField.isFirstResponder{
view.frame.origin.y -= getKeyboardHeight(notification)
}
}
Function to return the view to its normal position when the user finishes editing the bottom text field
#objc func keyboardWillHide(_ notification:Notification) {
view.frame.origin.y = 0
}
Functions to add and remove observers of keyboard notifications
func subscribeToKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
}
func subscribekeyboardWillHide() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
func unsubscribeFromKeyboardNotifications() {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
}
func unsubscribekeyboardWillHide() {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
and where I call them
override func viewWillAppear(_ animated: Bool) {
super .viewWillAppear(true)
cameraButton.isEnabled = UIImagePickerController.isSourceTypeAvailable(.camera)
subscribeToKeyboardNotifications()
subscribekeyboardWillHide()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
unsubscribeFromKeyboardNotifications()
unsubscribekeyboardWillHide()
}
if you could be kind to provide a simple explanation for your solution I would appreciate it
This is what I do while managing keyboard. It causes no problems.
Declare in your UIViewController class
private let notificationCenter = NotificationCenter.default
then in
override func viewDidLoad() {
super.viewDidLoad()
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
This is the adjust for keyboard function
#objc private func adjustForKeyboard(notification: Notification) {
guard let userInfo = notification.userInfo else { return }
guard let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return }
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
scrollView.contentInset = UIEdgeInsets.zero
} else {
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
scrollView.scrollIndicatorInsets = scrollView.contentInset
}
Deinit here -
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
notificationCenter.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
Every time you change the cursor location, UIResponder.keyboardWillShowNotification notification triggered, thats why every time you tap the textfield it moves up one keyboard height more.
You can use UIResponder.keyboardDidShowNotification notification instead of UIResponder.keyboardWillShowNotification. This one is not triggered when cursor location changes.

how to get the height of the keyboard with suggestion bar in Swift

In the app I am building I have a Textfield with a button at the bottom of my screen, and I want to animate them up when the keyboard appears.
For that I use this:
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: Notification) {
let keyboardSize = (notification.userInfo? [UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let keyboardHeight = keyboardSize?.height
if #available(iOS 12.0, *) {
self.submitConstraint.constant = (keyboardHeight! - view.safeAreaInsets.bottom) + 10
}
else {
self.submitConstraint.constant = view.safeAreaInsets.bottom + 10
}
UIView.animate(withDuration: 0.5){
self.view.layoutIfNeeded()
}
}
#objc func keyboardWillHide(notification: Notification){
self.submitConstraint.constant = 34 // or change according to your logic
UIView.animate(withDuration: 0.5){
self.view.layoutIfNeeded()
}
}
but, then a part of the button gets hidden under the suggestions toolbar.
I have googled this, and I found this answer: How to get the height of the keyboard including suggestions bar in swift. I tried the answers, and it didn't work for me, a part of the button still is hidden under the suggestions bar.
Could anybody help me?
Thanks!

Getting keyboard height on custom keyboard is 0

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

Keep a view always on top (Don't scroll with keyboard) in IQKeyboardManager

I'm using IQKeyboardManager to keep the text fields to go up after typing with the keyboard.
I don't want to scroll to a specific view even when clicked on the text field. Below is the screenshot of the design. I want the 'header' to remain on top.
From their documentation, there is a way to keep the navigation bar remain on top.
Disable the IQKeyboardManager for your ViewController.
for that,
IQKeyboardManager.sharedManager().disableInViewControllerClass(ViewController.self)
And In that viewController write the following code. It will move your view up as per keyboard height
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 {
self.view.frame.origin.y -= keyboardSize.height
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y += keyboardSize.height
}
}
}
Now you want your "HEADER" view remain on TOP then,
Do like this :
**
YourViewController.view -> [headerView][contentView]
**
Put textfield in [contentView] And change [contentView].y instead of Self.view in above code.
Disable the IQKeyboardManager for your viewController:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
IQKeyboardManager.sharedManager().enable = false
NotificationCenter.default.addObserver(self, selector: #selector(Login.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(Login.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
Handle keyboard:
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0{
self.table_view.frame.origin.y -= keyboardSize.height
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0{
self.table_view.frame.origin.y += keyboardSize.height
}
}
}
Remove observer:
override func viewWillDisappear(animated: Bool) {
IQKeyboardManager.sharedManager().enable = true
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Answers from #Wolverine and #Bhavin Ramani were great: the best way to keep your custom header staying at top is to manually handle your keyboard (according to IQKeyboardSwift author's comment). If you use iOS default navigation bar, it seems it's handled for you by the library.
Here I just want to share some updates on this topic, for my future reference, as the answers are a bit old and some Swift syntax has changed. Code below is written in Xcode 13.2, targeting iOS 13+.
Firstly, you want to disable KQKeyboardManager by doing
IQKeyboardManager.shared.enable = false
Please note this lines only disables moving text field up feature, other IQKeyboard features such as resign on touch outside, auto tool bar, and etc., are not disabled by this line, this is usually what you want.
Then, you register keyboard events observer in view controller's viewDidLoad, remove observers in deinit.
override func viewDidLoad() {
super.viewDidLoad()
IQKeyboardManager.shared.enable = false
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
deinit {
IQKeyboardManager.shared.enable = true
NotificationCenter.default.removeObserver(self)
}
Next, add view moving up/down methods for keyboard show/hide.
#objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
print("keyboardSize.height", keyboardSize.height)
// using the right key here is important, because
// keyboardFrameEndUserInfoKey is an user info key
// to retrieve the keyboard’s frame at the END of its animation.
// here you move up the views you need to move up
// if you use auto layout, update the corresponding constraints
// or you update the views' frame.origin.y
// you may want to do the updates within a 0.25s animation
}
}
#objc private func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? CGRect {
// reset views to their original position on keyboard dismiss
}
}
You may also want to enable/disable auto tool bar, as it could make your keyboard height unstable.
// in viewDidLoad set to false, in deinit set back to true (if you need it)
IQKeyboardManager.shared.enableAutoToolbar = false

Move up textfield above the keyboard

I tryed to use something to move up the view because when I go at the end of my form and I tap on the last textfield I can't see what I write in because the keyboard appears over the text field, so I found something but here if I tap on a textfield on the top of the view this one gonna gonna be hide by going upper, that's the code:
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y -= keyboardSize.height
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y += keyboardSize.height
}
}
But here there is an another problem, when I tap two times one an another textfield, a black bloc appears.
So what I wan't to do is detect the textfield and put the keyboard under it so move up the textfield. And detect if the keyboard is already here do don't show a black bloc. But I haven't found solution about.
I have used this code in my project :
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
// Register Keyboard Notification
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
// Remove Keyboard notification observer
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardWillShow(notification: NSNotification)
{
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
keyboardFrame = self.view.convertRect(keyboardFrame, fromView: nil)
var contentInset:UIEdgeInsets = self.scrollView.contentInset
contentInset.bottom = keyboardFrame.size.height - 30 //Set this value (30) according to your code as i have navigation tool bar for next and prev.
self.scrollView.contentInset = contentInset
}
func keyboardWillHide(notification: NSNotification) {
let contentInset:UIEdgeInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInset
}
Hope It will help you!!
Add a bool property self.isKeyBoardUp
In keyboardWillShow:
If isKeyBoardUp return, else set it to true and do your stuff.
In keyboardWillHide:
If not isKeyBoardUp return, else set it to false and do your stuff.
Please check this code i am try it and works for get this one from
Move textfield when keyboard appears swift
func keyboardWasShown(notification: NSNotification) {
var info = notification.userInfo!
var keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.bottomConstraint.constant = keyboardFrame.size.height + 20
})
}

Resources