SwiftUI
TextField
Live Changes
Swift Programming
iOS Development

How to detect live changes on TextField in SwiftUI?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In SwiftUI, a TextField updates its bound value as the user types, which makes live change detection straightforward once you understand state and bindings. The usual pattern is to store the text in @State and react to updates with onChange.

Bind the TextField to State

The first step is to connect the field to a value SwiftUI can observe. As the user types, the bound String changes immediately.

swift
1import SwiftUI
2
3struct UsernameView: View {
4    @State private var username = ""
5    @State private var message = "Enter at least 3 characters"
6
7    var body: some View {
8        VStack(alignment: .leading, spacing: 12) {
9            TextField("Username", text: $username)
10                .textFieldStyle(.roundedBorder)
11                .onChange(of: username) { newValue in
12                    if newValue.count >= 3 {
13                        message = "Looks good"
14                    } else {
15                        message = "Enter at least 3 characters"
16                    }
17                }
18
19            Text(message)
20                .foregroundStyle(username.count >= 3 ? .green : .secondary)
21        }
22        .padding()
23    }
24}

This works because SwiftUI re-renders the view whenever username changes. The onChange modifier gives you a clean place to run validation, update helper text, enable buttons, or trigger lightweight calculations.

Use Live Changes for Validation and UI Feedback

Live updates are most useful when the response is cheap and local to the UI. Good examples include:

  • showing a character count
  • enabling a submit button only when input is valid
  • formatting text as the user types
  • showing inline hints

Here is a slightly richer example with a disabled button:

swift
1import SwiftUI
2
3struct EmailFormView: View {
4    @State private var email = ""
5
6    private var isValidEmail: Bool {
7        email.contains("@") && email.contains(".")
8    }
9
10    var body: some View {
11        VStack(spacing: 16) {
12            TextField("Email address", text: $email)
13                .textInputAutocapitalization(.never)
14                .autocorrectionDisabled()
15                .textFieldStyle(.roundedBorder)
16
17            Text(isValidEmail ? "Ready to submit" : "Enter a valid email")
18                .foregroundStyle(isValidEmail ? .green : .red)
19
20            Button("Continue") {
21                print("Submitting \(email)")
22            }
23            .disabled(!isValidEmail)
24        }
25        .padding()
26    }
27}

Notice that this second example does not even need onChange. Because the view derives its output directly from email, the UI still reacts live. That is an important SwiftUI idea: not every live update needs an explicit callback.

Avoid Heavy Work on Every Keystroke

A common mistake is to perform a network request or database search on each character. That makes the UI noisy and wastes work. When the reaction is expensive, debounce it.

One simple approach is to keep a reference to a Task, cancel the previous task, and wait briefly before running the expensive operation:

swift
1import SwiftUI
2
3struct SearchView: View {
4    @State private var query = ""
5    @State private var results: [String] = []
6    @State private var searchTask: Task<Void, Never>?
7
8    var body: some View {
9        VStack {
10            TextField("Search", text: $query)
11                .textFieldStyle(.roundedBorder)
12                .onChange(of: query) { newValue in
13                    searchTask?.cancel()
14                    searchTask = Task {
15                        try? await Task.sleep(for: .milliseconds(300))
16                        guard !Task.isCancelled else { return }
17                        results = runLocalSearch(for: newValue)
18                    }
19                }
20
21            List(results, id: \.self) { item in
22                Text(item)
23            }
24        }
25        .padding()
26    }
27
28    private func runLocalSearch(for query: String) -> [String] {
29        let allItems = ["apple", "banana", "grape", "orange", "pear"]
30        if query.isEmpty { return [] }
31        return allItems.filter { $0.localizedCaseInsensitiveContains(query) }
32    }
33}

The cancellation step matters. Without it, an older search can finish after a newer one and briefly show stale results.

When to Use Other Hooks

Use onChange when you need side effects. Use derived properties when the UI can be computed directly from state. Use onSubmit only when you care about the return key or explicit submission, not every keystroke.

On newer platform versions you may also see onChange(of:initial:), which can run once on appearance and then on later changes. The core idea is still the same: SwiftUI watches a value and reruns your logic when that value changes.

Common Pitfalls

  • Using onSubmit when you actually need live typing updates. onSubmit does not fire on every character.
  • Doing expensive work for each keystroke without debounce or cancellation.
  • Mutating the same value inside onChange without a clear normalization rule. That can create surprising repeated updates.
  • Expecting the field to update a plain stored property. Live changes require a binding such as @State, @Binding, or an observable model property.
  • Forgetting that many UI reactions do not need a callback at all. Derived state is often simpler and easier to test.

Summary

  • A SwiftUI TextField reports live edits through its bound state value.
  • 'onChange is the standard tool when you need side effects during typing.'
  • Simple validation can often be expressed as derived UI state without any callback.
  • Debounce expensive work so typing stays responsive.
  • Choose onSubmit only for explicit submission events, not for per-character updates.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.