Is it possible to change image with fade animation using same Image? (SwiftUI)
Asked Answered
P

2

9

According to my logic, on tap gesture to the image it should be changed with fade animation, but actual result is that image changes without animation. Tested with Xcode 11.3.1, Simulator 13.2.2/13.3 if it is important.

P.S. Images are named as "img1", "img2", "img3", etc.

enum ImageEnum: String {
    case img1
    case img2
    case img3

    func next() -> ImageEnum {
        switch self {
            case .img1: return .img2
            case .img2: return .img3
            case .img3: return .img1
        }
    }
}
struct ContentView: View {
    @State private var img = ImageEnum.img1
    var body: some View {
        Image(img.rawValue)
            .onTapGesture {
                withAnimation {
                    self.img = self.img.next()
                }
            }
    }
}
Pavement answered 15/3, 2020 at 1:31 Comment(0)
C
8

Update: re-tested with Xcode 13.3 / iOS 15.4

Here is possible approach using one Image (for demo some small modifications made to use default images). The important changes marked with comments.

demo

enum ImageEnum: String {
    case img1 = "1.circle"
    case img2 = "2.circle"
    case img3 = "3.circle"

    func next() -> ImageEnum {
        switch self {
            case .img1: return .img2
            case .img2: return .img3
            case .img3: return .img1
        }
    }
}
struct QuickTest: View {
    @State private var img = ImageEnum.img1
    @State private var fadeOut = false
    var body: some View {
        Image(systemName: img.rawValue).resizable().frame(width: 300, height: 300)
            .opacity(fadeOut ? 0 : 1)
            .animation(.easeInOut(duration: 0.25), value: fadeOut)    // animatable fade in/out
            .onTapGesture {
                self.fadeOut.toggle()                 // 1) fade out

                // delayed appear
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
                    withAnimation {
                        self.img = self.img.next()    // 2) change image
                        self.fadeOut.toggle()         // 3) fade in
                    }
                }
            }
    }
}
Contribution answered 15/3, 2020 at 8:0 Comment(2)
It works, but why if I change i.e. value of Text using withAnimation it changes smoothly without extra modifiers and changing Image requires some logic?Pavement
Value of Text, ie string data, is animatable itself, but name of image is not.Contribution
H
15

I haven't tested this code, but something like this might be a bit simpler:

struct ContentView: View {
    @State private var img = ImageEnum.img1
    var body: some View {
        Image(img.rawValue)
            .id(img.rawValue)
            .transition(.opacity.animation(.default))
            .onTapGesture {
                withAnimation {
                    self.img = self.img.next()
                }
            }
    }
}

The idea is tell SwiftUI to redraw the Image whenever the filename of the asset changes by binding the View's identity to the filename itself. When the filename changes, SwiftUI assumes the View changed and a new View must be added to the view hierarchy, thus the transition is triggered.

Hayes answered 2/1, 2022 at 18:58 Comment(1)
.id is the trick to transition from old to new dataNanny
C
8

Update: re-tested with Xcode 13.3 / iOS 15.4

Here is possible approach using one Image (for demo some small modifications made to use default images). The important changes marked with comments.

demo

enum ImageEnum: String {
    case img1 = "1.circle"
    case img2 = "2.circle"
    case img3 = "3.circle"

    func next() -> ImageEnum {
        switch self {
            case .img1: return .img2
            case .img2: return .img3
            case .img3: return .img1
        }
    }
}
struct QuickTest: View {
    @State private var img = ImageEnum.img1
    @State private var fadeOut = false
    var body: some View {
        Image(systemName: img.rawValue).resizable().frame(width: 300, height: 300)
            .opacity(fadeOut ? 0 : 1)
            .animation(.easeInOut(duration: 0.25), value: fadeOut)    // animatable fade in/out
            .onTapGesture {
                self.fadeOut.toggle()                 // 1) fade out

                // delayed appear
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
                    withAnimation {
                        self.img = self.img.next()    // 2) change image
                        self.fadeOut.toggle()         // 3) fade in
                    }
                }
            }
    }
}
Contribution answered 15/3, 2020 at 8:0 Comment(2)
It works, but why if I change i.e. value of Text using withAnimation it changes smoothly without extra modifiers and changing Image requires some logic?Pavement
Value of Text, ie string data, is animatable itself, but name of image is not.Contribution

© 2022 - 2024 — McMap. All rights reserved.