Skip to main content

How to run some code when state changes using onChange()

About 3 minSwiftSwiftUIArticle(s)bloghackingwithswift.comcrashcourseswiftswiftuixcodeappstore

How to run some code when state changes using onChange() 관련

SwiftUI by Example

Back to Home

How to run some code when state changes using onChange() | SwiftUI by Example

How to run some code when state changes using onChange()

Updated for Xcode 15

Improved in iOS 17

SwiftUI lets us attach an onChange() modifier to any view, which will run code of our choosing when some state changes in our program. This is important, because we can't always use property observers like didSet with something like @State.

Important

This behavior is changing in iOS 17 and later, with the older behavior being deprecated.

If you need to target iOS 16 and earlier, onChange() accepts one parameter and sends its new value into a closure of your choosing. For example, this will print name changes as they are typed:

struct ContentView: View {
    @State private var name = ""

    var body: some View {
        TextField("Enter your name:", text: $name)
            .textFieldStyle(.roundedBorder)
            .onChange(of: name) { newValue in
                print("Name changed to \(name)!")
            }
    }
}

Download this as an Xcode projectopen in new window

If you're targeting iOS 17 or later, there's a variant that accepts no parameters – you can just read the property directly and be sure to get its new value, which isn't how the single-parameter version worked in iOS 16 and earlier.

iOS 17 also provides two other variants: one that accepts a two closure with parameters, one for the old value and one for the new value, and one that determines whether your action function should be run when your view is first shown.

For example, this prints out both the old and new value when a change happens:

struct ContentView: View {
    @State private var name = ""

    var body: some View {
        TextField("Enter your name", text: $name)
            .onChange(of: name) { oldValue, newValue in
                print("Changing from \(oldValue) to \(newValue)")
            }
    }
}

Download this as an Xcode projectopen in new window

And this prints a simple message when the value changes, but by adding initial: true also triggers the action closure when the view is shown:

struct ContentView: View {
    @State private var name = ""

    var body: some View {
        TextField("Enter your name", text: $name)
            .onChange(of: name, initial: true) {
                print("Name is now \(name)")
            }
    }
}

Download this as an Xcode projectopen in new window

Using initial: true is a really helpful way to consolidate functionality – rather than having to do some work in onAppear() and onChange(), you can do it all in one pass.

You might prefer to add a custom extension to Binding so that I attach observing code directly to the binding rather than to the view – it lets me place the observer next to the thing it's observing, rather than having lots of onChange() modifiers attached elsewhere in my view.

That would mean using code like this:

extension Binding {
    func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
        Binding(
            get: { self.wrappedValue },
            set: { newValue in
                self.wrappedValue = newValue
                handler(newValue)
            }
        )
    }
}

struct ContentView: View {
    @State private var name = ""

    var body: some View {
        TextField("Enter your name:", text: $name.onChange(nameChanged))
            .textFieldStyle(.roundedBorder)
    }

    func nameChanged(to value: String) {
        print("Name changed to \(name)!")
    }
}

Download this as an Xcode projectopen in new window

That being said, please be sure to run your code through Instruments if you do this – using onChange() on a view is more performant than adding it to a binding.

Similar solutions…
How to run code when your app launches | SwiftUI by Example

How to run code when your app launches
What's the difference between @ObservedObject, @State, and @EnvironmentObject? | SwiftUI by Example

What's the difference between @ObservedObject, @State, and @EnvironmentObject?
How to use @ObservedObject to manage state from external objects | SwiftUI by Example

How to use @ObservedObject to manage state from external objects
How to send state updates manually using objectWillChange | SwiftUI by Example

How to send state updates manually using objectWillChange
How to fix “Modifying state during view update, this will cause undefined behavior” | SwiftUI by Example

How to fix “Modifying state during view update, this will cause undefined behavior”

이찬희 (MarkiiimarK)
Never Stop Learning.