What is the best way to fix keyboard overlapping without frameworks like IQKeyboardManagerSwift or IHKeyboardAvoiding?
Set your scrollview bottom layout constraint,
This makes all textfields available by scrolling and you don't need to think about other devices and keyboard height differences.
#IBOutlet weak var mainScrollViewBottomConstraint: NSLayoutConstraint!
open override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWillShow(_ notification: Notification) {
let userInfo = (notification as NSNotification).userInfo!
let keyboardSize = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
mainScrollViewBottomConstraint.constant = keyboardSize.height
}
func keyboardWillHide(_ notification: Notification) {
mainScrollViewBottomConstraint.constant = 0
}
EDIT: I made a video about my solution.
https://youtu.be/gS4AKcJAg3Y
The best way is, set a top constraint for your view and animate the view using constraint when click in your UITextfield.
#IBOutlet var constraintViewTop: NSLayoutConstraint!
Animate the view up in "textFieldDidBeginEditing' delegate.
func textFieldDidBeginEditing(_ textField: UITextField) {
switch textField {
case yourTextfield:
//This is the code for view up
UIView.animate(withDuration: 0.45, animations: {
self. constraintViewTop.constant = -172
})
default:
//This the code for view down
UIView.animate(withDuration: 0.45, animations: {
self. constraintViewTop.constant = 0
})
}
}
I have found the best way to handle keyboard overlapping issues, just embed(implement) all the view and textfields in the static table view controller and the system automatically will move up the keyboard by itself. If you don't like this way, go for IQKeyboardManager pod.
Related
I try to resize the height of a UITextView-field when the keyboard appears (iOS 14.2, Xcode 12.3). The distance from the bottom of the UITextView to the save area is 90 and hence the lower part of it is hidden by the keyboard and can't be seen while editing.
I tried it with the solution shown here: Resize the UITextView when keyboard appears
Accordingly, my code is as follows:
class EditierenVC: UIViewController, UITextFieldDelegate, UITextViewDelegate {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(false)
NotificationCenter.default.addObserver(
self,
selector: #selector(EditierenVC.handleKeyboardDidShow(notification:)),
name: UIResponder.keyboardDidShowNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(EditierenVC.handleKeyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
#objc func handleKeyboardDidShow(notification: NSNotification) {
guard let endframeKeyboard = notification
.userInfo![UIResponder.keyboardFrameEndUserInfoKey]
as? CGRect else { return }
textfeld.contentInset = UIEdgeInsets(
top: 0.0,
left: 0.0,
bottom: endframeKeyboard.size.height-85,
right: 0.0
)
view.layoutIfNeeded()
}
#objc func handleKeyboardWillHide() {
textfeld.contentInset = .zero
view.layoutIfNeeded()
}
//**************************
// MARK: - Views
//**************************
#IBOutlet weak var textfeld: UITextView!
Unfortunately the inset-size is not changed, when the keyboard appears and the text is partly hidden.
Has anyone an idea, why it is not working?
Thanks for your support
I could not come up with a solution by using content inset but I can suggest another way.
If you add bottom constraint to the textView and create outlet for that, you can change its constant value in notifications;
#objc func handleKeyboardDidShow(notification: NSNotification) {
guard let endframeKeyboard = notification
.userInfo![UIResponder.keyboardFrameEndUserInfoKey]
as? CGRect else { return }
textViewBottomConstraint.constant = endframeKeyboard.size.height-85
}
#objc func handleKeyboardWillHide() {
textViewBottomConstraint.constant = // set the previous value here
}
EDIT: Added the methods that were requested.
UPDATE: When I click on input 1 the keyboardwillchange callback is called once. When I click on the 2nd input without first dismissing the keyboard, the keyboardwillchange callback is called TWICE for some reason. Additionally, I noticed my code is still calculating the correct frames. However, it seems xcode is ignoring that or just overriding and putting things back to how they were originally constructed.
I have a screen with some image content at the top and 2 text fields and a text view at the bottom half. The inputs are in a stack view. Whenever I click on a text field/view I move the stack view up far enough so all inputs are visible. When I dismiss the keyboard the stack view goes back to it's original location. This works great! When I click on one text field (regular keyboard) and then click on the other text field (number pad) my stack view goes back to the original location when it shouldn't move at all. To try and fix this I put in some boolean that tracks if the keyboard is displayed and use it to prevent my UIKeyboardWillShow method from doing anything.
What is Xcode 9.1/Swift 4 doing that I can stop from happening?
Side note: I tried using IQKeyboardManager, but it doesn't seem to allow me to prevent the top image content from moving too much.
My relevant code:
class MoodEntryVC: UIViewController, UITextFieldDelegate, UITextViewDelegate {
#IBOutlet weak var questionsStackView: UIStackView!
#IBOutlet weak var hoursTextField: UITextField!
#IBOutlet weak var locationTextField: UITextField!
#IBOutlet weak var otherInfoTextView: UITextView!
#IBOutlet weak var headerBarView: UIView!
#IBOutlet weak var bgHeaderView: UIImageView!
var currentTextField: UITextField?
var currentTextView: UITextView?
var currentBGHeaderFrame: CGRect!
var currentQuestionsStackFrame: CGRect!
var currentFieldEditing: FieldType!
var keyboardVisible: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
hideKeyboardWhenTappedAround2()
locationTextField.delegate = self
hoursTextField.delegate = self
otherInfoTextView.delegate = self
currentBGHeaderFrame = bgHeaderView.frame
currentQuestionsStackFrame = questionsStackView.frame
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillChangeFrame(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
fileprivate func moveQuestionsAndBGHeader(_ newTopY: CGFloat, _ bgNewTopY: CGFloat) {
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
let newRect = CGRect(x: self.questionsStackView.frame.origin.x, y: newTopY, width: self.questionsStackView.bounds.width, height: self.questionsStackView.bounds.height)
self.questionsStackView.frame = newRect
self.bgHeaderView.frame = CGRect(x: self.bgHeaderView.frame.origin.x, y: bgNewTopY, width: self.bgHeaderView.bounds.width, height: self.bgHeaderView.bounds.height)
}, completion: nil)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
UIView.animate(withDuration: 1.3, delay: 0.0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
textField.resignFirstResponder()
}, completion: nil)
return true
}
#objc func keyboardDidShow(_ notification: NSNotification) {
keyboardVisible = true
}
#objc func keyboardWillChangeFrame(_ notification: NSNotification) {
print("keyboard will change")
}
#objc func keyboardWillShow(_ notification: NSNotification) {
if keyboardVisible {
return
}
// Get keyboard sizing
let endFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
// Get data entry view sizing
let topY = questionsStackView.frame.origin.y
let bottomY = questionsStackView.frame.height + topY
let newTopY = endFrame.origin.y - 10 - questionsStackView.frame.height
// Get bgHeaderView sizing
let bgTopY = bgHeaderView.frame.origin.y
let bgBottomY = bgHeaderView.frame.height + bgTopY
let gapToDataEntry = topY - bgBottomY
let bgNewTopY = newTopY - gapToDataEntry - bgHeaderView.frame.height
// This moves the stack view to the correct location above the keyboard
moveQuestionsAndBGHeader(newTopY, bgNewTopY)
}
#objc func keyboardWillHide(_ notification: NSNotification) {
UIView.animate(withDuration: 1.3, delay: 0.0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
self.moveQuestionsAndBGHeader(self.currentQuestionsStackFrame.origin.y, self.currentBGHeaderFrame.origin.y)
self.view.endEditing(true)
}, completion: nil)
keyboardVisible = false
}
Thank you!
try adding this method
func textFieldDidEndEditing(textField: UITextField) {
view.endEditing(true)
}
when first textfield will become de active then keyboard will hide. similarly when other textfield will become active then keyboard will show.
my needs:
to move the UIView up base on whether textField covered by keyboard and how much it covered.
Don't want to use scrollView to rebuild it.
to achieve 1. I have to know the coordinate Y of the textField I chose. Lots of
answers using NSNotificationCenter to build a listener. There is what i don't understand, why set nil to object? isn't the object who trigger the function below? I try to get the position of the object but failed. Because I set the nil to object. try to set textField to object still not working.
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: UIKeyboardWillShowNotification, object: UITextField())
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: UIKeyboardWillHideNotification, object: UITextField())
How to get the position of the chosen textField? if not declare something like this:
#IBOutlet var textField: UITextField!
Is there hidden information inside notification? maybe in the userInfo? cause the keyboard's height is in it.
let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
Here is my code:
import UIKit
class VC_Base: UIViewController, UITextFieldDelegate{
var keyBoardShow:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(VC_Base.keyboardWillShow), name: UIKeyboardWillShowNotification, object: self)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(VC_Base.keyboardWillHide), name: UIKeyboardWillHideNotification, object: self)
// Do any additional setup after loading the view.
}
func keyboardWillShow(notification: NSNotification) {
guard keyBoardShow else{
adjustingHightLight(true, notification: notification)
keyBoardShow = true
return
}
keyBoardShow = true
}
func keyboardWillHide(notification: NSNotification) {
adjustingHightLight(false, notification: notification)
keyBoardShow = false
}
func adjustingHightLight(show: Bool, notification: NSNotification) {
var userInfo = notification.userInfo!
if let object = notification.object as! UITextField!{
print("there is object")
let textFieldCoverByKeyBoard = self.view.frame.maxY - object.frame.maxY
let keyboardFrame: CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
let differenceOfTextFieldAndKeyBoard = textFieldCoverByKeyBoard - keyboardFrame.height
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSTimeInterval
let changeInHeight = differenceOfTextFieldAndKeyBoard * (show ? 1 : -1)
if differenceOfTextFieldAndKeyBoard < 5{
UIView.animateWithDuration(animationDuration, delay: 0, usingSpringWithDamping: 0, initialSpringVelocity: 0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.view.frame.offsetInPlace(dx: 0, dy: changeInHeight)}, completion: nil)
}
}
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
self.view.endEditing(true)
return true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.view.endEditing(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
texting is the basic function it should achieve several things below:
1.whether the keyboard cover textField?
if true, how much did it cover and move the UIView upward the value so the keyboard won't cover it.
if false, do nothing and show keyboard.
2.if I already select one textField and then chose another. it won't move up again (which will happened in some answers).
But seems there are not much answer fulfill it except using scrollView(which I don't want to rebuild...)
please help!!THX.
So I have a UIButton which sits at the bottom of the UIView until the keyboard appears then the bottom constraint is updated to the keyboard height.
This works fine with the iOS default keyboard, but when using a custom keyboard like swiftKey the bottom constraint is still that of the iOS Keyboard height.
I have noticed that a custom keyboard sets off three notifications instead of one. So this may be the issue but how can I use the correct value ?
#IBOutlet weak var fieldBottomConstant: NSLayoutConstraint!
var keyboardShowing = false
var keyboardHeight: CGFloat = 0.0
func keyboardWillShow(_ n:Notification) {
self.keyboardShowing = true
if let keyboardSize = (n.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
// fieldBottomConstant.constant = keyboardSize.height
keyboardHeight = keyboardSize.height
}
fieldBottomConstant.constant = keyboardHeight
}
func keyboardWillHide(_ n:Notification) {
self.keyboardShowing = false
fieldBottomConstant.constant = 0
}
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(AddViewController.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(AddViewController.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(AddViewController.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(AddViewController.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(AddViewController.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardDidChangeFrame, object: nil)
}
UPDATE
I have added some more notifications to watch for any changes, and the constraints change to the custom keyboard height after rotating the devices however still do not use the custom keyboard height when the keyboard is first loaded.
The answer was to use
UIKeyboardFrameEndUserInfoKey
You should listen NSNotification.Name.UIKeyboardWillChangeFrame
Hi I am using this code to move my view when a textView is selected, this is to make sure my texView is visible for when the keyboard pops up.
override func viewDidLoad() {
super.viewDidLoad() NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: self.view.window)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: self.view.window)
}
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
if keyboardSize.height == offset.height {
if self.view.frame.origin.y == 0 {
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
})
}
print(self.view.frame.origin.y)
}
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)
}
How can i only move the view if the bottom textView is selected? Because currently if you select the uppermost textView it moves half of it off screen.
I appreciate any help, thanks in advance.
In the notification callbacks check for bottomTextView.isFirstResponder() and move the view only if its true. Otherwise don't move the view.
Make your class a UITextViewDelegate like so:
class ViewController: UIViewController, UITextViewDelegate {
Then in viewDidLoad set only the bottom textView to be controlled by the delegate:
bottomTextView.delegate = self
Then you can use these functions, changing the values to suit your needs:
func textViewDidBeginEditing(textView: UITextView) {
self.view.frame.origin.y -= 150
}
func textViewDidEndEditing(textView: UITextView) {
self.view.frame.origin.y += 150
}
Rather than only moving the keyboard for a single text field, I suggest a more flexible approach.
What I do is to keep track of which text field is being selected and do some calculations to move the view controller's content view up just enough to expose the text field that is becoming active.
To do that you need to set up your view controller to be the text fields' delegates.
I have a development blog post that explains this in detail:
Shifting views to make room for the keyboard
The code in that post is in Objective-C, but the concepts are identical. I'm not sure I have the same code in Swift that I can share (The only code I've found is in a project I did for a client.)
That blog post references a project called RandomBlobs on Github.
I would suggest using a delegate. For example, you can set you bottom textView's delegate to self (add UITextViewDelegate first of course) and implement the
textViewDidBeginEditing(_:) method. When that's triggered, you will know that the user started editing this particular view.