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