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
}
Related
I know this is an often asked question, but I cannot get any of the posted solutions (e.g. here) to work properly in my case. First off, when I handle keyboardWillShow notification using
if let keyboardHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
}
nothing happens. It seems like a bit of a hack, but this implementation below (specifically in keyboardWillShow) works for me, however strangely (1) it doesn't work the first time but works every subsequent time also (2) a large white bar appears above the keyboard for some reason? I don't think it matters but my UI lets the user tap an edit button so they can see what is editable, then they edit the textView, then tap done. The problem I'm trying to solve is that this textView is at the bottom of the tableView, so the keyboard obscures it while editing.
class ScoreAndStatsViewController: UITableViewController, UITextFieldDelegate, UITextViewDelegate {
#IBOutlet weak var editButton: UIButton!
#IBOutlet weak var notesTextField: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIApplication.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIApplication.keyboardWillHideNotification, object: nil)
}
#IBAction func editButtonIsPressed(_ sender: UIButton) {
if editMode == false {
notesTextField.isEditable = true
notesTextField.backgroundColor = iPhoneForeGroundColor
editButton.setTitle("Done", for: .normal)
self.editMode = true
//If edit mode is true, this means they've hit the done button so save
} else {
//save data
editButton.setTitle("Edit", for: .normal)
notesTextField.isEditable = false
notesTextField.backgroundColor = UIColor.clear
self.editMode = false
}
}
// MARK: Keyboard Notifications
#objc func keyboardWillShow(notification: NSNotification) {
let pointInTable:CGPoint = notesTextField.superview!.convert(notesTextField.frame.origin, to: tableView)
var contentOffset:CGPoint = tableView.contentOffset
contentOffset.y = pointInTable.y
if let accessoryView = tableView.inputAccessoryView {
contentOffset.y -= accessoryView.frame.size.height
}
tableView.contentOffset = contentOffset
}
#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 = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
})
}
}
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.
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.
I'm currently working on an app for iPhone using Swift 3 however I have ran into an issue with the scrollview.
Prior to selecting a text field and having the keyboard appear, the scrollview work normally (ie.: I can scroll up and down), however after dismissing the keyboard, the scrollview does not allow me to scroll anymore which is my problem.
Note: if I select a text field again and make the keyboard appear it works fine and stops working once it is dismissed again.
I have checked the isScrollEnabled property of the scrollview and it appears to be enabled. Unfortunately I am still not too familiar with all the details of the scrollview and cannot seem to figure out why it has stopped working.
Any help or pointers as to where I could look would be greatly appreciated.
Edit: there is quite a bit of code but here is the narrowed down portion related to scroll view and keyboard:
class ScrollViewController: UIViewController, UITextViewDelegate, UITextFieldDelegate {
//Scroll view
#IBOutlet weak var scrollView: UIScrollView!
//UIView inside the scroll view
#IBOutlet weak var contentView: UIView!
//Save button on the top right corner
#IBOutlet weak var saveButton: UIBarButtonItem!
//Text field being editted
var activeTextField:UITextField?
fileprivate var contentInset:CGFloat?
fileprivate var indicatorInset:CGFloat?
override func viewDidLoad() {
contentInset = scrollView.contentInset.bottom
indicatorInset = scrollView.scrollIndicatorInsets.bottom
NotificationCenter.default.addObserver(self,
selector: #selector(ScrollViewController.keyboardWillShow(_:)),
name: NSNotification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(ScrollViewController(_:)),
name: NSNotification.Name.UIKeyboardWillHide,
object: nil)
}
func adjustInsetForKeyboardShow(_ show: Bool, notification: Notification) {
let userInfo = notification.userInfo ?? [:]
let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let adjustmentHeight = (keyboardFrame.height + 20) * (show ? 1 : -1)
scrollView.contentInset.bottom = (contentInset! + adjustmentHeight)
scrollView.scrollIndicatorInsets.bottom = (indicatorInset! + adjustmentHeight)
}
func keyboardWillShow(_ notification: Notification) {
adjustInsetForKeyboardShow(true, notification: notification)
}
func keyboardWillHide(_ notification: Notification) {
adjustInsetForKeyboardShow(false, notification: notification)
}
//Tap gesture to dismiss the keyboard
#IBAction func hideKeyboard(_ sender: AnyObject) {
self.view.endEditing(false)
}
deinit {
NotificationCenter.default.removeObserver(self);
}
}
I have create extension of UIViewController and create method for scrollView. Just need to call from viewWillAppear() and viewDidDisappear()
extension UIViewController {
func registerForKeyboradDidShowWithBlock (scrollview:UIScrollView ,block: ((CGSize?) -> Void)? = nil ){
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardDidShow, object: nil, queue: nil) { (notification) in
if let userInfo = (notification as NSNotification).userInfo {
if let keyboarRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.findFirstResponder() != nil {
let keyboarRectNew = self.view .convert(keyboarRect, to: self.view)
let scrollViewSpace = scrollview.frame.origin.y + scrollview.contentOffset.y
let textFieldRect:CGRect = self.view.findFirstResponder()!.convert(self.view.findFirstResponder()!.bounds, to: self.view)
let textFieldSpace = textFieldRect.origin.y + textFieldRect.size.height
let remainingSpace = self.view.frame.size.height - keyboarRectNew.size.height
if scrollViewSpace + textFieldSpace > remainingSpace {
let gap = scrollViewSpace + textFieldSpace - remainingSpace
scrollview .setContentOffset(CGPoint(x: scrollview.contentOffset.x, y: gap), animated: true)
}
}
}
}
block?(CGSize.zero)
}
}
func registerForKeyboardWillHideNotificationWithBlock ( scrollview:UIScrollView ,block: ((Void) -> Void)? = nil) {
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillHide, object: nil, queue: nil, using: { (notification) -> Void in
scrollview.scrollRectToVisible(CGRect(x: 0, y: 0, width: 0, height: 0), animated: true)
scrollview.contentOffset = CGPoint(x: 0, y: 0)
scrollview.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0)
scrollview.scrollIndicatorInsets = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
block?()
})
}
func deregisterKeyboardShowAndHideNotification (){
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
self.view.findFirstResponder()?.resignFirstResponder()
}
}
I have created extension of UIView to create find first responder method in that.
extension UIView {
func findFirstResponder() -> UIView? {
if self.isFirstResponder {
return self
}
for subview: UIView in self.subviews {
let firstResponder = subview.findFirstResponder()
if nil != firstResponder {
return firstResponder
}
}
return nil
}
}
Create Extension of UIView and write down this method.
extension UIView {
func findFirstResponder() -> UIView? {
if self.isFirstResponder {
return self
}
for subview: UIView in self.subviews {
let firstResponder = subview.findFirstResponder()
if nil != firstResponder {
return firstResponder
}
}
return nil
}
}
Their is a third party library in Objective C to handle the keyboard with scroll view, collection view and table view. The name of library is tpkeyboardavoidingscrollview. Try to embed this if possible.
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: