Swift version: 5.10
UIKit lets us detect hardware 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.)
TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and more!
Sponsor Hacking with Swift and reach the world's largest Swift community!
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.
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()
.
GO FURTHER, FASTER Unleash your full potential as a Swift developer with the all-new Swift Career Accelerator: the most comprehensive, career-transforming learning resource ever created for iOS development. Whether you’re just starting out, looking to land your first job, or aiming to become a lead developer, this program offers everything you need to level up – from mastering Swift’s latest features to conquering interview questions and building robust portfolios.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Available from iOS 13.4
This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.
Link copied to your pasteboard.