SwiftUI: Animate changes in List without animating content changes

The following should disable animations for row internals

VStack(alignment: .leading) {
    Text(item.name) 
    Text(self.distanceString(for: item.distance))
}
.animation(nil)

The answer by @Asperi fixed the issue I was having also (Upvoted his answer as always).

I had an issue where I was animating the whole screen in using the below: AnyTransition.asymmetric(insertion: .move(edge: .bottom), removal: .move(edge: .top))

And all the Text() and Button() sub views where also animating in weird and not so wonderful ways. I used animation(nil) to fix the issue after seeing Asperi's answer. However the issue was that my Buttons no longer animated on selection, along with other animations I wanted.

So I added a new State variable to turn on and off the animations of the VStack. They are off by default and after the view has been animated on screen I enable them after a small delay:

struct QuestionView : View {
    @State private var allowAnimations : Bool = false

    var body : some View {
        VStack(alignment: .leading, spacing: 6.0) {
            Text("Some Text")

            Button(action: {}, label:Text("A Button")
        }
        .animation(self.allowAnimations ? .default : nil)
        .onAppear() {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                self.allowAnimations = true
            }
        }
    }
}

Just adding this for anyone who has a similar issue to me and needed to build on Asperi's excellent answer.


Thanks to @Brett for the delay solution. My code needed it in several places, so I wrapped it up in a ViewModifier.

Just add .delayedAnimation() to your view.

You can pass parameters for defaults other than one second and the default animation.

import SwiftUI

struct DelayedAnimation: ViewModifier {
  var delay: Double
  var animation: Animation

  @State private var animating = false

  func delayAnimation() {
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
      self.animating = true
    }
  }

  func body(content: Content) -> some View {
    content
      .animation(animating ? animation : nil)
      .onAppear(perform: delayAnimation)
  }
}

extension View {
  func delayedAnimation(delay: Double = 1.0, animation: Animation = .default) -> some View {
    self.modifier(DelayedAnimation(delay: delay, animation: animation))
  }
}