Keyboard covers text fields at the bottom of my view - ios

I have searched
here: Move a view up only when the keyboard covers an input field
here: Move textfield when keyboard appears swift
here: How to make a UITextField move up when keyboard is present?
and here: https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
Unfortunately, all of the links and seemingly everywhere else I can find do not give a good clean solution that I am looking for. They are either outdated with Obj-c code, or plain do not work in the current iteration of Xcode 9 with Swift 4.
How do I manage a keyboard that is covering text fields at the bottom of my view? I want the screen's view to move only when they keyboard is covering the text field view, without using a scroll view, with the ability to animate this to make it look pretty rather than have it just snap, and most importantly I do not want to use an outside library.
CoreAnimation libraries from Apple are great and all, but all of the sample code is outdated and in objective c which is deprecated at this point (I cannot believe that Apple isn't updating their documentation).
If someone could point me in the right direction to updated and current code or a library from Apple I am missing that will specifically address this issue, it would be much appreciated.

You can use IQKeyboardManagerSwift to solve your issue easily and fast.
Use below pod in your pod file Which give support to Swift 4.
pod 'IQKeyboardManagerSwift', '5.0.0'
Here is link to implement IQKeyboardManagerSwift.
https://github.com/hackiftekhar/IQKeyboardManager
Thanks!!!

This code will work, making your textField animating to above keyboard if its frame intersects with that of keyboard and animating back to original position on keyboard hide.
#IBOutlet weak var textField: UITextField!
var offsetY:CGFloat = 0
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardFrameChangeNotification(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
func keyboardFrameChangeNotification(notification: Notification) {
if let userInfo = notification.userInfo {
let endFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0
let animationCurveRawValue = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIViewAnimationOptions.curveEaseInOut.rawValue)
let animationCurve = UIViewAnimationOptions(rawValue: UInt(animationCurveRawValue))
if let _ = endFrame, endFrame!.intersects(self.textField.frame) {
self.offsetY = self.textField.frame.maxY - endFrame!.minY
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.textField.frame.origin.y = self.textField.frame.origin.y - self.offsetY
}, completion: nil)
} else {
if self.offsetY != 0 {
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.textField.frame.origin.y = self.textField.frame.origin.y + self.offsetY
self.offsetY = 0
}, completion: nil)
}
}
}
}

This piece of code worked for me.
In case of multiple textfields
I have implemented only for the textfields which are at the bottom (without using notification observer).
If you are using scrollView, this code might be helpful
(scrollViewDidScroll is optional)
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.contentSize = CGSize(width: self.scrollView.frame.size.width, height: (scrollView.frame.size.height + 300))// To be more specific, I have used multiple textfields so wanted to scroll to the end.So have given the constant 300.
}
func textFieldDidBeginEditing(_ textField:UITextField) {
self.scrollView.setContentOffset(textField.frame.origin, animated: true)
}
and if you want to set the textfields position according to the view,
try this:
func textFieldDidBeginEditing(_ textField:UITextField){
textField.frame.origin.y = textField.frame.origin.y - 150 //(If have not used contentsizing the scroll view then exclude this line)default origin takes the texfield to the top of the view.So to set lower textfields to proper position have used the constant 150.
self.scrollView.setContentOffset(textField.frame.origin, animated: true)
}
To do specifically for textfields at the bottom. Check their tag value textfield.tag in textFieldDidBeginEditing
func textFieldDidBeginEditing(_ textField:UITextField){
if textField.tag = 4 { //tag value of the textfield which are at the bottom
self.scrollView.setContentOffset(textField.frame.origin, animated: true)
}
}
If you implemented textfields in tableView go with notification observer which is explained below.
If there are multiple textfields in a tableView preferably go with Notification Observer
override func viewDidAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
}
}
#objc func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.2, animations: {
// For some reason adding inset in keyboardWillShow is animated by itself but removing is not, that's why we have to use animateWithDuration here
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
})
}
deinit {
print("denit")
NotificationCenter.default.removeObserver(self)
}

worked perfectly for me:
http://www.seemuapps.com/move-uitextfield-when-keyboard-is-presented
If delegates are set right,
func textFieldDidBeginEditing(_ textField: UITextField) {
moveTextField(textField, moveDistance: -250, up: true)
}
// Finish Editing The Text Field
func textFieldDidEndEditing(_ textField: UITextField) {
moveTextField(textField, moveDistance: -250, up: false)
}
// Hide the keyboard when the return key pressed
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
// Move the text field in a pretty animation!
func moveTextField(_ textField: UITextField, moveDistance: Int, up: Bool) {
let moveDuration = 0.3
let movement: CGFloat = CGFloat(up ? moveDistance : -moveDistance)
UIView.beginAnimations("animateTextField", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(moveDuration)
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}

Add an extensio to Uiview:
import UIKit
//Binding view to keyboard changes
extension UIView {
func bindToKeyboard(){
NotificationCenter.default.addObserver(self, selector: #selector(UIView.keyboardWillChange(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardWillChange(_ notification: NSNotification) {
let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let deltaY = targetFrame.origin.y - curFrame.origin.y
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
self.frame.origin.y += deltaY
},completion: {(true) in
self.layoutIfNeeded()
})
}
}

Related

Sliding a tableview when keyboard shows up

I am trying to slide a textbox, which is located in the bottom of a tableview (actually, in its footer view). So I tried two methods:
To animate autolayout constraint
Animate content insets of a tableview
Still, non of them worked, or partially worked. Currently, I am not able to test this on a real iPad (app is iPad only) and I am testing on Simulator, but I thought something like this should work:
Animating contentInsets
func keyboardWillShow(notification: NSNotification) {
if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
UIView.animate(withDuration: 0.2, animations: {
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
})
}
}
func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.2, animations: {
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
})
}
Animating autolayout constraint
func keyboardWillShow(notification: NSNotification) {
if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
UIView.animate(withDuration: 0.2, animations: {
self.bottomTableviewConstraint.constant = keyboardHeight
self.qaTableview.layoutIfNeeded()
})
}
}
func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.2, animations: {
self.bottomTableviewConstraint.constant = 0
self.qaTableview.layoutIfNeeded()
})
}
So in first example, I never noticed animation, nor tableview content moving. In the second example I made an outlet of a autolayout constraint (bottom vertical spacing) but I get some odd results. eg, tableview moves up, but not completely, and only for the first time.
If I put a breakpoint, keyboardHeight is equal to 471. I am I missing something obvious ?
This is what I tried and it worked perfectly fine as expected.
Can you check where you added your Notification center?
-- Animating contentInsets
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
UIView.animate(withDuration: 0.2, animations: {
self.tbl.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
})
}
}
func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.2, animations: {
self.tbl.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
})
}
-- Animating autolayout constraint
Make sure you do not have both Bottom layout and height constraints set for UITableView in Storyboard.
NOTE
But there's an even more easy way to do this, just by using a UITableViewController. It will take care of this feature by default. This is, of course, if you have no problem in using it.
EDIT
UITextView will act a little different from UITextField. Set delegate for UITextView. And do all table view alignments in textViewShouldBeginEditing.
extension ViewController: UITextViewDelegate {
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
let pointInTable:CGPoint = textView.superview!.convert(textView.frame.origin, to: tbl)
tbl.contentOffset = CGPoint(x: tbl.contentOffset.x, y: pointInTable.y)
return true
}
}
I use IQKeyboardManager library, which handles this behavior: https://github.com/hackiftekhar/IQKeyboardManager
IQKeyboardManager allows you to prevent issues of the keyboard sliding up and cover UITextField/UITextView without needing you to enter any code and no additional setup required. To use IQKeyboardManager you simply need to add source files to your project.
It's really easy to implement. Import the library with pods, and just add, in your AppDelegate:
import IQKeyboardManager
And in didFinishLaunchingWithOptions:
IQKeyboardManager.sharedManager().enable = true

How can I move the view up for last text field when keyboard appears?

I have a view inside that I have 6 text fields but I want to move up the view when click on last text field that I can Enter value in last text field.
Here is my code but it is working for all text fields :-
func animateViewMoving (up:Bool, moveValue :CGFloat){
let movementDuration:TimeInterval = 0.3
let movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations("animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
UIView.commitAnimations()
}
So for particular textField how can I move the view up (Keyboard size)
Thanks
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var contentView: UIView!
var textFieldActive:UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//Keyboard notification
NotificationCenter.default.addObserver(self,selector: #selector(keyboardWasShown),name:NSNotification.Name.UIKeyboardDidShow,
object: nil)
NotificationCenter.default.addObserver(self,selector: #selector(keyboardWillHide),name:NSNotification.Name.UIKeyboardWillHide,
object: nil)
// Do any additional setup after loading the view.
}
func textFieldDidBeginEditing(_ textField: UITextField){
textFieldActive = textField
}
// MARK: - Keyboard notification Method
func keyboardWasShown(notification: NSNotification)
{
let userInfo = notification.userInfo
let keyboardSize = (userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
let contentInset = UIEdgeInsetsMake(0.0, 0.0, (keyboardSize?.height)!+100, 0.0)
self.scrollViewGroupMakeTakers.contentInset = contentInset
self.scrollViewGroupMakeTakers.scrollIndicatorInsets = contentInset
// Step 3: Scroll the target text field into view.
var aRect: CGRect = self.contentViewMakeTakers.frame;
aRect.size.height -= (keyboardSize?.height)!+100
if (!aRect.contains(textFieldActive!.frame.origin))
{
let scrollPoint: CGPoint = CGPoint(x: 0.0, y: textFieldActive!.frame.origin.y - (keyboardSize!.height-150));
self.scrollViewGroupMakeTakers.setContentOffset(scrollPoint, animated: true)
}
}
func keyboardWillHide(notification: NSNotification)
{
let contentInsets : UIEdgeInsets = UIEdgeInsets.zero
scrollViewGroupMakeTakers.contentInset = contentInsets;
scrollViewGroupMakeTakers.scrollIndicatorInsets = contentInsets;
}
Try this-
func textFieldShouldReturn(textField: UITextField) -> Bool {
if textField == lastTextField{
view.frame.origin.y -= 50 //Or 100, 200 as you required
performAction()
}
}
func performAction() {
lastTextField.becomeFirstResponder()
otherTextField.resignFirstResponder()
}
Assuming you are using autoLayout
I am imagining all your UITextField to be present inside a separate UIScrollView. Further this UISCrollView will have a bottom constraint to its superView. Create an IBOutlet for this bottom spacing vertical constraint to its super view.
Note: You can do this even if its not inside a UIScrollView by simply taking the bottom constraint of your lowest UITextField but that will force your other UITextFields off screen so best put it inside a UIScrollView.
In your viewDidLoad setup observers as follows -
func viewDidLoad(){
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
//and then:
//MARK: Animate Keyboard
func keyboardWillShow(notification : NSNotification){
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
animateKeyboard(withConstraintValue: keyboardSize.size.height)
}
}
func keyboardWillHide(notification : NSNotification){
animateKeyboard(withConstraintValue: 0)
}
func animateKeyboard(withConstraintValue: CGFloat){
scrollViewBottomSpaceConstraint.constant = withConstraintValue
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
}

Swift move scroll view up when keyboard present

I'm working on an iOS app and currently all my elements are in a scroll view and when the keyboard is present I move the view up 250 pts. This solved my problem but the keyboard is always a different size per device.
How could I detect how far from the bottom of the screen my text field is and how tall the keyboard is?
You should observe the notification for showing and hiding the keyboard. And after that you can get the exact keyboard size and either shift or change the content insets of your scroll view. Here's a sample code:
extension UIViewController {
func registerForKeyboardDidShowNotification(scrollView: UIScrollView, usingBlock block: (NSNotification -> Void)? = nil) {
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidShowNotification, object: nil, queue: nil, usingBlock: { (notification) -> Void in
let userInfo = notification.userInfo!
let keyboardSize = userInfo[UIKeyboardFrameBeginUserInfoKey]?.CGRectValue.size
let contentInsets = UIEdgeInsetsMake(scrollView.contentInset.top, scrollView.contentInset.left, keyboardSize!.height, scrollView.contentInset.right)
scrollView.scrollEnabled = true
scrollView.setContentInsetAndScrollIndicatorInsets(contentInsets)
block?(notification)
})
}
func registerForKeyboardWillHideNotification(scrollView: UIScrollView, usingBlock block: (NSNotification -> Void)? = nil) {
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillHideNotification, object: nil, queue: nil, usingBlock: { (notification) -> Void in
let contentInsets = UIEdgeInsetsMake(scrollView.contentInset.top, scrollView.contentInset.left, 0, scrollView.contentInset.right)
scrollView.setContentInsetAndScrollIndicatorInsets(contentInsets)
scrollView.scrollEnabled = false
block?(notification)
})
}
}
extension UIScrollView {
func setContentInsetAndScrollIndicatorInsets(edgeInsets: UIEdgeInsets) {
self.contentInset = edgeInsets
self.scrollIndicatorInsets = edgeInsets
}
}
And in your UIViewController in which you want to shift the scrollview, just add next lines under the viewDidLoad() function
override func viewDidLoad() {
super.viewDidLoad()
registerForKeyboardDidShowNotification(scrollView)
registerForKeyboardWillHideNotification(scrollView)
}
I'm currently work on this and found a solution. First you need to add a notification to the view controller to identify whether the keyboard is on or not. For that you need to register this notification in viewDidLoad().
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardWillChangeFrameNotification, object: nil)
}
And also don't forget to remove this notification, when the view controller removed from the view. So remove this notification on viewDidDisappear().
override func viewDidDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillChangeFrameNotification, object: nil)
}
And the final thing is to manage the scroll view when the keyboard is on or off. So first you need to identify the keyboard height.Then for pretty smooth animation, you can use keyboard animation mood and duration time to animate your scroll view.
func keyboardDidShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
let duration:NSTimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.unsignedLongValue ?? UIViewAnimationOptions.CurveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
if endFrame?.origin.y >= UIScreen.mainScreen().bounds.size.height {
//isKeyboardActive = false
UIView.animateWithDuration(duration,
delay: NSTimeInterval(0),
options: animationCurve,
animations: {
// move scroll view height to 0.0
},
completion: { _ in
})
} else {
//isKeyboardActive = true
UIView.animateWithDuration(duration,
delay: NSTimeInterval(0),
options: animationCurve,
animations: {
// move scroll view height to endFrame?.size.height ?? 0.0
},
completion: { _ in
})
}
}
}
#noir_eagle answer seems right.
But there may be is a simpler solution. Maybe you could try using IQKeyboardManager. It allows you to handle these keyboard things in a simple and seamless way.
I think you really should, at least, spend few minutes looking at it.

UIKeyboardWillShowNotification is called three times

I need to move a UIView up as soon as the keyboard will become visible. But the problem I'm facing right now is that my UIKeyboardWillShowNotification is called three times when I'm using a custom Keyboard (e.g. SwiftKey) which results in a bad animation.
Is there a way to handle only the last notification? I could easily dodge the first one because the height is 0, but the second one looks like a valid height and I don't find an answer on how to solve this.
Here is what I've so far:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillAppear:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillDisappear:", name: UIKeyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardWillAppear(notification: NSNotification){
print("keyboard appear")
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
print("with height: \(keyboardSize.height)")
if keyboardSize.height == 0.0 {
return
}
self.txtViewBottomSpace.constant = keyboardSize.height
UIView.animateWithDuration(0.4, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}
func keyboardWillDisappear(notification: NSNotification){
print("Keyboard disappear")
self.txtViewBottomSpace.constant = 0.0
UIView.animateWithDuration(0.4, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
My Log output is:
keyboard appear
with height: 0.0
keyboard appear
with height: 216.0
keyboard appear
with height: 258.0
Keyboard disappear
So is there any way to only handle the third notification and "ignore" the first two?
Set all bellow fields to NO can resolve this problem.
Capitalizaion: None
Correction: No
Smart Dashes: No
Smart insert: No
Smart Quote: No
Spell Checking: No
Change the notification name UIKeyboardDidShowNotification and UIKeyboardDidHideNotification then solve the problem
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillAppear:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillDisappear:", name: UIKeyboardDidHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardWillAppear(notification: NSNotification){
print("keyboard appear")
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
print("with height: \(keyboardSize.height)")
if keyboardSize.height == 0.0 {
return
}
self.txtViewBottomSpace.constant = keyboardSize.height
UIView.animateWithDuration(0.4, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}
func keyboardWillDisappear(notification: NSNotification){
print("Keyboard disappear")
self.txtViewBottomSpace.constant = 0.0
UIView.animateWithDuration(0.4, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
I suggest to replace the static animation duration (0.4) with the animation duration of the keyboard, returned in the userInfo dictionary of the notification under UIKeyboardAnimationDurationUserInfoKey.
In this way your animation will be in sync with the keyboard animation. You can also retrieve the animation curve used by the keyboard with the UIKeyboardAnimationCurveUserInfoKey key.
func keyboardWillAppear(notification: NSNotification){
print("keyboard appear")
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
let animationDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue;
print("with height: \(keyboardSize.height)")
if keyboardSize.height == 0.0 {
return
}
self.txtViewBottomSpace.constant = keyboardSize.height
UIView.animateWithDuration(animationDuration!, delay: 0.0, options: .BeginFromCurrentState, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}
func keyboardWillDisappear(notification: NSNotification){
print("Keyboard disappear")
let animationDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue;
self.txtViewBottomSpace.constant = 0.0
UIView.animateWithDuration(animationDuration!, delay: 0.0, options: .BeginFromCurrentState, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
The reason for this is because keyboards can have different sizes, especially third party ones. So the first notification you receive will always be for the default system height, and any you receive after that will include the new heights of a third party keyboard extension if one is loaded. In order to get around this, in your method that handles the notification, you need to get the height originally, and then set that as a default height (I think 226). Then set a variable to this first height, and then for resulting calls to the notification method you can check if the new height is greater than the original height, and if it is you can find the delta, and then readjust your frames accordingly.

Resize the screen when keyboard appears

I am building a chat app. I have to move a textfield when keyboard appears. I am doing this with the following code:
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
if let keyboardSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
kbHeight = keyboardSize.height
self.animateTextField(true)
}
}
}
func keyboardWillHide(notification: NSNotification) {
self.animateTextField(false)
}
func animateTextField(up: Bool) {
var movement = (up ? -kbHeight : kbHeight)
UIView.animateWithDuration(0.3, animations: {
self.view.frame = CGRectOffset(self.view.frame, 0, movement)
})
}
But when I use this code, the first messages doesn't show. I guess I have to resize the tableview.
Here are screenshots Before and After the keyboard appears:
I am using auto layout.
How can I resolve this problem?
2020 UPDATE
Correctly using a constraint...
There is only one way to properly handle this mess in iOS.
Paste KUIViewController below in to your project,
Create a constraint which is very simply to the "bottom of your content".
Drag that constraint to bottomConstraintForKeyboard
KUIViewController will automatically and correctly resize your content view at all times.
Absolutely everything is totally automatic.
All Apple behaviors are handled correctly in the standard way, such as dismissing by taps, etc etc.
You are 100% completely done.
So "which view should you resize?"
You can not use .view ...
Because ... you cannot resize the .view in iOS!!!!!! Doh!
Simply make a UIView named "holder". It sits inside .view.
Put everything of yours inside "holder".
Holder will of course have four simple constraints top/bottom/left/right to .view.
That bottom constraint to "holder" is indeed bottomConstraintForKeyboard.
You're done.
Send a bill to the cliient and go drinking.
There is nothing more to do.
class KUIViewController: UIViewController {
// KBaseVC is the KEYBOARD variant BaseVC. more on this later
#IBOutlet var bottomConstraintForKeyboard: NSLayoutConstraint!
#objc func keyboardWillShow(sender: NSNotification) {
let i = sender.userInfo!
let s: TimeInterval = (i[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
let k = (i[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
bottomConstraintForKeyboard.constant = k
// Note. that is the correct, actual value. Some prefer to use:
// bottomConstraintForKeyboard.constant = k - bottomLayoutGuide.length
UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}
#objc func keyboardWillHide(sender: NSNotification) {
let info = sender.userInfo!
let s: TimeInterval = (info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
bottomConstraintForKeyboard.constant = 0
UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}
#objc func clearKeyboard() {
view.endEditing(true)
// (subtle iOS bug/problem in obscure cases: see note below
// you may prefer to add a short delay here)
}
func keyboardNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
keyboardNotifications()
let t = UITapGestureRecognizer(target: self, action: #selector(clearKeyboard))
view.addGestureRecognizer(t)
t.cancelsTouchesInView = false
}
}
Simply ...
Use KUIViewController anywhere a keyboard might appear.
class AddCustomer: KUIViewController, SomeProtocol {
class EnterPost: KUIViewController {
class EditPurchase: KUIViewController {
On those screens absolutely everything is now completely automatic regarding the keyboard.
You're done.
Phew.
*Minor footnote - background clicks correctly dismiss the keyboard. That includes clicks which fall on your content. This is correct Apple behavior. Any unusual variation from that would take a huge amount of very anti-Apple custom programming.
*Extremely minor footnote - so, any and all buttons on the screen will work 100% correctly every time. However in the INCREDIBLY obscure case of nested (!) container views inside nested (!) scroll views with nested (!) page view containers (!!!!), you may find that a button will seemingly not work. This seems to be basically a (obscure!) problem in current iOS. If you encounter this incredibly obscure problem, fortunately the solution is simple. Looking at the function clearKeyboard(), simply add a short delay, you're done.
#objc func clearKeyboard() {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
self.view.endEditing(true)
}
}
(A great tip from user #wildcat12 https://stackoverflow.com/a/57698468/294884 )
You can create an outlet of the bottom auto layout constraint of your table view.
Then simply use this code:
func keyboardWillShow(sender: NSNotification) {
let info = sender.userInfo!
var keyboardSize = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
bottomConstraint.constant = keyboardSize - bottomLayoutGuide.length
let duration: TimeInterval = (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
UIView.animate(withDuration: duration) { self.view.layoutIfNeeded() }
}
func keyboardWillHide(sender: NSNotification) {
let info = sender.userInfo!
let duration: TimeInterval = (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
bottomConstraint.constant = 0
UIView.animate(withDuration: duration) { self.view.layoutIfNeeded() }
}
If you have trouble creating the bottom constraint:
In storyboard
Select your search bar.
At the corner in the lower right you'll see 3 icons. Click the middle one looking like |-[]-|.
At the top of that popup, there are 4 boxes. Enter 0 at the one for the bottom.
Constraint created!
Now you can drag it to your view controller and add it as an outlet.
Another solution is to set the tableView.contentInset.bottom. But I haven't done that before. If you prefer that, I can try to explain it.
Using inset:
func keyboardWillShow(sender: NSNotification) {
let info = sender.userInfo!
let keyboardSize = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
tableView.contentInset.bottom = keyboardSize
}
func keyboardWillHide(sender: NSNotification) {
tableView.contentInset.bottom = 0
}
You can try this code for setting the inset. I haven't tried it myself yet, but it should be something like that.
EDIT: Changed the duration with the advice of nacho4d
From #Fattie 's message:
A detail - (unfortunately) clicks on your content will also dismiss the keyboard. (They both get the event.) However, this is almost always correct behavior; give it a try. There is no reasonable was to avoid this, so forget about it and go with the Apple-flow.
This can be solved by implementing the following UIGestureRecognizerDelegate's method:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return !(touch.view?.isKind(of: UIControl.self) ?? true)
}
That way, if the user touches any UIControl (UIButton, UITextField, etc.) the gesture recognizer won't call the clearKeyboard() method.
For this to work, remember to subclass UIGestureRecognizerDelegate either at class definition or with an extension. Then, in viewDidLoad() you should assign the gesture recognizer delegate as self.
Ready to copy and paste code:
// 1. Subclass UIGestureRecognizerDelegate
class KUIViewController: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet var bottomConstraintForKeyboard: NSLayoutConstraint!
func keyboardWillShow(sender: NSNotification) {
let i = sender.userInfo!
let k = (i[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
bottomConstraintForKeyboard.constant = k - bottomLayoutGuide.length
let s: TimeInterval = (i[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}
func keyboardWillHide(sender: NSNotification) {
let info = sender.userInfo!
let s: TimeInterval = (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
bottomConstraintForKeyboard.constant = 0
UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}
func keyboardNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow),
name: Notification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide),
name: Notification.Name.UIKeyboardWillHide,
object: nil)
}
func clearKeyboard() {
view.endEditing(true)
}
override func viewDidLoad() {
super.viewDidLoad()
keyboardNotifications()
let t = UITapGestureRecognizer(target: self, action: #selector(clearKeyboard))
view.addGestureRecognizer(t)
t.cancelsTouchesInView = false
// 2. Set the gesture recognizer's delegate as self
t.delegate = self
}
// 3. Implement this method from UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return !(touch.view?.isKind(of: UIControl.self) ?? true)
}
}
Maybe it will help somebody.
You can achieve the desired behavior without using interface builder at all
First of all you will need to create a constraint and calculate safe area insets in order to support buttonless devices properly
var container: UIView!
var bottomConstraint: NSLayoutConstraint!
let safeInsets = UIApplication.shared.windows[0].safeAreaInsets
then initialize it somewhere in your code
container = UIView()
bottomConstraint = container.bottomAnchor.constraint(equalTo: view.bottomAnchor)
attach it to view and activate
view.addSubview(container)
NSLayoutConstraint.activate([
...
container.leadingAnchor.constraint(equalTo: view.leadingAnchor),
container.trailingAnchor.constraint(equalTo: view.trailingAnchor),
container.topAnchor.constraint(equalTo: view.topAnchor),
bottomConstraint,
...
])
and finally
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if bottomConstraint.constant == 0 {
bottomConstraint.constant = -keyboardSize.height + safeInsets.bottom
view.layoutIfNeeded()
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
bottomConstraint.constant = 0
view.layoutIfNeeded()
}
Also if your view is something scrollable and you want to move it up with keyboard and return to initial position as the keyboard hides, you can change view's contentOffset
view.contentOffset = CGPoint(x: view.contentOffset.x, y: view.contentOffset.y + keyboardSize.height - safeInsets.bottom)
for scrolling up, and
view.contentOffset = CGPoint(x: view.contentOffset.x, y: view.contentOffset.y - keyboardSize.height + safeInsets.bottom)
to move it down
if you don't want to fight with this yourself you might find the TPKeyboardAvoiding framework useful
Simply just following the 'installation instructions' i.e. drag and drop the appropriate .h/.m files into your project and then make you ScrollView / TableView a subclass like below:

Resources