iosswiftuitoggletextfieldpicker

Visibility of TextField label compared to Picker or Toggle in SwiftUI


If I place a Picker or a Toggle in a SwiftUI Form, the associated label is always visible:

enter image description here

But for a TextField, the label is only shown while the field is empty:

enter image description here

And as soon as I start to fill in the TextField, the label disappears.

enter image description here

So now I have no context for what the TextField is meant for.

This would not be a problem if the TextField content is self explanatory, but I have a form with multiple TextFields used to input numerical values, and once they are filled in I have no context for differentiating between each number.

Is there any way to make a SwiftUI TextField behave like a Picker or Toggle, and have the label permanently displayed?


Update:

After trying out Benzy's answer of using LabeledContent, I get this sort of result when I have multiple fields. As you can see the alignment of the TextFields themselves depends on the length of the text in the LabeledContent. I'd prefer to align the TextFields.

enter image description here


Solution

  • Try wrapping the TextField as LabeledContent:

    Form {
        Section("Creator") {
            LabeledContent("Name") {
                TextField("Name", text: $creator, prompt: Text("Name"))
            }
        }
    }
    

    Screenshot Screenshot


    You might prefer to supply an empty prompt and then add styling to the label, for example, to style it like a prompt:

    LabeledContent {
        TextField("Name", text: $creator, prompt: Text(""))
    } label: {
        Text("Name")
            .foregroundStyle(.secondary)
    }
    

    Screenshot Screenshot


    EDIT You were asking in a comment, how to align multiple text fields.

    A simple way would be to set a fixed width on the labels. However, if you want the labels to be as wide as the widest label, you could use a ZStack to determine the footprint for the labels:

    private let allLabels = ["Street", "City", "State", "Country"]
    
    private var labelFootprint: some View {
        ZStack {
            ForEach(allLabels, id: \.self) { label in
                Text(LocalizedStringKey(label))
            }
        }
        .hidden()
    }
    

    The visible labels can then be shown as an overlay over the footprint. For example:

    LabeledContent {
        TextField("Street", text: $street)
    } label: {
        labelFootprint.overlay(alignment: .leading) {
            Text("Street")
        }
    }
    

    Screenshot