My view is determined by state stored in a ViewModel
. Sometimes the view might call a function on its ViewModel
, causing an asynchronous state change.
How can I animate the effect of that state change in the View
?
Here's a contrived example, where the call to viewModel.change()
will cause the view to change colour.
class ViewModel: ObservableObject {
@Published var color: UIColor = .blue
func change() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.color = .red
}
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
Color(viewModel.color).onAppear {
withAnimation(.easeInOut(duration: 1.0)) {
self.viewModel.change()
}
}
}
}
If I remove the ViewModel
and store the state in the view itself, everything works as expected. That's not a great solution, however, because I want to encapsulate state in the ViewModel
.
struct ContentView: View {
@State var color: UIColor = .blue
var body: some View {
Color(color).onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation(.easeInOut(duration: 1.0)) {
self.color = .red
}
}
}
}
}
It looks as though using withAnimation
inside an async closure causes the color not to animate, but instead to change instantly.
Either removing the wrapping asyncAfter
, or removing the withAnimation
call and adding an animation
modifier in the body
of your ContentView
(as follows) should fix the issue:
Color(viewModel.color).onAppear {
self.viewModel.change()
}.animation(.easeInOut(duration: 1))
Tested locally (iOS 13.3, Xcode 11.3) and this also appears to dissolve/fade from blue to red as you intend.