我正在尝试在文本框中处理换行符的shift + Enter(聊天应用程序例如WhatsApp,Discord等实现)。目前,我正在使用 SwiftUI TextEditor,但它没有任何方法可以像在 AppKit 中那样处理原始键盘事件。因此,一个黑客解决方案是检查消息的最后一个字符是否是 .onchange 中的换行符,然后发送消息。这种方法适用于“输入发送”,但如果按下 Shift 键(对于多行消息),我找不到一种不发送消息的方法。
我正在尝试使用
NSViewRepresentable
来使用 AppKit API 的方法,如下所示:
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) { }
}
然后我将其设置为文本编辑器的覆盖/背景(都尝试过),如下所示:
TextEditor(text: $message)
.overlay(KeyEventHandling())
...more modifiers
但是,它似乎只在文本编辑器未聚焦时处理事件。当 TextEditor(或任何其他元素,似乎)获得焦点时,按 Shift 键不会调用 flagsChanged 覆盖。
除了将整个 TextEditor 重新实现为
NSViewRepresentable
之外,是否有更好的方法来接收修饰键状态更改?谢谢!
新行的正确快捷键实际上是 option+return 并且
TextField
支持它,例如
TextField("type something...", text: $text, onEditingChanged: { _ in
print("changed")
}, onCommit: {
print("commit")
})
import SwiftUI
enum FocusField: Hashable {
case rowOne
case rowTwo
}
struct WrappedTextEdit: View {
@Binding var text: String
@State var didWrapText = false
// we need this to avoid loosing focus
var focusField: FocusField
var focusState: FocusState<FocusField?>.Binding
/**
https://www.avanderlee.com/swiftui/key-press-events-detection/
https://onmyway133.com/posts/how-to-pass-focusstate-binding-in-swiftui/
*/
var body: some View {
TextField("...", text: $text, axis: .vertical)
.onKeyPress(action: { (keyPress: KeyPress) in
print("""
characters: '\(keyPress.characters)', modifiers: '\(keyPress.modifiers)', phase: '\(keyPress.phase)', debugDescription: '\(keyPress.debugDescription)'
""")
if keyPress.key == .return && keyPress.modifiers == .shift {
print("shift + return")
text += "\n"
didWrapText = true
return .handled
}
if keyPress.key == .return {
return .ignored
}
// CharacterSet.alphanumerics.contains(keyPress.characters)
text += keyPress.characters
didWrapText = false
return .handled
})
.focused(focusState, equals: focusField)
.onChange(of: focusState.wrappedValue, { oldValue, newValue in
print("didWrapText: '\(didWrapText)'")
print("isFocused changed, '\(oldValue)' -> '\(newValue)'")
if didWrapText {
DispatchQueue.main.async {
focusState.wrappedValue = focusField
}
}
})
}
}
struct ContentView: View {
@State var row1: String = "row one"
@State var row2: String = "row two"
@FocusState var focusState: FocusField?
var body: some View {
VStack {
Spacer()
HStack {
WrappedTextEdit(text: $row1, focusField: .rowOne, focusState: $focusState)
.textFieldStyle(.roundedBorder)
.padding(5)
Spacer()
}
HStack {
WrappedTextEdit(text: $row2, focusField: .rowTwo, focusState: $focusState)
.textFieldStyle(.roundedBorder)
.padding(5)
Spacer()
}
Spacer()
}
.onAppear(perform: {
focusState = .rowOne
})
.padding()
}
}
#Preview {
ContentView()
}