WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

Scrolling a list up and down breaks the animation

Forums > SwiftUI

Greetings,

I have an image that I animate when an action is being performed. And it works well if the animated section of the view stays in sight. But when I scroll the page down to the bottom and go back up, the animation stopped.

You can see a demo here: https://imgur.com/a/pEKEJgj

Hosts www.apple.com and www.bing.com start the animation properly, but when I scroll down and back up, it stopped.

This is the code for the animation itself:

@State private var rotationState = false
    let animation = Animation.linear(duration: 1).repeatForever(autoreverses: false)

And this is the code to call the animation in the view:

Image(systemName: "line.3.crossed.swirl.circle.fill")
                                                    .foregroundColor(.orange)
                                                    .rotationEffect(Angle.degrees(rotationState ? 360 : 0))
                                                    .onAppear{
                                                        withAnimation(animation) {
                                                            rotationState = true
                                                        }
                                                    }

I've also tried using a dedicated Swift file as a View like this:

import SwiftUI

struct StatusCheckingView: View {
    @State private var rotationState = false
    let animation = Animation.linear(duration: 1).repeatForever(autoreverses: false)

    var body: some View {
        Image(systemName: "line.3.crossed.swirl.circle.fill")
            .foregroundColor(.orange)
            .rotationEffect(Angle.degrees(self.rotationState ? 360 : 0))
            .onAppear{
                withAnimation(animation) {
                    self.rotationState = true
                }
            }
    }
}

struct StatusCheckingView_Previews: PreviewProvider {
    static var previews: some View {
        StatusCheckingView()
    }
}

But got the same result.

Anyone else faced the same issue and found a solution for it?

Thank you :-)

   

This response may not be 100% accurate. But it's what I remember from other animation lectures.

Imagine you have a simple square in your layout with a variable for the background color, currently

private var backgroundColor = Color.red

You have marked the square shape with an animation sequence so that anytime you change the background color, it will slowly change to the new color over five seconds. Simple animation.

So what happens when you actually change the variable to .blue ?

What happens is the variable is changed instantly! It was .red, and now it is .blue. iOS renders a before image (.red) and and after image (.blue), then creates several hundred, perhaps thousand, in between structs where it slowly fades from one color to the other. Then it displays these structs over the five second duration you previously specified giving the illusion of color transition.

But remember, the background color variable is already set to .blue !

Now consider your case. While on screen, the animation is taking five seconds to run its course. But then you scroll the view off screen. Because the view is a light weight struct, iOS is free to destroy it while creating new structs to display. But then you scroll back and iOS must recreate the struct it just destroyed.

So it looks at the vars passed to it, or perhaps looks up the @State vars stored outside the struct, then recreates it for the display.

Ask your self this: if you were to recreate the view, what parameters do you need? To color the square, you need to know its background color. It's .blue ! It's not "half red / half blue". It's not 0.75 blue and 0.25 red. It's 100% blue.

When recreating the view struct, the before background color is .blue, therefore it's rendered blue without any animation.

   

So are you saying it's not possible to keep an animation going when you scroll a list up and down?

Or are you saying I'm not using the appropriate way to do it?

   

I wrote a short test. Turns out (predictably) that I was wrong. Paste this code into a new project and run in a simulator. Tap the Animate toggle to start the animation. Notice how the values of the foreground color for each row of the list change immediately. However the fade animations take 8 seconds to complete.

This gives you time to scroll the list so the first few rows go OFF the screen. Count to three. Then release your scroll. The first few rows return to the screen, but are still animating.

//  OffScreenFadeTest.swift
//  Created by Oblelix on 4/3/22.

import SwiftUI
struct OffScreenFadeTest: View {
    private var startColor = Color.blue.opacity(0.3)  // very pale color
    @State private var isAnimating = false
    var body: some View {
        VStack {
            // 1. Tap the toggle to start the animation.
            Toggle("Animate", isOn: $isAnimating)
            List{
                Group {
                    // 2. Scroll the list so the first few rows are OFF SCREEN.
                    // 3. Count to 3.
                    // 4. Release the scroll.
                    ListCell( cellColor: isAnimating ? .red    : startColor)
                    ListCell( cellColor: isAnimating ? .red    : startColor)
                    ListCell( cellColor: isAnimating ? .yellow : startColor)
                    ListCell( cellColor: isAnimating ? .teal   : startColor)
                    ListCell( cellColor: isAnimating ? .orange : startColor)
                    ListCell( cellColor: isAnimating ? .blue   : startColor)
                    ListCell( cellColor: isAnimating ? .indigo : startColor)
                    ListCell( cellColor: isAnimating ? .yellow : startColor)
                    ListCell( cellColor: isAnimating ? .purple : startColor)
                }
                Group {
                    ListCell( cellColor: isAnimating ? .red    : startColor)
                    ListCell( cellColor: isAnimating ? .red    : startColor)
                    ListCell( cellColor: isAnimating ? .yellow : startColor)
                    ListCell( cellColor: isAnimating ? .teal   : startColor)
                    ListCell( cellColor: isAnimating ? .orange : startColor)
                    ListCell( cellColor: isAnimating ? .blue   : startColor)
                    ListCell( cellColor: isAnimating ? .indigo : startColor)
                    ListCell( cellColor: isAnimating ? .yellow : startColor)
                    ListCell( cellColor: isAnimating ? .purple : startColor)
                }
            }
        }
    }
}

struct ListCell: View {
    var cellColor   = Color.blue
    var body: some View {
        ZStack {
            // Animate the change over 8 seconds.
            // But notice the color changes immediately.
            Rectangle().foregroundColor( cellColor )
                .animation(.linear(duration: 8.0), value: cellColor)
            Text("\(cellColor.description)")
        }
    }
}

   

Hi Obelix,

I tried you code, and you might have missed it, but when you scroll, you can see the last line, 2nd occurence of purple, is already purple when you reach the bottom, as it's not on screen when it's loaded.

I've upped the time to complete and still purple is instantly purple while the other ones are fading slowly.

The issue with your example is that there are not enough lines. I've added 2 additional groups, to make it 40 lines, and this is more obvious this way.

Up the time to 60 seconds, and run the code. The lines at the bottom are already fully colored, and when you go back to the top, the same applies to the ones that were fading slowly before scrolling.

Either it's not supposed to work at all or it needs to be written in another way or it's a bug, I'm not sure at this point.

   

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.