dillon
dillon

Reputation: 33

Swift - Dismiss Keyboard and call TouchUpInside Button Simultaneously not working when using UIKeyboardWillChangeFrame

I am using swift and having issues with TouchUpInside: if I'm using UIKeyboardWillChangeFrame or UIKeyboardWillShow/UIKeyboardWillHide, & the keyboard is showing, & the button I'm trying to press is behind the keyboard when keyboard is shown initially. (If I scroll down to the button till visible and press, no touchUpInside called).

TouchDown seems to work consistently whether the keyboard is showing or not, but TouchUpInside is not called. If the button is above the top of the keyboard when the keyboard is initially shown, TouchUpInside works. I'm using keyboardNotification to set the height of a view below my scrollView in order to raise up my scrollView when keyboard is showing. From what I can see it's only usually when the button is the last element in the scrollView (and therefore likely to be behind the keyboard when keyboard shown).

@IBOutlet var keyboardHeightLayoutConstraint: NSLayoutConstraint?
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var saveButton: UIButton!
@IBAction func saveTouchUpInside(_ sender: UIButton) {
   print("touchupinside = does not work")
}
@objc func saveTouchDown(notification:NSNotification){
   print("touchdown = works")
}

viewWillAppear:

textField.delegate = self

NotificationCenter.default.addObserver(self,selector:#selector(self.keyboardNotification(notification:)),name: 

NSNotification.Name.UIKeyboardWillChangeFrame,object: nil)
self.saveButton.addTarget(self, action:#selector(ViewController.saveTouchDown(notification:)), for: .touchDown)

deinit {
    NotificationCenter.default.removeObserver(self)
}

@objc func keyboardNotification(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
        let endFrameY = endFrame?.origin.y ?? 0
        let duration:TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
        let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
        let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
        let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
        if endFrameY >= UIScreen.main.bounds.size.height {
            self.keyboardHeightLayoutConstraint?.constant = 0.0
        } else {
            self.keyboardHeightLayoutConstraint?.constant = endFrame?.size.height ?? 0.0
        }
            UIView.animate(withDuration: duration, delay: TimeInterval(0),options: animationCurve, animations: { self.view.layoutIfNeeded() }, completion: nil)
        }
    }

I would like to dismiss the keyboard and call saveTouchUpInside at the same time, without using TouchDown.

Upvotes: 0

Views: 301

Answers (1)

Partha G
Partha G

Reputation: 1236

I abstract the keyboard interaction as a separate class so that my controllers do not get bloated(also follows separation of concerns). Here is the keyboard manager class that I use.

import UIKit


/**
*  To adjust the scroll view associated with the displayed view to accommodate
*  the display of keyboard so that the view gets adjusted accordingly without getting hidden
*/
class KeyboardManager {

    private var scrollView: UIScrollView

    /**
    *  -parameter scrollView: ScrollView that need to be adjusted so that it does not get clipped by the presence of the keyboard
    */
    init(scrollView: UIScrollView) {

        self.scrollView = scrollView

        let notificationCenter = NotificationCenter.default
        notificationCenter.addObserver(self,
                                                                     selector: #selector(adjustForKeyboard),
                                                                     name: UIResponder.keyboardWillHideNotification, object: nil)
        notificationCenter.addObserver(self,
                                                                     selector: #selector(adjustForKeyboard),
                                                                     name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
    }

    /**
    * Indicates that the on-screen keyboard is about to be presented.
    *  -parameter notification: Contains animation and frame details on the keyboard
    *
    */
    @objc func adjustForKeyboard(notification: Notification) {

        guard let containedView = scrollView.superview else { return }

        let userInfo = notification.userInfo!
        let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let keyboardViewEndFrame = containedView.convert(keyboardScreenEndFrame, to: containedView.window)

        let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber
        let rawAnimationCurveValue = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).uintValue

        UIView.animate(withDuration: TimeInterval(truncating: duration),
                                     delay: 0,
                                     options: [UIView.AnimationOptions(rawValue: rawAnimationCurveValue)],
                                     animations: {

                                        if notification.name == UIResponder.keyboardWillHideNotification {
                                            self.scrollView.contentInset = UIEdgeInsets.zero
                                        } else {
                                            self.scrollView.contentInset = UIEdgeInsets(top: 0,
                                                                                                                                    left: 0,
                                                                                                                                    bottom: keyboardViewEndFrame.height,
                                                                                                                                    right: 0)
                                        }

                                        self.scrollView.scrollIndicatorInsets = self.scrollView.contentInset

        },
                                     completion: nil)
    }

    deinit {
        let notificationCenter = NotificationCenter.default
        notificationCenter.removeObserver(self)
    }

}

Its usage is like this

  • create a reference to the keyboard manager
 private var keyboardManager: KeyboardManager!
  • and assign the keyboard manager class like below in viewDidLoad where self.scrollView is the scrollView that you are working with
 self.keyboardManager = KeyboardManager(scrollView: self.scrollView)

This should take care of the issue. If that does not work, probably a sample project might help to take a deep dive into that.

Upvotes: 0

Related Questions