Learn Courses My Dashboard

TextField doesn't show text until it's selected - new Xcode

This didn’t happen before I upgraded to Xcode 13. One of the TextFields in my view doesn’t show the text that’s in it until I select it. It’s there, you just can’t see it. The reason I know it’s there is that, if it weren’t, the Save button in the navigation bar would be disabled, but it’s not.

Here’s the code:

  var body: some View
  {
    Form
    {
      TextField("Name", text: $name)
        .modifier(TextFieldClearButton(text: $name))
        .autocapitalization(.words)

      if duplicateItem
      {
        Text("This name matches a different item.")
          .foregroundColor(.red)
          .font(.callout)
      }

      // Allow all dates, including earlier than today, in case the
      // item being edited has already passed its due date.
      DatePicker("Due Date:",
                 selection: $dueDate,
                 displayedComponents: .date)
        .accentColor(.blue)

      HStack
      {
        Spacer()

        Button("Today")
        { dueDate = Date() }
        .buttonStyle(DateButtonStyle())

        Spacer()

        Button("+ 1 month")
        { dueDate = Calendar.current.date(byAdding: .month, value: 1, to: dueDate )! }
        .buttonStyle(DateButtonStyle())

        Spacer()

        Button("+ 1 year")
        { dueDate = Calendar.current.date(byAdding: .year, value: 1, to: dueDate )! }
        .buttonStyle(DateButtonStyle())

        Spacer()
      }

      HStack
      {
        Text("Price: $")
        TextField("", text: $price)
          .keyboardType(.decimalPad)
          .modifier(TextFieldClearButton(text: $price))
      }
      
      if !priceIsValid
      {
        Text("If you include a price, it must be a valid number.")
          .foregroundColor(.red)
          .font(.callout)
      }

      Section(header: Text("Notes").foregroundColor(colorContrast))
      {
        TextEditor(text: $notes)
          .frame(height: 100)
      }
    }
    .navigationBarBackButtonHidden(true)
    .navigationBarTitle("Edit item", displayMode: .inline)
    .navigationBarItems(
      leading: Button("Cancel")
      { presentationMode.wrappedValue.dismiss() }
      .foregroundColor(.blue),                           // *TEMP FIX*
      trailing: Button("Save")
      { saveItem() }
      .disabled(!formIsComplete)
      .foregroundColor(formIsComplete ? .blue : .gray))  // *TEMP FIX*
    .onAppear() { setup() }
  }

It’s the first TextField that’s having the problem. All the other fields work fine, including the Price TextField.

I’ve tried putting the code inside a Section, adding an EmptyView above the field, adding a Text above the field, and deleting the modifiers.

Anyone else experiencing this?

have you tried adding a foreground color?

Thanks for the suggestion. The text appears when I tap inside the field, so the foreground color is visible.

This problem didn’t exist with Xcode 12, so I suspect an Xcode bug.

ah i see so the foreground is always active now? maybe try removing it then or try doing a backgroundColor instead

Maybe if I approach this from another angle.

Is there a way to programmatically select a TextField when it appears, or when the containing view appears?

You might be able to glean something from this article that may help.

Thanks, but this doesn’t look like the solution. The article specifically recommends not to use .focusable() on a TextField. In any case, I want to apply focus, not just make it focusable, which it already is.

I would like to report this to Apple, but my Feedback app disappeared with IOS15 and I can’t find it in the App Store.

I guess nobody reading this has seen this problem.

The other alternative is to create a UIViewRepresentable custom TextField in which you can make use of UIKit to set that field as the firstResponder(). The code below is an example of how to do that.

It creates a textField that mirrors the way a SwiftUI textfield looks with a style of RoundedBorderTextFieldStyle() including giving you an option to have placeholder text.

If you create a test project and replace the contents of ContentView with all of this code you can see how it works.

struct ContentView: View {
    @State private var email = ""
    @State private var password = ""

    var body: some View {
        VStack(spacing: 20) {
            TextField("Email", text: $email)
                .textFieldStyle(RoundedBorderTextFieldStyle())
            CustomTextField(text: $password, placeHolderText: "Password")
                .padding(5)
                .frame(height: 40)
                .overlay(RoundedRectangle(cornerRadius: 5)
                            .stroke(Color.gray.opacity(0.5), lineWidth: 1)
                )
        }
        .padding(.horizontal)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


struct CustomTextField: UIViewRepresentable {

    @Binding var text: String
    var isFirstResponder: Bool = false
    var placeHolderText = ""

    func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.delegate = context.coordinator
        textField.placeholder = placeHolderText
        textField.becomeFirstResponder()
        return textField
    }

    func makeCoordinator() -> CustomTextField.Coordinator {
        return Coordinator(text: $text)
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
        uiView.text = text
        if isFirstResponder && !context.coordinator.didBecomeFirstResponder  {
            uiView.becomeFirstResponder()
            context.coordinator.didBecomeFirstResponder = true
        }
    }

    class Coordinator: NSObject, UITextFieldDelegate {

        @Binding var text: String
        var didBecomeFirstResponder = false

        init(text: Binding<String>) {
            _text = text
        }

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

    }
}

Note, the fact that you cannot use a setting to make the field automatically focused as you navigate to the View is not a bug. The SwiftUI developers have not made that a priority. I suspect that at some point there will be a modifier to enable you to do that.

That’s not what I’m calling a bug.

When the view opens, the TextField is blank, even though the @State field is not empty. Tapping inside the field causes the text to appear. Before Xcode 13, the text appeared when the view opened.

That’s the bug.

How is the textfield populated when the view is presented? Is it via a passed in parameter or via an .onAppear() ?

Problem solved. I put all the fields except Notes inside a Vstack and now the text displays as it did with Xcode 12. I guess it should have been in a Vstack all along, but that never caused a problem until now.

I don’t know why that solved the problem, but oh well.

Thanks for everyone’s suggestions.

Another thing I noticed was that, after putting the fields inside a Vstack, the dividers between the fields were no longer there. I had to get them back with “Divider()”.