响应 SwiftUI 中的按键事件
Respond to key press events in SwiftUI
我想响应按键操作,例如 macOS/OSX 上的 esc
键,以及 iPad 上使用外部键盘时的按键。我该怎么做?
我曾考虑过将 @available
/#available
与 SwiftUI 的 onExitCommand
一起使用,这看起来很有前途,但不幸的是那只适用于 macOS/OSX。我如何才能响应 SwiftUI 中的按键,而不仅仅是 macOS/OSX?
更新:SwiftUI 2 现在有 .keyboardShortcut(_:modifiers:)
.
旧答案:
感谢 @Asperi 为我指明了正确的方向,我现在已经成功完成了这项工作。
解决方案是使用 UIKeyCommand
。这是我做的,但你可以根据你的情况进行不同的调整。
我有一个名为 AppState
的 @EnvironmentObject
,它可以帮助我设置委托,因此它们的键盘输入可能会因当前显示的视图而异。
protocol KeyInput {
func onKeyPress(_ key: String)
}
class KeyInputController<Content: View>: UIHostingController<Content> {
private let state: AppState
init(rootView: Content, state: AppState) {
self.state = state
super.init(rootView: rootView)
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func becomeFirstResponder() -> Bool {
true
}
override var keyCommands: [UIKeyCommand]? {
switch state.current {
case .usingApp:
return [
UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(keyPressed(_:)))
]
default:
return nil
}
}
@objc private func keyPressed(_ sender: UIKeyCommand) {
guard let key = sender.input else { return }
state.delegate?.onKeyPress(key)
}
}
AppState (@EnvironmentObject
):
class AppState: ObservableObject {
var delegate: KeyInput?
/* ... */
}
场景代理看起来像这样:
let stateObject = AppState()
let contentView = ContentView()
.environmentObject(stateObject)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = KeyInputController(rootView: contentView, state: stateObject)
/* ... */
}
这使得现在根据按下的键添加功能变得非常容易。
符合KeyInput
,例如:
struct ContentView: View, KeyInput {
/* ... */
var body: some View {
Text("Hello world!")
.onAppear {
self.state.delegate = self
}
}
func onKeyPress(_ key: String) {
print(key)
guard key == UIKeyCommand.inputEscape else { return }
// esc key was pressed
/* ... */
}
}
在 macOS 和 tvOS 上,视图有一个 onExitCommand(perform:)
修饰符。
来自 Apple 的 documentation:
Sets up an action that triggers in response to receiving the exit command while the view has focus.
The user generates an exit command by pressing the Menu button on tvOS, or the escape key on macOS.
例如:
struct ContentView: View {
var body: some View {
VStack {
TextField("Top", text: .constant(""))
.onExitCommand(perform: {
print("Exit from top text field")
})
TextField("Bottom", text: .constant(""))
.onExitCommand(perform: {
print("Exit from bottom text field")
})
}
.padding()
}
}
一个更简单的解决方案是 .onExitCommand 修饰符。
正如@Chuck H 评论的那样,如果文本字段位于 ScrollView 内,或者在我的情况下是 Section,则此方法无效。
但我发现只要将 TextField 及其 .onExitCommand 嵌入到 HStack 或 VStack 中,.onExitCommand 就可以正常工作。
HStack { // This HStack is for .onExitCommand to work
TextField("Nombre del proyecto", text: $texto)
.onExitCommand(perform: {
print("Cancelando edición....")
})
.textFieldStyle(PlainTextFieldStyle())
.lineLimit(1)
}
我想响应按键操作,例如 macOS/OSX 上的 esc
键,以及 iPad 上使用外部键盘时的按键。我该怎么做?
我曾考虑过将 @available
/#available
与 SwiftUI 的 onExitCommand
一起使用,这看起来很有前途,但不幸的是那只适用于 macOS/OSX。我如何才能响应 SwiftUI 中的按键,而不仅仅是 macOS/OSX?
更新:SwiftUI 2 现在有 .keyboardShortcut(_:modifiers:)
.
旧答案:
感谢 @Asperi 为我指明了正确的方向,我现在已经成功完成了这项工作。
解决方案是使用 UIKeyCommand
。这是我做的,但你可以根据你的情况进行不同的调整。
我有一个名为 AppState
的 @EnvironmentObject
,它可以帮助我设置委托,因此它们的键盘输入可能会因当前显示的视图而异。
protocol KeyInput {
func onKeyPress(_ key: String)
}
class KeyInputController<Content: View>: UIHostingController<Content> {
private let state: AppState
init(rootView: Content, state: AppState) {
self.state = state
super.init(rootView: rootView)
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func becomeFirstResponder() -> Bool {
true
}
override var keyCommands: [UIKeyCommand]? {
switch state.current {
case .usingApp:
return [
UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(keyPressed(_:)))
]
default:
return nil
}
}
@objc private func keyPressed(_ sender: UIKeyCommand) {
guard let key = sender.input else { return }
state.delegate?.onKeyPress(key)
}
}
AppState (@EnvironmentObject
):
class AppState: ObservableObject {
var delegate: KeyInput?
/* ... */
}
场景代理看起来像这样:
let stateObject = AppState()
let contentView = ContentView()
.environmentObject(stateObject)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = KeyInputController(rootView: contentView, state: stateObject)
/* ... */
}
这使得现在根据按下的键添加功能变得非常容易。
符合KeyInput
,例如:
struct ContentView: View, KeyInput {
/* ... */
var body: some View {
Text("Hello world!")
.onAppear {
self.state.delegate = self
}
}
func onKeyPress(_ key: String) {
print(key)
guard key == UIKeyCommand.inputEscape else { return }
// esc key was pressed
/* ... */
}
}
在 macOS 和 tvOS 上,视图有一个 onExitCommand(perform:)
修饰符。
来自 Apple 的 documentation:
Sets up an action that triggers in response to receiving the exit command while the view has focus.
The user generates an exit command by pressing the Menu button on tvOS, or the escape key on macOS.
例如:
struct ContentView: View {
var body: some View {
VStack {
TextField("Top", text: .constant(""))
.onExitCommand(perform: {
print("Exit from top text field")
})
TextField("Bottom", text: .constant(""))
.onExitCommand(perform: {
print("Exit from bottom text field")
})
}
.padding()
}
}
一个更简单的解决方案是 .onExitCommand 修饰符。 正如@Chuck H 评论的那样,如果文本字段位于 ScrollView 内,或者在我的情况下是 Section,则此方法无效。 但我发现只要将 TextField 及其 .onExitCommand 嵌入到 HStack 或 VStack 中,.onExitCommand 就可以正常工作。
HStack { // This HStack is for .onExitCommand to work
TextField("Nombre del proyecto", text: $texto)
.onExitCommand(perform: {
print("Cancelando edición....")
})
.textFieldStyle(PlainTextFieldStyle())
.lineLimit(1)
}