Search code examples
iosswiftswiftuixcode11

Crossfading Images in SwiftUI doesn't seem to work


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?


Solution

  • 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
                }
            }
        }
    
    }