I am trying to build a SwiftUI app in which a user can tab through several textfields laid out in a grid. The default behavior with the SwiftUI TextField does not work for me (I want the focus to proceed from top-to-bottom, rather than left-to-right) so I am trying to make a custom textfield by making NSTextField conform to NSViewRepresentable.
The code below displays in my application just fine, and the coordinator is initialized properly (it prints to the console), but none of the coordinator methods are ever called (they don't print anything). Am I doing something wrong? How do I fix this?
import SwiftUI
struct OrderedTextField: NSViewRepresentable {
@Binding var text: String
@Binding var selectedField: Int
var tag: Int
func makeNSView(context: NSViewRepresentableContext<OrderedTextField>) -> NSTextField {
let textField = NSTextField()
textField.delegate = context.coordinator
textField.tag = tag
textField.placeholderString = ""
return textField
}
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
func updateNSView(_ nsView: NSTextField, context: NSViewRepresentableContext<OrderedTextField>) {
nsView.delegate = context.coordinator
context.coordinator.newSelection = { newSelection in
DispatchQueue.main.async {
self.selectedField = newSelection
}
}
if nsView.tag == self.selectedField {
nsView.becomeFirstResponder()
}
}
}
extension OrderedTextField {
class Coordinator: NSObject, NSTextFieldDelegate {
@Binding var text: String
var newSelection: (Int) -> () = { _ in }
init(text: Binding<String>) {
print("Initializing!")
_text = text
}
func textShouldBeginEditing(_ textObject: NSText) -> Bool {
print("Should begin editing!")
return true
}
func textDidBeginEditing(_ notification: Notification) {
print("Began editing")
}
func textDidChange(_ notification: Notification) {
print("textDidChange")
}
func textShouldEndEditing(_ textObject: NSText) -> Bool {
print("should end editing")
return true
}
func textDidEndEditing(_ notification: Notification) {
print("did end editing")
}
}
}
As far as I checked, your Coordinator
does not have any methods defined in NSTextFieldDelegate
.
You should better try something like this:
extension OrderedTextField {
class Coordinator: NSObject, NSTextFieldDelegate {
@Binding var text: String
var newSelection: (Int) -> () = { _ in }
init(text: Binding<String>) {
print("Initializing!")
_text = text
}
func textField(_ textField: NSTextField, textView: NSTextView, candidatesForSelectedRange selectedRange: NSRange) -> [Any]? {
print(#function)
return nil
}
func textField(_ textField: NSTextField, textView: NSTextView, candidates: [NSTextCheckingResult], forSelectedRange selectedRange: NSRange) -> [NSTextCheckingResult] {
print(#function)
return candidates
}
func textField(_ textField: NSTextField, textView: NSTextView, shouldSelectCandidateAt index: Int) -> Bool {
print(#function)
return true
}
func controlTextDidBeginEditing(_ obj: Notification) {
print(#function)
}
func controlTextDidEndEditing(_ obj: Notification) {
print(#function)
}
func controlTextDidChange(_ obj: Notification) {
print(#function)
}
func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool {
print(#function)
return true
}
func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool {
print(#function)
return true
}
//...
}
}
Or you may try subclassing NSTextField
and override methods like textShouldBeginEditing(_:)
, textDidBeginEditing(_:)
, textDidChange(_:)
... in the subclass.