SwiftUI EaseIn Transition Cycling Through Multiple Colors

Viewed 166

I'm currently trying to create an animation using SwiftUI to transition between multiple colors, however, I'm stuck on a particular issue. The code works, however, it only changes across maybe 2-3 different colors then resets back to the original color.I am also using a library called 'DynamicColor' through GitHub as a way of easily converting a hex code to a SwiftUI Color variable.

Below is the @State variable I've defined which holds the current color. It also shows the array of UInt64 numbers that hold all the colors I'd like to cycle through.

//Initialize the Color variable to the first color on the DesignMatters transition

@State private var bgColor = Color(hex: 0x67C7C7)

//Array of colors that I'd like to cycle through

let colorArray:[UInt64] = [0x65CCC2, 0x6EC6B2, 0x75AA89, 0x83B88A, 0x8BB37E, 0x9EA77E, 0xB0987A, 0xC18F77, 0xD28274, 0xE27771, 0xEC6E72, 0xE17180, 0xD5758E, 0xC9769D, 0xBD79AC, 0xB07BBD, 0xA280CD, 0x9687D5, 0x8E93D2, 0x849FD0, 0x7BADCD, 0x71B9CB, 0x66C8C7, 0x65CCBE, 0x6BC8B3, 0x75C1A4, 0x7CBC96, 0x83B98B, 0x8BB37E, 0x9EA67B, 0xAF9979, 0xC28D77, 0xD28275, 0xE47872, 0xEB6E75, 0xDD7284, 0xCE7696, 0xC078A5, 0xB47BB7, 0xA67FC8, 0x9B80D6, 0x908DD3, 0x879CD1, 0x7DA7CD, 0x73B6CC]

The animation function that I've created looks like this:

//Function to transition between colors in the background
func backgroundColorAnimation () {

    var count = 0
    withAnimation(.easeIn(duration: 1).repeatForever(autoreverses: false)) {
    
    //increment the counter
    count += 1
            
    //check if counter is within bounds of the array
    if (count < colorArray.count) {
                
        self.bgColor = Color(hex: colorArray[count])
                
     }
  }
}

And finally, here is the body of the view that actually shows the animation:

var body: some View {
        
    ZStack {
            
        Rectangle()
            .opacity(0)
            
        Text("Hello World!")
            
     }
     .background(bgColor)
     .frame(width:500, height: 350)
     .onAppear {
           
         backgroundColorAnimation()
           
     }
}

I have tried to simply just re-assign the "self.bgColor" variable one after the other with the same result. I have also messed with the duration of the animation but, again, no luck. Anyways, if anyone happens to have any idea what I may be doing wrong, I'd really appreciate the help. Thank you so much and hope you all have a great rest of your day!

(UPDATE) In looking at the code I posted, for whatever reason the count of the array of UInt64 variables is returning 1. Just for clarification of the other way I've approached the issue, I've included the body of the view where I just change the variable one after the other:

ZStack {
            
  Rectangle()
     .opacity(0)
            
  Text("Hello World!")
            
}
.background(bgColor)
.frame(width:500, height: 350)
.onAppear {
            
    withAnimation(.easeIn(duration: 1).repeatForever(autoreverses: true)) {
               
                self.bgColor = Color(hex: 0x67C7C7)
                self.bgColor = Color(hex: 0x65CCC2)
                self.bgColor = Color(hex: 0x6EC6B2)
                self.bgColor = Color(hex: 0x75AA89)
                self.bgColor = Color(hex: 0x83B88A)
                self.bgColor = Color(hex: 0x8BB37E)
                self.bgColor = Color(hex: 0x9EA77E)
                self.bgColor = Color(hex: 0xB0987A)
                self.bgColor = Color(hex: 0xC18F77)
                self.bgColor = Color(hex: 0xD28274)
                self.bgColor = Color(hex: 0xE27771)
                self.bgColor = Color(hex: 0xEC6E72)
                self.bgColor = Color(hex: 0xE17180)
                self.bgColor = Color(hex: 0xD5758E)
                self.bgColor = Color(hex: 0xC9769D)
                self.bgColor = Color(hex: 0xBD79AC)
                self.bgColor = Color(hex: 0xB07BBD)
                self.bgColor = Color(hex: 0xA280CD)
                self.bgColor = Color(hex: 0x9687D5)
                self.bgColor = Color(hex: 0x8E93D2)
                self.bgColor = Color(hex: 0x849FD0)
                self.bgColor = Color(hex: 0x7BADCD)
                self.bgColor = Color(hex: 0x71B9CB)
                self.bgColor = Color(hex: 0x66C8C7)
                self.bgColor = Color(hex: 0x65CCBE)
                self.bgColor = Color(hex: 0x6BC8B3)
                self.bgColor = Color(hex: 0x75C1A4)
                self.bgColor = Color(hex: 0x7CBC96)
                self.bgColor = Color(hex: 0x83B98B)
                self.bgColor = Color(hex: 0x8BB37E)
                self.bgColor = Color(hex: 0x9EA67B)
                self.bgColor = Color(hex: 0xAF9979)
                self.bgColor = Color(hex: 0xC28D77)
                self.bgColor = Color(hex: 0xD28275)
                self.bgColor = Color(hex: 0xE47872)
                self.bgColor = Color(hex: 0xEB6E75)
                self.bgColor = Color(hex: 0xDD7284)
                self.bgColor = Color(hex: 0xCE7696)
                self.bgColor = Color(hex: 0xC078A5)
                self.bgColor = Color(hex: 0xB47BB7)
                self.bgColor = Color(hex: 0xA67FC8)
                self.bgColor = Color(hex: 0x9B80D6)
                self.bgColor = Color(hex: 0x908DD3)
                self.bgColor = Color(hex: 0x879CD1)
                self.bgColor = Color(hex: 0x7DA7CD)
                self.bgColor = Color(hex: 0x73B6CC)
                
            }
           
        }
1 Answers

This solution doesn't use one animation but rather a timer that sends an update every second.

I'm farely new to SwiftUI, so I'm not quite sure if this is the best solution.

However, I was able to find a work-around using a timer. Here is my code:

struct ColorChangeAnimation: View {
    
    @State private var bgColor = Color(hex: 0x67C7C7)
    @State private var bgColorIndex = 0
    @State var colorArray:[UInt64] = [0x65CCC2, 0x6EC6B2, 0x75AA89, 0x83B88A, 0x8BB37E, 0x9EA77E, 0xB0987A, 0xC18F77, 0xD28274, 0xE27771, 0xEC6E72, 0xE17180, 0xD5758E, 0xC9769D, 0xBD79AC, 0xB07BBD, 0xA280CD, 0x9687D5, 0x8E93D2, 0x849FD0, 0x7BADCD, 0x71B9CB, 0x66C8C7, 0x65CCBE, 0x6BC8B3, 0x75C1A4, 0x7CBC96, 0x83B98B, 0x8BB37E, 0x9EA67B, 0xAF9979, 0xC28D77, 0xD28275, 0xE47872, 0xEB6E75, 0xDD7284, 0xCE7696, 0xC078A5, 0xB47BB7, 0xA67FC8, 0x9B80D6, 0x908DD3, 0x879CD1, 0x7DA7CD, 0x73B6CC]
    
    @State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        
        Rectangle()
            .ignoresSafeArea()
            .foregroundColor(Color(hex:  UInt(colorArray[bgColorIndex])))
            .onReceive(timer) { _ in
                
                if bgColorIndex == colorArray.count - 1 {
                    timer.upstream.connect().cancel()
                    
                    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                        withAnimation(.easeIn(duration: 1)) { self.bgColorIndex = 0 }
                        timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
                    }
                    
                } else {
                    withAnimation(.easeIn(duration: 1)) { self.bgColorIndex += 1 }
                }
            }
    }
}

Explaination

At the beginning I declare a new @State variable which tracks the current index of the colorArray. I then use the .onAppear(perform: //code) modifier to add 1 to the index every second.

I added some code which disconnects the timer when it reaches the top of the array. I then add a 1 second delay in which I then set the array back to 0. This is to ensure that the index always stays within range and that every color gets 1 second of showtime.

Again, not quite sure if this is the best solution, but it works!

GIF of working project:

Short clip demonstrating part of the animation

Related