I have a background view that shows a status indicator behind the main content. I have it set to animate and I would like it to animate when the status changes, I'm passing the status to the background view. However, it's only animating the first time it appears rather than when the status is changing.
I'm using onAppear to turn the animation on, but it seems onAppear is only called the first time the view is rendered rather than when the content changes.
I've tried without success:
Using onChange(of) with the status.
Bringing the animation logic into the primary view.
I do have a workaround in my repro code (commented), but there must be a better approach.
xcode 12.4/iOS 14.4 & xcode 12.5/iOS 14.5
I'm using onAppear to turn the animation on, but it seems onAppear is only called the first time the view is rendered rather than when the content changes.
I've tried without success:
Using onChange(of) with the status.
Bringing the animation logic into the primary view.
I do have a workaround in my repro code (commented), but there must be a better approach.
xcode 12.4/iOS 14.4 & xcode 12.5/iOS 14.5
Code Block struct ContentView: View { @State private var status: String = "1" var body: some View { VStack(spacing: 0) { Spacer() BackgroundView(status: status) // This is my current workaround, but looking for a better way // if status != "1" && status != "3" { // BackgroundView(status: status) // } else if status == "3" { // BackgroundView(status: status) // } else { // BackgroundView(status: status) // } Spacer() Button(action: { if status == "1" { status = "2" } else if status == "2" { status = "3" } else { status = "1" } }, label: { Text("next status") }) Spacer() } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } struct BackgroundView: View { var status: String = "1" @State private var isAnimating: Bool = false var body: some View { ZStack { Group { Image(systemName: "\(status).circle.fill") .resizable() .scaledToFit() .frame(width: 220) .foregroundColor(.red) } .scaleEffect(isAnimating ? 1.0 : 0, anchor: .top) } .animation(Animation.easeOut(duration: 0.4), value: isAnimating) .onAppear(perform: { print("appear: \(status)") isAnimating = true }) // .onDisappear(perform: { // print("disappear: \(status)") // isAnimating = false // doesn't help // } ) } }
How about something like this?
Your BackgroundView will animate as expected when isAnimating changes from false to true.
So, do it onChange of status.
Code Block struct ContentView: View { @State private var status: String = "" var body: some View { VStack(spacing: 0) { Spacer() BackgroundView(status: $status) Spacer() Button(action: { if status == "1" { status = "2" } else if status == "2" { status = "3" } else { status = "1" } }, label: { Text("next status") }) Spacer() } .onAppear { status = "1" } } } struct BackgroundView: View { @Binding var status: String @State var isAnimating: Bool = false var body: some View { ZStack { Group { Image(systemName: "\(status).circle.fill") .resizable() .scaledToFit() .frame(width: 220) .foregroundColor(.red) } .scaleEffect(isAnimating ? 1.0 : 0, anchor: .top) } .onChange(of: status) {_ in isAnimating = false withAnimation(Animation.easeOut(duration: 0.4)) { isAnimating = true } } } }
Your BackgroundView will animate as expected when isAnimating changes from false to true.
So, do it onChange of status.