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

Nguyễn Khắc Hào
  • 1,980
  • 2
  • 15
  • 25
  • 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.) –  Aug 20 '19 at 01: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. – Nguyễn Khắc Hào Aug 20 '19 at 02: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! –  Aug 20 '19 at 02:50
  • Yeah that's sad. I didn't even know animations only apply to a View until now. I actually have some (bad) ways to circumvent the problem, just trying to see if there are any more straightforward solution. Thanks anyway – Nguyễn Khắc Hào Aug 20 '19 at 03:00
  • A bit of background - I don't think this helps in terms of SwiftUI, but who knows. Talking UIKit, you have a UIView as a base. You can subclass it into nearly anything, including a UIButton. Focusing on UIView? Every one has a `CALayer` behind it. And the "CA" in a `view.layer` stands for `CoreAnimation`. (I'm pretty sure you already know this, so please, don't be offended.) Take this into a SwiftUI stack - which is totally different than a UIKit stack (and I'm talking a processing stack, not a view stack) and that's why I'm thinking it won't work with a SwiftUI stack. Hoping I'm wrong... –  Aug 20 '19 at 03:15
  • Oh don't worry I'm not offended at all since I have little to no knowledge about UIKit. Interesting to know anyway – Nguyễn Khắc Hào Aug 20 '19 at 03:29
  • 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. – arsenius Oct 04 '19 at 03:44
  • Yeah I did realize the animation works when there is no ScrollView, but kept thinking it was because of something I did. Thanks for letting me know its a bug – Nguyễn Khắc Hào Oct 06 '19 at 08:53
  • 1
    Looks like animations doesn't work when Views are embedded on a ForEach View, I hope that's just a bug – Mattia Righetti Nov 01 '19 at 22:26
  • There is solution, please check answer. @dfd. – Asperi Mar 28 '20 at 16:18
  • There is solution, please check answer. @arsenius. – Asperi Mar 28 '20 at 16:19

1 Answers1

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) !!!
           }
         }
       }
   }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 2
    This works nicely for removal, but when doing #append, transition animation does not work. – NeverwinterMoon May 25 '20 at 12:13
  • @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 – Hamad Fuad Feb 23 '23 at 11:43