I'm animating a square into a circle in the following snippet. My view observes the isCircle
property to determine whether to draw a circle or square. The MyData#doSomethingExpensive
method is meant to simulate a long running task which, upon finishing, triggers the UI to animate (in this case animating a square to a circle).
This actually works as intended, but I have a withAnimation
call in my @Observable
UI model. I would rather have withAnimation
inside the view, if possible. I would also like to continue using the @Observable
API instead of switching back to ObservableObject
conformance. Is there a way to refactor this such that the view will respond to changes in isCircle
with the same animation that I have here:
import SwiftUI
@Observable
final class MyData {
var isCircle = false
func doSomethingExpensive() {
Task {
try await Task.sleep(for: .milliseconds(Int.random(in: 300...800)))
withAnimation(.smooth(duration: 1.5)) {
self.isCircle.toggle()
}
}
}
}
struct ContentView: View {
let myData = MyData()
var body: some View {
VStack {
if myData.isCircle {
Circle().fill(.blue).frame(width: 200)
} else {
Rectangle().fill(.red).frame(width: 200, height: 200)
}
Button("Animate later") {
myData.doSomethingExpensive()
}
}
}
}
I found a reasonable approach that works for my case. It requires wrapping the if
in a ZStack and applying the .animation(value:)
modifier to that ZStack:
@Observable
final class MyData {
var isCircle = false
func doSomethingExpensive() {
Task {
try await Task.sleep(for: .milliseconds(Int.random(in: 300...800)))
self.isCircle.toggle()
}
}
}
struct ContentView: View {
let myData = MyData()
var body: some View {
VStack {
ZStack {
if myData.isCircle {
Circle().fill(.blue).frame(width: 200)
} else {
Rectangle().fill(.red).frame(width: 200, height: 200)
}
}.animation(.smooth(duration: 1.5), value: myData.isCircle)
Button("Animate later") {
myData.doSomethingExpensive()
}
}
}
}