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.)
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
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()
.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
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.