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()
}
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.