UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

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

Swift version: 5.6

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.)

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on 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().

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on 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 Pro SwiftUI 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 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 Beyond Code

Was this page useful? Let us know!

Average rating: 4.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.