How to use TextField in SwiftUI | SwiftUI Bootcamp #35
Almost every app has a place where users type something: a search bar, a username field, a tag input. This lesson builds a complete text input flow — a styled TextField, a validation function that enables or disables the save button, and a list that grows as entries are added.
What You'll Learn
- How
TextFieldbinds to a@Statestring and updates live as the user types - How to validate input and use the result to enable/disable a button and change its appearance
- How to clear the text field after saving, and how to grow a list from user input
Mental Model
Think of TextField like a whiteboard that two people share. The @State var textFieldText is the surface of the whiteboard — it holds the current writing. TextField is one person writing on it. Your Text(data) views in the list are another person reading what was written. When the whiteboard changes (the user types), everyone looking at it sees the update immediately.
The $textFieldText binding is the shared reference to the whiteboard — not a copy, but a live connection. The $ means "I want to write to this whiteboard", not just read from it.
Detailed Explanation
TextField("placeholder", text: $textFieldText) creates an editable text field that is two-way bound to textFieldText. Every keystroke updates the string in real time. This means you can run validation (textIsAppropriate()) on every character change just by calling it from within body.
Validation-driven UI is a core SwiftUI pattern. Instead of checking on submit, you evaluate the validation function as a computed expression inside modifiers. .background(textIsAppropriate() ? Color.blue : Color.gray) gives instant feedback — the button turns blue the moment the text becomes valid. .disabled(!textIsAppropriate()) prevents submission of invalid input at the interaction level, not just the visual level.
saveText() appends textFieldText to dataArray and then resets textFieldText = "". Setting it back to empty clears the TextField because of the two-way binding. The new entry immediately appears at the bottom of the ForEach list because dataArray is a @State array.
The commented-out .textFieldStyle(RoundedBorderTextFieldStyle()) shows SwiftUI's built-in styling. It's a quick option but offers less customization than the manual padding + background + cornerRadius approach used in the active code.
Code Structure
TextfieldBootcamp.swift demonstrates a complete input-save-display pattern. The TextField is the input. textIsAppropriate() is the validation gate used in two places (button color and .disabled). saveText() handles the submission. ForEach(dataArray) renders all saved entries below the input.
Complete Code
TextfieldBootcamp.swift
import SwiftUI
struct TextfieldBootcamp: View {
@State var textFieldText: String = "" // live text binding — updates with every keystroke
@State var dataArray: [String] = [] // stores all saved entries
var body: some View {
NavigationView {
VStack {
TextField("Type something here...", text: $textFieldText) // placeholder text + two-way binding
//.textFieldStyle(RoundedBorderTextFieldStyle()) // built-in style — simpler but less customizable
.padding()
.background(Color.gray.opacity(0.3).cornerRadius(10)) // custom styled background
.foregroundColor(.red) // text color while typing
.font(.headline)
Button(action: {
if textIsAppropriate() { // guard: only saves if input meets requirements
saveText()
}
}, label: {
Text("Save".uppercased())
.padding()
.frame(maxWidth: .infinity) // full-width button
.background(textIsAppropriate() ? Color.blue : Color.gray) // visual validation feedback
.cornerRadius(10)
.foregroundColor(.white)
.font(.headline)
})
.disabled(!textIsAppropriate()) // disables tap interaction when input is invalid
ForEach(dataArray, id: \.self) { data in // renders each saved entry as a text row
Text(data)
}
Spacer()
}
.padding()
.navigationTitle("TextField Bootcamp!")
}
}
func textIsAppropriate() -> Bool {
// check text
if textFieldText.count >= 3 { // minimum 3 characters required
return true
}
return false
}
func saveText() {
dataArray.append(textFieldText) // adds current text to the list
textFieldText = "" // clears the TextField via the binding
}
}
struct TextfieldBootcamp_Previews: PreviewProvider {
static var previews: some View {
TextfieldBootcamp()
}
}Code Walkthrough
TextField("Type something here...", text: $textFieldText)— The first argument is placeholder text that appears when the field is empty. Thetext: $textFieldTextbinding means any keystroke updatestextFieldTextimmediately, and any external change totextFieldText(liketextFieldText = ""insaveText) updates what's shown in the field.textIsAppropriate() ? Color.blue : Color.gray— This function is called on every render. BecausetextFieldTextchanges every keystroke andtextFieldTextis used insidetextIsAppropriate(), the button color updates reactively with every character typed. No explicit observer or listener is needed..disabled(!textIsAppropriate())— This is a critical UX detail..disabledprevents the tap gesture entirely, not just the visual change. Combined with the gray color, it creates a clear "not yet ready" state.Text("Save".uppercased())—.uppercased()is a Swift String method called before the text is passed toText. This is equivalent toText("SAVE")— it transforms the string at the data level, not the display level.dataArray.append(textFieldText)thentextFieldText = ""— The order matters here. Append first (so the current text is saved), then clear. If you clear first, you'd append an empty string.ForEach(dataArray, id: \.self)— Renders all saved entries. Each new save causesdataArrayto change, triggering a re-render of theForEach. The new entry appears at the bottom of the list instantly, with SwiftUI's default insertion animation.
Common Mistakes
Mistake: Calling textIsAppropriate() inside the button action for validation, but not also using it in .disabled
If you only validate in the action closure, the button is still visually active and tappable even when invalid. Use .disabled(!textIsAppropriate()) to block the tap, and the matching ternary on background color to provide visual feedback. Both pieces work together.
Mistake: Using textFieldText.count > 3 instead of >= 3
This accepts only strings of 4+ characters. Users typing 3-character inputs ("abc") would be confused by the grayed button. Make sure your condition matches the minimum you communicate to the user.
Mistake: Forgetting textFieldText = "" in saveText()
Without clearing the field, the same text remains after saving. Users expect a save action to clear the input — this matches standard iOS form behavior.
Key Takeaways
TextFieldwith a$binding creates a live two-way sync between the field and a@Statestring — every keystroke is reflected immediately.- Validation logic as a simple
Boolfunction can be used in multiple places (background,.disabled,ifguards) to create cohesive, reactive UI. - Clearing the text field after saving is done by mutating the bound
@Statevariable — the binding automatically reflects the change in the field.
Last updated: June 27, 2026