How to fix onChange(of:perform:) deprecation warnings in iOS 17

Fix the onChange(of:perform:) deprecation warnings in iOS 17.

If you're in the process of updating your SwiftUI apps for iOS 17, you are probably running into this warning:

'onChange(of:perform:)' was deprecated in iOS 17.0: Use `onChange` with a two or zero parameter action closure instead.

I found this warning to be confusing. "Use 2 parameters...or zero parameters?" "What's wrong with using the one parameter I'm already using?" It was only in writing this post that I figured out the why behind Apple's reasoning for this change. By forcing myself to try and write about this warning, I figured it out.

What's Changed?

Essentially, Apple deprecated the existing instance method in favor of two new methods:

onChange(of:initial:_:) | Apple Developer Documentation
Adds a modifier for this view that fires an action when a specific value changes.

And:

onChange(of:initial:_:) | Apple Developer Documentation
Adds a modifier for this view that fires an action when a specific value changes.

You'll notice those methods have some subtle, but important differences in their method signature. The first uses these:

value The value to check against when determining whether to run the closure.
initial Whether the action should be run when this view initially appears.
action A closure to run when the value changes.

Notice that the action closure takes no parameters. Using this, we no longer have to pass anything into our closure to get the value. The observed value comes along for the ride automatically. Thus we have zero arguments coming into our action closure.

That second link, points to a similar version, with the key difference being the two values coming into our action closure: oldValue and newValue.

value The value to check against when determining whether to run the closure.
initial Whether the action should be run when this view initially appears.
action A closure to run when the value changes.
oldValue The old value that failed the comparison check (or the initial value when requested).
newValue The new value that failed the comparison check.

In the second instance, we have two new arguments we can pass into the closure oldValue and newValue, yielding two arguments.

The first is cleaner, easier to reason about, and simply less code. The second is more powerful.

You might have also notices that both methods have a new initial parameter, which will allow you to decide if the closure should run when the view initially appears. I haven't yet needed to do that, but I expect this is something some developers might be very excited about. The SwiftUI team has taken the old .onChange() modifier and made it cleaner and more powerful with these new methods. Sweet.


How to fix this warning

Your code might've looked something like this up until now. With one value argument coming into your trailing closure.

If that "trailing closure" terminology sounds funny to you or is something you still haven't quite wrapped your head around yet, forget about it for now. It took me a real long time to get it too.

Anyways, your code might look something like this today:

.onChange(of: selectedIcon) { tag in
    if tag == "Default" {
        changeAppIcon(to: nil)
    } else {
        changeAppIcon(to: tag)
    }
}

With this version of .onChange() being deprecated, Apple is essentially saying the following with their warning:

"Hey, stop using this. We're going to remove it some time in the future and this will totally stop working in your app. Use these other versions of .onChange() instead."

By dropping the tag in bits and using the selectedIcon argument instead, we can adopt the new, preferred way of using .onChange() and silence the Xcode warning.

.onChange(of: selectedIcon) {
    if selectedIcon == "Default" {
        changeAppIcon(to: nil)
    } else {
        changeAppIcon(to: selectedIcon)
    }
}

oldValue and newValue version

In some cases, you might want to use the old value and the new value of the property you are observing. In those cases, you can now do the following:

.onChange(of: someState) { oldState, newState in
    model.yourDidChangeMethod(from: oldState, to: newState)
}

This is that "two parameter action closure" that Apple mentions in their warning. Adopting whichever suits your view will silence the warning.

🤙

Subscribe to Optimistic Closures

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe