Search code examples
iosobjective-cswiftkeystroke

How to measure touch pressure (keystroke) on iOS system keyboard?


Probably there is no way to measure touch pressure using a custom UIGestureRecognizer, since we cannot directly access the system keyboard's view [1]. It can be achieved if a custom inputViewController is used but it's not an option for me. I want to store key hold time and key press pressure for each key. Do you know a way to do this?

Edit: [1] I said "We cannot directly access the system keyboard's view." but actually we can: UIRemoteKeyboardWindow > InputSetContainerView > InputSetHostView.

I think that an improvement to Nirav Bhatt's answer may solve this issue but it disables the functionality of touches on the keyboard view. When I touch on a key, it outputs force of touch but the corresponding letter isn't typed to text field.


Solution

  • The solution is easier than I think. For iOS 9+ systems, we can follow these steps to access system keyboard and listen keystroke...

    At first, create a custom UIGestureRecognizer to listen keystroke. The implementation of this step is skipped, since it's trivial (just override touchesBegan, touchesMoved, touchesEnded).

    Add KeyboardManager to access keyboard view:

    final class KeyboardManager {
    
        static let shared = KeyboardManager()
    
        private init() { }
    
        /// Returns keyboard view on application window
        ///
        /// - Returns: Keyboard view
        func keyboardView() -> UIView? {
            for window in UIApplication.shared.windows {
                if let keyboardView = keyboardViewFromWindow(window) {
                    return keyboardView
                }
            }
            return nil
        }
    
        /// Returns keyboard view from given window
        ///
        /// - Parameter window: Keyboard view container candidate window
        /// - Returns: Keyboard view
        func keyboardViewFromWindow(_ window: UIWindow) -> UIView? {
            if window.hasClassNameSuffix("UIRemoteKeyboardWindow") {
                let inputSetContainerView = window.subview(withSuffix: "InputSetContainerView")
                let inputSetHostView = inputSetContainerView?.subview(withSuffix: "InputSetHostView")
                return inputSetHostView
            }
    
            return nil
        }
    }
    

    UIView extension functions used above:

    @nonobjc extension UIView {
    
        /// Returns first found subview with given class name
        ///
        /// - Parameter className: Subview class name
        /// - Returns: First subview with given class name
        func subview(withSuffix className: String) -> UIView? {
            return subviews.first {
                $0.hasClassNameSuffix(className)
            }
        }
    
        /// Compares suffix of view class name with given name
        ///
        /// - Parameter className: Class name that will be compared
        /// - Returns: Comparison result of view class name and given name
        func hasClassNameSuffix(_ className: String) -> Bool {
            return NSStringFromClass(type(of: self)).hasSuffix(className)
        }
    }
    

    Then, add your gesture recognizer to keyboard view using below functions:

    KeyboardManager.shared.keyboardView()?.addGestureRecognizer(keyboardGestureRecognizer).

    Note: Your gesture recognizer shouldn't extend UITapGestureRecognizer. UILongPressGestureRecognizer is okay. We cannot add multiple tap gesture recognizers with same type to a view.