19

Is there any way I can add animation when elements of a ForEach loop appears or disappears?

I have tried using withAnimation{} and .animation() in many ways but they didn't seem to work

Here is some code (Xcode 11 beta 5):

import SwiftUI

struct test: View {
    @State var ContentArray = ["A","B","C"]
    var body: some View {
        ScrollView{
        VStack{
            ForEach(ContentArray.indices, id: \.self){index in
                ZStack{
                // Object
                    Text(self.ContentArray[index])
                    .frame(width:100,height:100)
                    .background(Color.gray)
                    .cornerRadius(20)
                    .padding()
                //Delete button
                    Button(action: {
                      self.ContentArray.remove(at: index)
                    }){
                    Text("✕")
                    .foregroundColor(.white)
                    .frame(width:40,height:40)
                    .background(Color.red)
                    .cornerRadius(100)
                   }.offset(x:40,y:-40)
             }
           }
         }   
       }
   }
}


#if DEBUG
struct test_Previews: PreviewProvider {
    static var previews: some View {
        test()
    }
}
#endif

As can be seen below, without animations everything feels super abrupt. Any solution is really appreciated

Important notice: The layout should change in the same way List does when the number of elements changes. For example, every object automatically moves top when a top object is deleted

Expected result

Bad result

11
  • 1
    I think animations only apply to a View - just like a UIView. Instead of a VStack have you tried using a List? I'm pretty sure animations work there. (IIRC, stacks are not views, just a way to order views.) Commented Aug 20, 2019 at 1:40
  • 1
    I've just tried using List and yup it did work thanks. But if given the option I'd prefer to avoid List since it's more limited compared to stacks when designing the UI. Commented Aug 20, 2019 at 2:09
  • I understand, but if I'm correct in that stacks - H, V, Z - are not true views and that animations are only applied to views (think UIKit) then you may well be limited there. IMHO, a List is almost like a UITableView. Next, a "hierarchy" of views (think a Button with a VStack for it's label) gets "automagically" shrunk into a single View by SwiftUI. But a "stack" of Views? I really thought I saw things to suggest that's separate views in a... stack. (Hope I'm wrong.) You can use animations to make a view "appear" - offset, slide, easeInOut. - but that seems overkill. Good luck! Commented Aug 20, 2019 at 2:50
  • 1
    I have no idea what @dfd is talking about here re: "true views". I do not believe that is accurate. This is a bug in SwiftUI. Comment out the ScrollView and the animations should start working. (I didn't try your exact example, but made my own minimal version.) I've filed feedback and suggest the OP do the same. Commented Oct 4, 2019 at 3:44
  • 1
    Looks like animations doesn't work when Views are embedded on a ForEach View, I hope that's just a bug Commented Nov 1, 2019 at 22:26

1 Answer 1

7

It looks like this problem is still up to day (Xcode 11.4), because by just copy-pasting the observed effect is the same. So, there are a couple of problems here: first, it needs to setup combination of animation and transition correctly; and, second, the ForEach container have to know which exactly item is removed, so items must be identified, instead of indices, which are anonymous.

As a result we have the following effect (transition/animation can be others):

demo

struct TestAnimationInStack: View {
    @State var ContentArray = ["A","B","C", "D", "E", "F", "G", "I", "J"]
    var body: some View {
        ScrollView{
        VStack{
            ForEach(Array(ContentArray.enumerated()), id: \.element){ (i, item) in // << 1) !
                ZStack{
                // Object
                    Text(item)
                    .frame(width:100,height:100)
                    .background(Color.gray)
                    .cornerRadius(20)
                    .padding()
                //Delete button
                    Button(action: {
                       withAnimation { () -> () in              // << 2) !!
                           self.ContentArray.remove(at: i)         
                       }
                    }){
                    Text("✕")
                    .foregroundColor(.white)
                    .frame(width:40,height:40)
                    .background(Color.red)
                    .cornerRadius(100)
                   }.offset(x:40,y:-40)
                }.transition(AnyTransition.scale)              // << 3) !!!
           }
         }
       }
   }
}
Sign up to request clarification or add additional context in comments.

2 Comments

This works nicely for removal, but when doing #append, transition animation does not work.
@NeverwinterMoon insertion transition will animate if you don't update the value inside the animation block. All you have to do is attach .animation(.default, value: YOUR_VALUE) to ZStack and update the value

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.