响应 SwiftUI 中的按键事件

Geo*_*e_E 8 macos keyboard-events ios swift swiftui

我想要响应按键,例如escmacOS/OSX 上的按键以及在 iPad 上使用外部键盘时的按键。我怎样才能做到这一点?

我曾想过将@available/#available与 SwiftUI 一起使用onExitCommand,这看起来很有前途,但不幸的是这只适用于 macOS/OSX。除了 macOS/OSX 之外,如何响应 SwiftUI 中的按键操作?

Pet*_*orn 6

在 macOS 和 tvOS 上有一个onExitCommand(perform:)视图修饰符。来自苹果的文档

设置在视图具有焦点时响应接收退出命令而触发的操作。

用户通过按 tvOS 上的菜单按钮或 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()
    }
    
}
Run Code Online (Sandbox Code Playgroud)


Geo*_*e_E 3

更新:SwiftUI 2 现在有.keyboardShortcut(_:modifiers:).


旧答案:

感谢@Asperi为我指明了正确的方向,我现在已经成功地完成了这项工作。

解决方案是使用UIKeyCommand. 这是我所做的,但您可以根据自己的情况进行不同的调整。

我有一个@EnvironmentObjectCalled AppState,它可以帮助我设置委托,因此它们的键盘输入可能会有所不同,具体取决于当前显示的视图。

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

应用程序状态 ( @EnvironmentObject):

class AppState: ObservableObject {

    var delegate: KeyInput?
    /* ... */
}
Run Code Online (Sandbox Code Playgroud)

场景委托看起来像这样:

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)

    /* ... */
}
Run Code Online (Sandbox Code Playgroud)

这使得现在根据按下的键添加功能变得非常容易。

符合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
        /* ... */
    }
}
Run Code Online (Sandbox Code Playgroud)