onPreferenceChange closure is now nonIsolated?

Running up Xcode 16.2 Beta 1, a lot of my code that used onPreferenceChange in Views to change @State properties of those views, such as some notion of a measured width is now complaining about mutating the @MainActor-isolated properties from Sendable closures.

Now I've got to hoop-jump to change @State properties from onPreferenceChange? OK, but seems a bit of extra churn.

How are you getting around this? My spontaneous thought was to use a Task to dispatch to the MainActor, but that seems to introduce visual glitches that weren't there previously.

The level of change in this migration period is insane. The preference is isolated on main actor and managed by view layer, why the closure is not MainActor?

This feels like a very awkward change. I'm not sure what the appropriate resolution for this is, but I guess we could assume it's isolated?

.onPreferenceChange(MyPreference.self) { value in
  MainActor.assumeIsolated {
    myValue = value
  }
}

Of course this will crash at runtime if that assumption is incorrect.

This feels particularly bizarre now that all View conformances are automatically @MainActor now.

Same boat. This change breaks our SDK and just seems odd. Now that Xcode 16.2 has released, using a Task for this seems inappropriate so I'd +1 the MainActor.assumeIsolated but honestly what nonsense and it's frustrating to not know if this change is purposeful. Why are no devs commenting on this; incredibly frustrating.

I like Swift Concurrency but I reckon the devs might be at the point of 'can we make SwiftUI truly concurrent for performance gains' and finding that realistically the answer is no so they need to @MainActor it all. I'm at the same pain point and the biggest and obvious issue is nonisolated contexts that need to access isolated contexts need to be made async so really I need to either @MainActor or async it all.

Just separate your struct from the View conformance and do the conformance in extension

You could capture the state/binding and mutate the wrapped value, e.g.

.onPreferenceChange(SizeKey.self) { [$size] size in
  $size.wrappedValue = size
}

Btw the thread title is misleading, onPreferenceChange was always nonisolated, what changed is the action closure becoming @Sendable.

onPreferenceChange closure is now nonIsolated?
 
 
Q