swiftuibuttonuikeyboardnotificationcentertouch-up-inside

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.


Solution

  • 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

     private var keyboardManager: KeyboardManager!
    
     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.