0

My goal is to have control over the type of animation when an object is added to the @State events array.

withAnimation only occurs on the first append to the events array. It is then ignored on additional appends.

I'm currently running this on Xcode 11 beta 4

I've tried adding the calling DispatchQueue.main.async, having the animation on the Text() object.

If I use a list it performs animation on addition, however I don't know how to modify those animations.

Goal

  • Have text slide in with each append and fade out on each remove.
struct Event: Identifiable {
    var id = UUID()
    var title: String
}

struct ContentView: View {
    @State
    var events = [Event]()

    var body: some View {
        VStack {
            ScrollView {
                ForEach(events) { event in
                    Text(event.title)
                    .animation(.linear(duration: 2))
                }
            }

            HStack {
                Button(action: {
                    withAnimation(.easeOut(duration: 1.5)) {
                        self.events.append(Event(title: "Animate Please"))
                    }
                })  {
                    Image(systemName: "plus.circle.fill").resizable().frame(width: 40, height: 40, alignment: .center)
                }
            }

        }
    }
}

I'm expecting that each append has an animation that is described in the withAnimation block.

1 Answer 1

5

When SwiftUI layouts and animations behave in ways you think are not correct, I suggest you add borders. The outcome may surprise you and point you directly into the cause. In most cases, you'll see that SwiftUI was actually right! As in your case:

Start by adding borders:

ScrollView {
    ForEach(events) { event in
        Text(event.title)
        .border(Color.red)
        .animation(.linear(duration: 2))
    }.border(Color.blue)
}.border(Color.green)

enter image description here

When you run your app, you'll see that before adding your first array element, the ScrollView is collapsed into zero width. That is correct, as the ScrollView is empty. However, when you add your first element, it needs to be expanded to accommodate the "Animate Please" text. The Text() view also starts with zero width, but as its containing ScrollView grows, it does too. These are the changes that get animated.

Now, when you add your second element, there is nothing to animate. The Text() view is placed with its final size right from the start.

If instead of "Animate Please", you change your code to use a random length text, you will see that when adding a largest view, animations do occur. This is because ScrollView needs to expand again:

self.events.append(Event(title: String(repeating: "A", count: Int.random(in: 0..<20))))

What next: You have not explained in your question what animation you expect to see. Is it a fade-in? A slide? Note that in addition to animations, you may define transitions, which determines the type of animation to perform when a view is added or removed from your hierarchy.

If after putting these tips into practice, you continue to struggle, I suggest you edit your question and tell us exactly what animation would you like to see when adding a new element to your array.

UPDATE

According to your comments, you want the text to slide. The simplest form, is using a transition. Unfortunately, the ScrollView seems to disable transitions on its children. I don't know if that is intended or a bug. Anyway, here I post two methods. One with transitions (does not work with ScrollView) and one using only animations, which does work inside a ScrollView, but requires more code:

With transitions (does not work inside a ScrollView)

struct ContentView: View {
    @State private var events = [Event]()

    var body: some View {
        VStack {
            ForEach(events) { event in
                // A simple slide
                Text(event.title).transition(.slide).animation(.linear(duration: 2))

                // To specify slide direction
                Text(event.title).transition(.move(edge: .trailing)).animation(.linear(duration: 2))

                // Slide combined with fade-in
                Text(event.title).transition(AnyTransition.slide.combined(with: .opacity)).animation(.linear(duration: 2))
            }

            Spacer()
            HStack {
                Button(action: {
                    self.events.append(Event(title: "Animate Please"))
                })  {
                    Image(systemName: "plus.circle.fill").resizable().frame(width: 40, height: 40, alignment: .center)
                }
            }
        }
    }
}

Without transitions (works inside a ScrollView):

struct Event: Identifiable {
    var id = UUID()
    var title: String
    var added: Bool = false
}

struct ContentView: View {
    @State var events = [Event]()

    var body: some View {
        VStack {
            ScrollView {
                ForEach(0..<events.count) { i in
                    // A simple slide
                    Text(self.events[i].title).animation(.linear(duration: 2))
                    .offset(x: self.events[i].added ? 0 : 100).opacity(self.events[i].added ? 1 : 0)
                    .onAppear {
                        self.events[i].added = true
                    }
                }
                HStack { Spacer() } // This forces the ScrollView to expand horizontally from the start.
            }.border(Color.green)

            HStack {
                Button(action: {
                    self.events.append(Event(title: "Animate Please"))
                })  {
                    Image(systemName: "plus.circle.fill").resizable().frame(width: 40, height: 40, alignment: .center)
                }
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the detailed explanation @kontiki. Adding borders helps a lot with understanding what's going on. I'd like to see a simple slide in transition for each append. I'll update my question too.
Updated the answer to make the slide possible. Cheers.
For me, append does not work with transition, in ScrollView or without it. list#remove works with transition animation though, in ScrollView or without it...

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.