NEW: Subscribe to Hacking with Swift+ and accelerate your learning! >>

How to detect keyboard input using pressesBegan() and pressesEnded()

Swift version: 5.2

Paul Hudson    @twostraws   

UIKit lets us detect keyboard input from the user through the methods pressesBegan() and pressesEnded(), both of which are passed a set of UIPress instances that contain key codes and modifiers we can inspect. If you implement one of these two methods, you should call super to forward the message on for any keyboard events you don’t handle.

For example, if you had a dice game you could make it so that the user could press R to roll the dice or H to show a help screen, all by implementing this method in a view controller:

override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
    guard let key = presses.first?.key else { return }

    switch key.keyCode {
    case .keyboardR:
        print("Roll dice")
    case .keyboardH:
        print("Show help")
    default:
        super.pressesBegan(presses, with: event)
    }
}

You might see folks always calling super.pressesBegan() even when they handle the keypress, but that’s likely to cause problems because UIKit will pass the keypress up the responder chain even after you’ve handled it - several objects may act on the same keypress.

The pressesEnded() method works in much the same way: you can override it in a view or view controller, read which key was released, then pass the event on to super if you don’t handle it. For example, if you had a quiz app where you wanted the user to proceed when they press and release the spacebar, you’d write this:

override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
    guard let key = presses.first?.key else { return }

    switch key.keyCode {
    case .keyboardSpacebar:
        print("Continue the quiz…")
    default:
        super.pressesEnded(presses, with: event)
    }
}

Rather than using the keyCode constants, you can also read the exact letters that were tapped with the characters property.

If you combine pressesBegan() and pressesEnded(), you can effectively detect when the user is holding down a key. For example, this creates a custom AVPlayerViewController subclass that plays a movie only while spacebar is being held down:

import AVKit
import UIKit

class CustomMovieController: AVPlayerViewController {
    override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
        guard let key = presses.first?.key else { return }

        switch key.keyCode {
        case .keyboardSpacebar:
            player?.play()
        case .keyboardLeftArrow:
            player?.seek(to: .zero)
        default:
            super.pressesBegan(presses, with: event)
        }
    }

    override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
        guard let key = presses.first?.key else { return }

        switch key.keyCode {
        case .keyboardSpacebar:
            player?.pause()
        default:
            super.pressesEnded(presses, with: event)
        }
    }
}

To try that out, create an AVPlayer item with a movie you want to play, then pass it in. This will show the movie player when the screen is tapped:

import AVKit
import UIKit

class ViewController: UIViewController {
     override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let videoURL = URL(string: "https://bit.ly/aryashake")
        let vc = CustomMovieController()
        vc.player = AVPlayer(url: videoURL!)
        present(vc, animated: true)
    }
}

(Yes, that’s my dog. Yes, she knows she’s beautiful.)

Subscribe to Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reading modifier keys such as Cmd, Option, and Shift

Along with the key that was pressed, UIKit also sends us any modifier keys that were held down such as Option and Shift. These are provided as a set, so you can check for particular keys using contains() then one of the UIKeyModifierFlags such as .control.

For example, this creates a view controller with a red rectangle in the center, and if you press Shift then either left arrow or right arrow the rectangle rotates in the appropriate direction:

class ViewController: UIViewController {
    let rectangle = UIView(frame: CGRect(x: 0, y: 0, width: 256, height: 256))

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(rectangle)
        rectangle.backgroundColor = .red
    }

    override func viewDidLayoutSubviews() {
        rectangle.center = view.center
    }

    override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
        guard let key = presses.first?.key else { return }
        guard key.modifierFlags.contains(.shift) else { return }

        UIView.animate(withDuration: 0.5) {
            switch key.keyCode {
            case .keyboardLeftArrow:
                self.rotate(by: -.pi / 2)
            case .keyboardRightArrow:
                self.rotate(by: .pi / 2)
            default:
                super.pressesBegan(presses, with: event)
            }
        }
    }

    func rotate(by amount: CGFloat) {
        rectangle.transform = rectangle.transform.concatenating(CGAffineTransform(rotationAngle: amount))
    }
}

If you are using characters to read the actual letters that get tapped, you might find it useful to try charactersIgnoringModifiers – it sends back the same string, except ignoring any modifier keys. For example, if the user press Shift+n characters will be set to “N” but key.charactersIgnoringModifiers will be set to “n” because it ignores the Shift key.

Reading all presses

There’s one last thing you might want to do, which is to read all the current keyboard presses that are active when a new one comes in. This would be useful if you wanted to check if the user was holding down two or three specific keys at the same time.

To do this, read the event?.allPresses property in either pressesBegan() or pressesEnded(), and evaluate the keys however you want. For example, this prints a message when the keys “a”, “b”, and “c” are held down:

override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
    let keys = event?.allPresses.compactMap { $0.key?.characters }.sorted()
    if keys == ["a", "b", "c"] {
        print("Key combination pressed!")
    }
}

Tip: If you’re using Swift 5.2 or later, you can write event?.allPresses.compactMap(\.key?.characters).sorted().

Subscribe to Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Available from iOS 13.4

Similar solutions…

About the Swift Knowledge Base

This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.2/5

Link copied to your pasteboard.