SwiftUI Mac Catalyst应用中如何实现键盘(Escape键)关闭对话框?
在SwiftUI Mac Catalyst应用中实现Escape键关闭对话框的方案
Mac上关闭对话框的惯用操作是按Escape键,但在SwiftUI Mac Catalyst应用中,尝试多种keyboardShortcut配置都无法实现该功能。测试环境为XCode 15.0.1、macOS 13.6、最低iOS部署版本16.0,示例代码如下:
import SwiftUI struct ContentView: View { @State private var isSheetPresented = false var body: some View { VStack { Button("Show Sheet") { isSheetPresented.toggle() } .sheet(isPresented: $isSheetPresented) { SheetView(dismissAction: { isSheetPresented = false }) } } } } struct SheetView: View { var dismissAction: () -> Void var body: some View { VStack { Text("Hello") Button("Dismiss") { dismissAction() } } .keyboardShortcut("e", modifiers: .command) // works .keyboardShortcut(.escape, modifiers: .command) // does not work .keyboardShortcut("e",modifiers: []) // does not work .keyboardShortcut(.escape, modifiers: []) // does not work .keyboardShortcut(.cancelAction) // escape, command w and command period do not work } } @main struct testkeyboardApp: App { var body: some Scene { WindowGroup { ContentView() } } }
解决方案
方案一:使用原生环境变量绑定快捷键
替换自定义的dismissAction为SwiftUI内置的Environment(\.dismiss),直接为Sheet根视图绑定Escape键触发关闭操作:
import SwiftUI struct ContentView: View { @State private var isSheetPresented = false var body: some View { VStack { Button("Show Sheet") { isSheetPresented.toggle() } .sheet(isPresented: $isSheetPresented) { SheetView() } } } } struct SheetView: View { @Environment(\.dismiss) private var dismiss var body: some View { VStack { Text("Hello") Button("Dismiss") { dismiss() } } .keyboardShortcut(.escape, action: dismiss) } } @main struct testkeyboardApp: App { var body: some Scene { WindowGroup { ContentView() } } }
方案二:UIKit事件拦截(复杂场景适配)
如果方案一无效,可通过UIKit的事件处理直接拦截Escape键,确保在Catalyst环境下响应:
import SwiftUI import UIKit // 自定义视图容器,拦截键盘事件 struct EscapeHandlerView<Content: View>: UIViewControllerRepresentable { let content: Content let onEscape: () -> Void func makeUIViewController(context: Context) -> UIHostingController<Content> { let controller = UIHostingController(rootView: content) controller.becomeFirstResponder() return controller } func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: Context) { uiViewController.rootView = content } func makeCoordinator() -> Coordinator { Coordinator(onEscape: onEscape) } class Coordinator: NSObject, UIKeyInput { let onEscape: () -> Void init(onEscape: @escaping () -> Void) { self.onEscape = onEscape super.init() } var hasText: Bool { false } func insertText(_ text: String) {} func deleteBackward() {} override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) { guard presses.first?.key.keyCode == .escape else { super.pressesBegan(presses, with: event) return } onEscape() } } } // 使用示例 struct SheetView: View { @Environment(\.dismiss) private var dismiss var body: some View { EscapeHandlerView(onEscape: dismiss) { VStack { Text("Hello") Button("Dismiss") { dismiss() } } } } }
方案一利用SwiftUI原生能力实现,代码简洁且适配多数场景;方案二通过UIKit底层事件处理,适合原生快捷键绑定失效的复杂交互场景。
内容的提问来源于stack exchange,提问作者John Cashew
相关产品推荐
相关产品推荐

