I'm having a issue with a simple SwiftUI view containing a TextField
.
If the View
is inside a SwiftUI App > Scene > WindowGroup
, the TextField
in the view behave as expected and I can use shortcuts inside the TextField
like Command+a
to select all the text in it or copy/past
.
If the View
is inside a NSHostingView
set as the contentView
of a NSWindow
created programmatically, my TextField
doesn't accept shortcuts using the Command
key. I can still type text or use shortcuts with CTRL
but with Command
the bell ring and nothing happens.
Here is the code for the working version:
import Foundation
import SwiftUI
struct MyView: View {
@State var text = ""
var body: some View {
TextField("My textfield", text: $text).padding()
}
}
@main
struct TestCoreDataSwiftApp: App {
var body: some Scene {
WindowGroup {
MyView()
}
}
}
Here is the code for the not working version:
import Foundation
import Cocoa
import SwiftUI
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
struct MyView: View {
@State var text = ""
var body: some View {
TextField("My textfield", text: $text).padding()
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
private var window: NSWindow?
func applicationDidFinishLaunching(_ aNotification: Notification) {
window = NSWindow()
window?.contentView = NSHostingView(rootView: MyView())
window?.makeKeyAndOrderFront(self)
window?.makeFirstResponder(nil)
}
}
[EDIT]
I did more tests.
If everything is created with Interface Builder (drag and drop a NSTextField
inside a NSView
), it also works as expected.
If we create a NSView
programmatically and add a NSTextField
, here again it doesn't work:
// Not working code
func applicationDidFinishLaunching(_ aNotification: Notification)
{
NSApplication.shared.activate(ignoringOtherApps: true)
window = NSWindow(contentRect: NSRect(x: 400, y: 400, width: 400, height: 100), styleMask: [.miniaturizable, .closable, .resizable, .titled], backing: .buffered, defer: false)
window.center()
window.title = "Title"
window.contentView = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 300))
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 60))
window.contentView?.addSubview(textField)
window.makeKeyAndOrderFront(nil)
}
I suspect it's something related to the focus or the firstResponder
but I'm unable to make it work. Any help is welcome. Thanks!
In fact, those shortcuts are related to the application menu (NSMenu
). They don't exist by default without it.
So, if the application doesn't have the Edit
menu where all this shortcuts are defined, they are simply not available in the TextField
.
In the various cases where it works, it's because both the default IB project and the Swift UI project add this menu by default.
It's not a matter of firstResponder
in this case.
In order to make this work, the solutions are:
Edit
menu in the application with the shortcuts (doesn't work if the menu is hidden).class NSHostingViewShortcuts<Content>: NSHostingView<Content> where Content : View {
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if event.modifierFlags.contains(.command) {
let shortcuts = [
"a": #selector(NSStandardKeyBindingResponding.selectAll(_:)),
"x": #selector(NSText.cut(_:)),
"c": #selector(NSText.copy(_:)),
"v": #selector(NSText.paste(_:))
]
if event.characters != nil && shortcuts[event.characters!] != nil {
NSApp.sendAction(shortcuts[event.characters!]!, to: nil, from: nil)
return true
}
}
return super.performKeyEquivalent(with: event)
}
}