用于复制、粘贴和剪切的 macOS SwiftUI TextEditor 键盘快捷键

Roh*_*han 4 macos swift swiftui macos-big-sur

我正在 SwiftUI 中为 macOS 菜单/状态栏制作一个应用程序,单击该应用程序时,会打开一个NSPopover. 该应用程序以(Big Sur 中的新功能)为中心TextEditor,但这TextEditor似乎并没有响应用于复制、粘贴和剪切的典型 Cmd + C/V/X 键盘快捷键。我知道TextEditors确实支持这些快捷方式,因为如果我在 XCode 中启动一个新项目并且不将其放入NSPopover(例如,我只是将其放入常规 Mac 应用程序中),它就可以工作。复制/粘贴/剪切选项仍然出现在右键菜单中,但我不确定为什么我不能使用键盘快捷键在NSPopover.

我相信这与以下事实有关:当您单击打开弹出窗口时,macOS 不会“聚焦”该应用程序。通常,当您打开应用程序时,您会在 Mac 菜单栏的左上角(Apple 徽标旁边)看到应用程序名称和相关菜单选项。我的应用程序不这样做(大概是因为它是一个弹出窗口)。

这是相关代码:

ContentView.swift 中的文本编辑器:

TextEditor(text: $userData.note)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .padding(10)
                    .font(.body)
                    .background(Color(red: 30 / 255, green: 30 / 255, blue: 30 / 255))
Run Code Online (Sandbox Code Playgroud)

NotedApp.swift 中的 NSPopover 逻辑:

@main
struct MenuBarPopoverApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        Settings{
            EmptyView()
        }
    }
}
class AppDelegate: NSObject, NSApplicationDelegate {
    var popover = NSPopover.init()
    var statusBarItem: NSStatusItem?

    func applicationDidFinishLaunching(_ notification: Notification) {
        
        let contentView = ContentView()

        popover.behavior = .transient
        popover.animates = false
        popover.contentViewController = NSViewController()
        popover.contentViewController?.view = NSHostingView(rootView: contentView)
        statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        statusBarItem?.button?.title = "Noted"
        statusBarItem?.button?.action = #selector(AppDelegate.togglePopover(_:))
    }
    @objc func showPopover(_ sender: AnyObject?) {
        if let button = statusBarItem?.button {
            popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
        }
    }
    @objc func closePopover(_ sender: AnyObject?) {
        popover.performClose(sender)
    }
    @objc func togglePopover(_ sender: AnyObject?) {
        if popover.isShown {
            closePopover(sender)
        } else {
            showPopover(sender)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在 GitHub 存储库中找到整个应用程序: https: //github.com/R-Taneja/Noted

Col*_*rts 6

我在构建 SwiftUI macOS 弹出应用程序时偶然发现了这个问题(和解决方案),虽然当前的解决方案可用,但它存在一些缺点。最大的担忧是需要让我们ContentView意识到并响应编辑操作,这可能无法很好地适应嵌套视图和复杂的导航。

我的解决方案依赖于链并使用NSApplication.sendAction(_:to:from:)NSResponder发送目标。支持和对象都利用,并且当这些对象中的任何一个是第一响应者时,消息被传递给它们。nilNSTextViewNSTextFieldNSText

我已经确认以下内容适用于复杂的层次结构,并提供NSText.

菜单示例

@main
struct MyApp: App {
var body: some Scene {
    Settings {
        EmptyView()
    }
    .commands {
        CommandMenu("Edit") {
            Section {

                // MARK: - `Select All` -
                Button("Select All") {
                    NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil)
                }
                .keyboardShortcut(.a)
                
                // MARK: - `Cut` -
                Button("Cut") {
                    NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: nil)
                }
                .keyboardShortcut(.x)
                
                // MARK: - `Copy` -
                Button("Copy") {
                    NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil)
                }
                .keyboardShortcut(.c)
                
                // MARK: - `Paste` -
                Button("Paste") {
                    NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: nil)
                }
                .keyboardShortcut(.v)
            }
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

键盘事件修饰符(非必需)

这只是一个便利修饰符,对于实现有效的解决方案来说并不是必需的。

// MARK: - `Modifiers` -

fileprivate struct KeyboardEventModifier: ViewModifier {
    enum Key: String {
        case a, c, v, x
    }
    
    let key: Key
    let modifiers: EventModifiers
    
    func body(content: Content) -> some View {
        content.keyboardShortcut(KeyEquivalent(Character(key.rawValue)), modifiers: modifiers)
    }
}

extension View {
    fileprivate func keyboardShortcut(_ key: KeyboardEventModifier.Key, modifiers: EventModifiers = .command) -> some View {
        modifier(KeyboardEventModifier(key: key, modifiers: modifiers))
    }
}
Run Code Online (Sandbox Code Playgroud)

希望这有助于为其他人解决这个问题!

在 macOS Monterrey 12.1 上使用 SwiftUI 2.0 进行测试