Cycle detected through use of UIKit Textfield Representable in SwiftUI
Asked Answered
C

1

0

I am using a boolean (searchVM.showSearchView: Bool) to simultaneously turn opacity of a SearchView to 1.0 AND show/hide a UIRepresentableKeyboard.

This gives me the same functionality as Google Maps when you tap on the search bar and (simultaneously) the white search view appears, textfield focused, and keyboard showing.

However I am getting the below message print twice every time I set the showSearchView to true.

=== AttributeGraph: cycle detected through attribute 320536 ===

=== AttributeGraph: cycle detected through attribute 318224 ===

Why am I getting this message? Is it the passing around of my searchVM?

SearchViewModel

class SearchViewModel: ObservableObject {

    var text: String = ""    

    @Published var showSearchView: Bool = false 
 
}

Textfield & Cancel button

struct SearchBar: View {
    @ObservedObject var searchVM: SearchViewModel
    @State var text: String = ""
   
    var body: some View {
        HStack {
            FirstResponderTextfield(searchVM: searchVM, text: $text, placeholder: "Search")
            Text("Cancel")
                .onTapGesture {
                    searchVM.showSearchView.toggle()
                }
        }
    }
}

FirstResponderTextField

struct FirstResponderTextfield: UIViewRepresentable {
    
    @ObservedObject var searchVM : SearchViewModel

    @Binding var text: String
    let placeholder: String

    // COORDINATOR
    class TextFieldCoordinator: NSObject, UITextFieldDelegate {

        var control: FirstResponderTextfield
        
        @Binding var text: String // unused but required

        func textFieldDidChangeSelection(_ textField: UITextField) {
           control.searchVM.text = textField.text ?? ""
          
        }

        func textFieldDidEndEditing(_ textField: UITextField) {
            
        }

        init(control: FirstResponderTextfield, text: Binding<String>) {
            self.control = control
            self._text = text
        }
    }

    func makeCoordinator() -> TextFieldCoordinator {
        return TextFieldCoordinator(control: self, text: $text)
    }

    func makeUIView(context: Context) -> some UIView {
        let textField = UITextField()
        textField.delegate = context.coordinator
        textField.placeholder = placeholder
        return textField
    }

    func updateUIView(_ uiView: UIViewType, context: Context) {
        if searchVM.showSearchView {
            uiView.becomeFirstResponder() // show keyboard
        } else {
            uiView.resignFirstResponder() // dismiss keyboard
        }
    }
}

Casias answered 5/12, 2021 at 21:32 Comment(1)
This might be of some use to you #62870086Bearden
C
1

I fixed this by adding DispatchQueue.asyncAfter( .now() + 0.05). Probably a mickey-mouse way of doing things but it works!

 func updateUIView(_ uiView: UIViewType, context: Context) {
        // here we check to see if our textfield has become first responder
        if searchVM.showSearchView {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                uiView.becomeFirstResponder() // show keyboard
            }
           
        } else {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                uiView.resignFirstResponder() // dismiss keyboard
            }
        }
    }
Casias answered 5/12, 2021 at 21:50 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.