magnuskahr

writing code

SwiftUI: Trailing label TextField

Having a trailing label on a textfield can enforce the relationship with the value, such as a trailing domain for an email registration or the currency for an amount input. In this post, we will go over some SwiftUI layout manoeuvres to have a trailing label appended after the value of the textfield.

Lets dive in! First let handle the layout of the label:

struct TrailingLabelTextField: View {

	let placeholder: String
	@Binding var value: String
	let label: String

	var body: some View {
		TextField(placeholder, text: $value)
            .textFieldStyle(.plain)
            .background(alignment: .trailing) {
                HStack(spacing: 0) {
                    Text(value.isEmpty ? placeholder : value).hidden()
                    Text(label)
                }
                .fixedSize()
                .frame(maxWidth: .infinity, alignment: .leading)
            }
	}
}

First off, we have a view that takes three inputs: a placeholder, a binding to a value, and a label. The interesting thing comes when we look at the body of the view. A textfield is bound to the default style, which enforces the same layout sizing as a Text view. Then in the background, we add two text views aligned to the trailing edge. The first has the current value or placeholder of the text field, but it is hidden while still taking up space in the layout. After that, we have a text view with the label. These text views are told to maintain their fixed size and have an infinite max width aligned to the leading edge.

Now, the idea is that the views grow from the trailing edge, and with their fixed size, overflow to the leading edge. This gives the impression that the label is horizontally aligned with the value of the text field.

With the layout of the label in place, we still need to take care of the textfield's layout. Currently, if the textfield overflows, the value will appear in front of the label because the textfield doesn't consider the label's layout, unlike how the label considers the textfield's value layout.

var body: some View {
	HStack(spacing: 0) {
		TextField(placeholder, text: $value)
            .textFieldStyle(.plain)
		Text(verbatim: " ")
		Text(label).hidden()
	}
	.background(alignment: .trailing) {
		HStack(spacing: 0) {
			Text(value.isEmpty ? placeholder : value).hidden()
			Text(verbatim: " ")
			Text(label)
		}
		.fixedSize()      
		.frame(maxWidth: .infinity, alignment: .leading)
	}
}

With this new body, we use the same hidden layout trick: Add the text field and the label to a horizontal stack and then hide the label. Also, notice that an extra text view with a whitespace is added. This step is optional but ensures space between the textfield value and the label.

Conclusion

With these layout tricks, we have created a wrapped textfield that adds a perfectly horizontally placed label after the text field value. This can easily be adjusted to your needs and used for a wide range of inputs.

Please note that we haven't considered the accessibility of the text field, but that is a topic for another time.