You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中图片添加抖动动画时出现闪烁问题求助

解决SwiftUI中图片抖动动画闪烁问题

问题描述

我尝试给图片添加简单的抖动效果模拟删除动画,但图片出现闪烁。用普通圆形或其他SwiftUI原生视图时动画正常,使用Apple原生AsyncImage也会出现同样的闪烁问题,相关代码如下:

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

问题原因

闪烁的核心原因:

  1. 同一个isJiggling状态绑定了两个独立动画,导致SwiftUI动画系统冲突,异步加载的图片(如KFImage、AsyncImage)因视图重绘频繁,冲突被放大为闪烁。
  2. randomize方法每次调用都会生成新随机值,动画初始化时的不一致进一步加剧了不稳定。

修复方案

修改后的代码

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())
                    .animation(nil, value: UUID()) // 隔离图片加载时的动画干扰
                    .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
    // 提前生成随机动画时长,避免每次计算都变化
    private let xDuration: TimeInterval
    private let yDuration: TimeInterval

    init(amount: Double) {
        self.amount = amount
        self.xDuration = randomize(interval: 0.07, withVariance: 0.025)
        self.yDuration = randomize(interval: 0.14, withVariance: 0.025)
    }

    func body(content: Content) -> some View {
        content
            .offset(x: isJiggling ? amount : -amount)
            .offset(y: isJiggling ? -amount : amount)
            .animation(
                // 组合x、y轴动画,用不同时长实现自然抖动
                Animation.easeInOut(duration: xDuration)
                    .repeatForever(autoreverses: true)
                    .combined(with: Animation.easeInOut(duration: yDuration)
                        .repeatForever(autoreverses: true)),
                value: isJiggling
            )
            .onAppear {
                isJiggling.toggle()
            }
    }

    private func randomize(interval: TimeInterval, withVariance variance: Double) -> TimeInterval {
         interval + variance * (Double.random(in: 0...1) * 2 - 1) // 生成-1到1的随机值,让时长有正负波动
    }
}

关键修改点

  • 合并动画逻辑:用combined(with:)将x、y轴的动画合并为一个,避免同一状态触发多个动画导致冲突。
  • 稳定随机时长:在init中提前生成动画时长,确保动画过程中参数稳定,减少不必要的重绘。
  • 隔离图片加载动画:给KFImage添加animation(nil),防止图片加载完成时的布局变化干扰抖动动画。
  • 优化随机值生成:调整randomize方法,生成正负波动的随机时长,让抖动效果更自然。

内容的提问来源于stack exchange,提问作者Ahmed Zaidan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 08:44:53