I'd like to achieve an an animated background that crossfades a bunch of images in SwiftUI.
I've managed to get an array of Colors to crossfade nicely between them using the following code snippets, but if I replace the array of Colors with an array of Images the same approach doesn't work. The images are replaced, but not animated. Yet both a Color and an Image are of type some View right?
The following works:
struct ImageBackgroundView: View {
@State private var index = 0
private let colors:[Color] = [.red, .blue, .green, .orange, .pink, .black]
private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
var body: some View {
colors[self.index]
.animation(.easeInOut(duration: 1))
.onReceive(timer) { _ in
print(self.index)
self.index += 1
if self.index > 5 { self.index = 0 }
}
}
but, if I replace the [Color] array with an array of type [Images], as follows, the images are transitioned, but the crossfade doesn't appear to work:
struct ImageBackgroundView: View {
@State private var index = 0
private let images = (1...8).map { Image("background\($0)") }
private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
var body: some View {
images[self.index]
.animation(.easeInOut(duration: 1))
.onReceive(timer) { _ in
print(self.index)
self.index += 1
if self.index > 5 { self.index = 0 }
}
}
Can anyone shed any light on why this might be the case? Is it just a bug in SwiftUI at the present time?
I achieved a similar effect in a previous app using all sorts of addSubview calls animating the opacity of overlaying views - all sorts of stuff we shouldn't need to fiddle with in this brave new SwiftUI world right?
I changed your example a little and now background images changed with animation. But I can't suggest, why the same things don't work either for Color or for Image. So, here is my working example, maybe it will push you in right direction:
struct AnimatedBackground: View {
@State private var index = 0
private let images: [Image] = ["trash", "star", "circle", "circle.fill", "square", "cloud"].map{ Image(systemName: $0) }
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
ForEach(images.indices, id: \.self) { imageIndex in
self.images[imageIndex]
.resizable()
.transition(.opacity)
.opacity(imageIndex == self.index ? 1 : 0)
}
}
.onReceive(timer) { _ in
withAnimation {
self.index = self.index < self.images.count - 1 ? self.index + 1 : 0
}
}
}
}
This is interesting and it got me thinking.
You can use a flag, say show. And a ZStack that holds your image object.
And then use transition combined with animation you want in that image.
But, only do that if show is true.
Attach the .onReceive event to the ZStack instead of your image. In that event, toggle the flag show.
@State private var index = 0
@State private var show = true
private let images = (1...8).map { Image("background\($0)") }
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
if self.show {
images[self.index]
.transition(AnyTransition.opacity.animation(.easeInOut(duration: 1.0)))
}
}.onReceive(timer) { _ in
print(self.index)
self.index += 1
self.show.toggle()
if self.index > 4 { self.index = 0 }
}
}
Voila!

Also, as far as I know, Color is not a View.
A Color is a late-binding token: SwiftUI only resolves it to a concrete value just before using it in a given environment.
ref: https://developer.apple.com/documentation/swiftui/color
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With