I'm attempting to set a textfield as first responder using its tag property and a @Binding. As I am unable to access the underlying UITextField from a SwiftUI TextField and call .becomeFirstResponder() directly, I'm having to wrap a UITextField using UIViewRepresentable. The code below works but results in the following console message === AttributeGraph: cycle detected through attribute <#> ===.
It sounds like I have a memory leak and/or retain cycle, I've isolated the issue to the line textField.becomeFirstResponder() but having inspected Xcode's Memory Graph Hierarchy I can not see what is wrong?
Any help provided is be much appreciated.
struct CustomTextField: UIViewRepresentable {
var tag: Int
@Binding var selectedTag: Int
@Binding var text: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: ResponderTextField
init(_ textField: ResponderTextField) {
self.parent = textField
}
func textFieldDidChangeSelection(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.tag = tag
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ textField: UITextField, context: Context) {
if textField.tag == selectedTag, textField.window != nil, textField.isFirstResponder == false {
textField.becomeFirstResponder()
}
}
}
on a hunch, I put becomeFirstResponder() in an async dispatch. This fixes the warnings. I'm guessing there is some kind of creation loop which happens when the view is created, and you call becomeFirstResponder(), so that calls the view, which triggers SwiftUI to find the view which it hasn't properly created yet (or something like that)
anyway - this works for me: