Swift UI Image flickers when animating

Swift UI Image flickers when animating

  

Im trying to apply a simple shaking effect to an image to simulate a deleting animation. When I do this the image flickers. When I try this with a normal circle or any native swift ui view it works just fine. Why does the image flicker?

I have tried this with Apple's native AsyncImage and the same bug happens.

import SwiftUI
import Kingfisher

struct PinnedChatView: View {    
    var body: some View {
        ZStack {
            VStack(spacing: 8){
                KFImage(URL(string: "https://letsenhance.io/static/8f5e523ee6b2479e26ecc91b9c25261e/1015f/MainAfter.jpg"))
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 100, height: 100)
                    .clipShape(Circle())
                    .jiggle(isEnabled: true)
             }
         }
    }
}
extension View {
    @ViewBuilder
    func jiggle(amount: Double = 4, isEnabled: Bool = true) -> some View {
        if isEnabled {
            modifier(JiggleViewModifier(amount: amount))
        } else {
            self
        }
    }
}

private struct JiggleViewModifier: ViewModifier {
    let amount: Double

    @State private var isJiggling = false

    func body(content: Content) -> some View {
        content
            .offset(x: isJiggling ? 3 : -3)
            .offset(y: isJiggling ? -3 : 3)
            .animation(
                .easeInOut(duration: randomize(interval: 0.07, withVariance: 0.025))
                .repeatForever(autoreverses: true),
                value: isJiggling
            )
            .animation (
                .easeInOut(duration: randomize(interval: 0.14, withVariance: 0.025))
                .repeatForever(autoreverses: true),
                value: isJiggling
            )
            .onAppear {
                isJiggling.toggle()
            }
    }

    private func randomize(interval: TimeInterval, withVariance variance: Double) -> TimeInterval {
         interval + variance * (Double.random(in: 500...1_000) / 500)
    }
}

Answer

Because it is animating the other modifiers like resize, aspectRatio and so on.

A quick workaround for this is to use task modifier instead of onAppear inside the JiggleViewModifier:

.task {
    isJiggling.toggle()
}

Demo


Alternative method

You can also achieve the same result with a little bit of logic change in the body of the JiggleViewModifier:

@State private var x = 0.0
@State private var y = 0.0

func body(content: Content) -> some View {
    content
        .offset(x: x, y: y)
        .onAppear {
            let animation = Animation
                .easeInOut(duration: randomize(interval: 0.07, withVariance: 0.025))
                .repeatForever(autoreverses: true)

            withAnimation(animation) {
                x = amount
                y = -amount
            }
        }
}

P.S.: You originally forgot to use the amount parameter.

© 2024 Dagalaxy. All rights reserved.