'Updating state variable does not update TextField's text

I have TextField in a form which should be updated dynamically, on a specific user action. Since I'm embedding this SwiftUI view inside a UIKit view, I had to customize it a bit:

/// Base TextField
struct CustomTextFieldSUI: View {
    @State var text: String = ""
    var placeholder: Text
    var disableAutoCorrect = false
    var isSecure: Bool = false
    var onEditingChanged: (Bool) -> () = { _ in }
    var onCommit: () -> () = { }
    var onChange: (String) -> Void
    var keyboardType: UIKeyboardType = .default
    var contentType: UITextContentType?
    
    var body: some View {
        
        ZStack(alignment: .leading) {
            if text.isEmpty {
                placeholder
                    .foregroundColor(placeholderColor)
            }
            if isSecure {
                SecureField("", text: $text.onChange({ newText in
                    onChange(newText)
                }), onCommit: onCommit)
                    .foregroundColor(foregroundColor)

                   
            } else {
                TextField("", text: $text.onChange({ newText in
                    onChange(newText)
                }), onEditingChanged: onEditingChanged, onCommit: onCommit)
                    .foregroundColor(foregroundColor)
                    .disableAutocorrection(disableAutoCorrect)
                    .keyboardType(keyboardType)
                    .textContentType(contentType)
                    .autocapitalization(keyboardType == UIKeyboardType.emailAddress ? .none : .words)
            }
           
        }
    }
    
}

Which itself is used to create another custom view:

struct TextFieldWithIconSUI: View {
    @State var text: String = ""
    @State var isSecure: Bool

    let icon: Image
    let placeholder: String
    var prompt: String? = nil
    let disableAutoCorrect: Bool = false
    var keyboardType: UIKeyboardType = .default
    var contentType: UITextContentType?
    var onEditingChanged: (Bool) -> () = { _ in }
    var onCommit: () -> () = { }
    var onChange: (String) -> Void

    var body: some View {
        VStack {
            ZStack {
                RoundedRectangle(cornerRadius: 0)
                                    HStack(alignment: .center, spacing: iconSpacing) {
                    CustomTextFieldSUI(
                        text: text,
                        placeholder: Text(placeholder),
                        placeholderColor: .gray,
                        foregroundColor: .white,
                        disableAutoCorrect: disableAutoCorrect,
                        isSecure: isSecure, onEditingChanged: onEditingChanged, onCommit: onCommit, onChange: { newText in
                            text = newText
                            onChange(newText)
                        },
                        keyboardType: keyboardType,
                        contentType: contentType)
                    
                    icon
                        .scaledToFit()
                        .onTapGesture {
                            isSecure.toggle()
                        }
                      
                        
                }
                
            }
            
            if (prompt != nil) {
                VStack(alignment: .leading) {
                    Text(prompt!)
                        .padding(.leading, 10)
                        .padding(.top, -2)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .lineLimit(2)
                }
            }
        }
    }
}

Now in the client view I'm using it with a list to create a simple auto-complete list:

TextFieldWithIconSUI(text: displayCountryName, isSecure: false, icon: emptyImage, placeholder: "Country", keyboardType: .default, contentType: .countryName, onEditingChanged: { changed in
    self.shouldShowCountriesList = changed
}, onChange: { text in
    self.displayCountryName = text
    self.shouldShowCountriesList = true
})

And somewhere in my form, I'm using the text from that TextField to show the autocorrect list. The list is shown but when I select an item it does not update the TextField's text, in debugger the state variable is updated, but the UI is not showing the new value.

if shouldShowCountriesList {
    VStack(alignment: .leading) {
        List {
            ForEach(countriesList.filter { $0.lowercased().hasPrefix(country.object.lowercased()) }.prefix(3), id: \.self) { countryName in
                Text(countryName)
                        .onTapGesture {
                            self.displayCountryName = countryName
                            self.shouldShowCountriesList = false
                        }
                
            }
        }
    }
}


Solution 1:[1]

I had to use Binding variables for my custom text field:

struct CustomTextFieldSUI: View {
    var text: Binding<String>
    var placeholder: Text
    var placeholderColor: Color
    var foregroundColor: Color
    var disableAutoCorrect = false
    var isSecure: Bool = false
    var onEditingChanged: (Bool) -> () = { _ in }
    var onCommit: () -> () = { }
    var onChange: (String) -> Void
    var keyboardType: UIKeyboardType = .default
    var contentType: UITextContentType?
    
    var body: some View {
        let bindingText = Binding(
            get: {
                self.text.wrappedValue
                
            },
            set: {
                self.text.wrappedValue = $0
            onChange($0)
        })
        ZStack(alignment: .leading) {
            if text.wrappedValue.isEmpty {
                placeholder
                    .foregroundColor(placeholderColor)
            }

            
            if isSecure {
                SecureField("", text: bindingText, onCommit: onCommit)
                    .foregroundColor(foregroundColor)

                   
            } else {
                TextField("", text: bindingText, onEditingChanged: onEditingChanged, onCommit: onCommit)
                    .foregroundColor(foregroundColor)
                    .disableAutocorrection(disableAutoCorrect)
                    .keyboardType(keyboardType)
                    .textContentType(contentType)
                    .autocapitalization(keyboardType == UIKeyboardType.emailAddress ? .none : .words)
                
            }
           
        }
    }
    
}

And passed the binding variables from the host.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Maysam