I'm trying to handle shift+enter for newline in a text box (that chat apps e.g. WhatsApp, Discord etc. implement). Currently, I'm using a SwiftUI TextEditor, but that doesn't have any way to handle raw keyboard events like you can do in AppKit. So, a hackish solution is to check if the last character of the message is a newline in .onchange, then send the message. This approach works for "enter to send", but I can't find a way to not send the message if the shift key is pressed (for multiline messages).
I was trying out an approach using NSViewRepresentable
to use AppKit APIs as follows:
struct KeyEventHandling: NSViewRepresentable {
class KeyView: NSView {
override var acceptsFirstResponder: Bool { true }
override func keyDown(with event: NSEvent) {
print("keydown event")
}
override func flagsChanged(with event: NSEvent) {
switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [.shift]:
print("shift key pressed")
default:
print("no modifier keys are pressed")
}
}
}
func makeNSView(context: Context) -> NSView {
let view = KeyView()
DispatchQueue.main.async { // wait till next event cycle
view.window?.makeFirstResponder(view)
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) { }
}
I then set it as a overlay/background (tried both) of the TextEditor like so:
TextEditor(text: $message)
.overlay(KeyEventHandling())
...more modifiers
However, it only seems to handle the events while the TextEditor isn't focused. When the TextEditor (or any other element, it seems) is focused, pressing shift doesn't call the flagsChanged override.
Is there a better approach, short of reimplementing the whole TextEditor as a NSViewRepresentable
, to receive modifier key state changes? Thanks!
The correct shortcut for new line is actually option+return and TextField
supports it, e.g.
TextField("type something...", text: $text, onEditingChanged: { _ in
print("changed")
}, onCommit: {
print("commit")
})