Keyboard-driven actions in SwiftUI with onKeyPress
Learn how to capture and respond to the pressed keys in a hardware keyboard in a SwiftUI app.
In iOS 17 and later, you can use the onKeyPress(_:action:)
method to make a view respond to a physical keyboard event. It detects when a key is pressed and lets you perform actions based on it.
To take advantage of this method, be sure the view is focusable.
struct ContentView: View {
@FocusState var isFocused: Bool
var body: some View {
MyView()
.focusable()
.focused($isFocused)
}
}
Then, apply the modifier to your view.
MyView()
.focusable()
.focused($isFocused)
// 1.
.onKeyPress { pressedKey in
// 2.
print("You have just pressed \(pressedKey.key)")
// 3.
return .handled
}
The method works as follows:
- It receives a parameter of type
KeyPress
, a type describing the key that was just pressed by the user. - It performs an
action
once one or more keys have been pressed. - Then, it returns a
KeyPress.Result
enum value specifying whether the action handled the event or not. Use:
Here is an example of how to fully implement the modifier in a SwiftUI view.
import SwiftUI
struct ContentView: View {
@FocusState var isFocused: Bool?
@State private var squareColor: Color = .gray
@State private var colorName: String = "Gray"
var body: some View {
ZStack {
Color.black
.ignoresSafeArea()
VStack(alignment: .center, spacing: 8) {
Text("Press the **first letter** of a color to change the background!")
.font(.subheadline)
.foregroundColor(.gray)
.padding(.top, 40)
Spacer()
Text("You chose \(colorName)")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(squareColor)
.padding(10)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.white.opacity(0.2))
.shadow(color: squareColor.opacity(0.7), radius: 8, x: 0, y: 4)
)
.padding(.bottom, 20)
Spacer()
VStack(alignment: .center, spacing: 10) {
ForEach(0..<20) { row in
HStack(spacing: 10) {
ForEach(0..<25) { column in
LightingSquare(color: $squareColor)
}
}
.offset(x: row % 2 == 0 ? 0 : 12.5, y: 0)
}
}
// Make the view focusable
.focusable()
.focused($isFocused, equals: true)
// Implement the key press method
.onKeyPress { key in
handleKeyPress(keyPress: key)
return .handled
}
Spacer()
}
.padding()
.onAppear {
isFocused = true
}
}
}
// Handle Key Press for Color Change
private func handleKeyPress(keyPress: KeyPress) {
switch keyPress.key {
case KeyEquivalent("r"):
squareColor = .red
colorName = "Red"
case KeyEquivalent("g"):
squareColor = .green
colorName = "Green"
case KeyEquivalent("b"):
squareColor = .blue
colorName = "Blue"
case KeyEquivalent("y"):
squareColor = .yellow
colorName = "Yellow"
case KeyEquivalent("t"):
squareColor = .teal
colorName = "Teal"
case KeyEquivalent("p"):
squareColor = .pink
colorName = "Pink"
case KeyEquivalent("w"):
squareColor = .white
colorName = "White"
case KeyEquivalent("m"):
squareColor = .mint
colorName = "Mint"
case KeyEquivalent("o"):
squareColor = .orange
colorName = "Orange"
default:
break
}
}
}
struct LightingSquare: View {
@Binding var color: Color
@State private var isFlashing: Bool = false
var body: some View {
Rectangle()
.fill(color)
.frame(width: 15, height: 15)
.opacity(isFlashing ? 0.8 : 0.3)
.animation(
Animation.easeInOut(duration: Double.random(in: 0.5...2.5)).repeatForever(autoreverses: true),
value: isFlashing
)
.onAppear {
isFlashing = true
}
}
}
In this view, a focusable grid of flashing squares dynamically changes its color based on the physical key pressed by the user: the onKeyPress
method captures the specific key inputs to update the grid and background colors in real-time when it matches with any case provided.