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

SwiftUI中仅更新footerView,避免LottieAnimationView重复播放

解决ScrollView滚动时Lottie动画重复播放的问题

问题原因

你当前的代码通过if let message = footer.message, messageBannerVisisbility条件渲染footerMessageView,每次messageBannerVisisbility从false切换为true时,footerMessageView会被重新创建,内部的Lottie动画也会随之重置并重新播放。

解决方案

核心思路有两点:

  1. 避免通过条件渲染销毁/重建footerMessageView,改用透明度、偏移量控制显示隐藏,保留视图实例和其内部状态。
  2. 记录动画是否已经播放过,确保仅在页面首次加载时触发一次播放。

方法一:用状态变量控制动画播放时机

  1. 添加状态变量记录动画播放状态:
@State var messageBannerVisisbility: Bool = false
@State private var hasAnimationPlayed = false // 新增:标记动画是否已播放
  1. 修改footerView的显示逻辑,替换条件渲染为透明度+偏移量控制:
@ViewBuilder private func footerView(footer: SignupPageV2Footer) -> some View {
    VStack(alignment: .leading, spacing: 0) {
        // 移除条件判断中的messageBannerVisisbility,改用透明度/偏移控制显示隐藏
        if let message = footer.message {
            footerMessageView(from: message, hasAnimationPlayed: $hasAnimationPlayed)
                .opacity(messageBannerVisisbility ? 1 : 0)
                .offset(y: messageBannerVisisbility ? 0 : 20) // 模拟原transition的底部移入效果
                .animation(.easeIn(duration: 0.3), value: messageBannerVisisbility)
        }
        
        // 下方按钮区域代码保持不变
        HStack(alignment: .center, spacing: 8) {
            Label(footer.signupAction.info.stripHTMLTags)
                .textColor(.secondary)
                .frame(width: 115, alignment: .leading)
            
            LozengeButton(title: footer.signupAction.label, isLoading: $viewModel.isPaymentInProgress) {
                viewModel.startPayment()
            }
            .accessibility(identifier: "subscribe_button")
        }
        .padding([.horizontal, .top], 16)
        .padding(.bottom, 8)
        .background(Color.white)
    }
    .background(Color.white.edgesIgnoringSafeArea(.bottom).elevation(.LightBackground.small))
}
  1. 更新footerMessageView,仅在首次显示时播放动画:
@ViewBuilder private func footerMessageView(from message: SignupPageV2FooterMessage, hasAnimationPlayed: Binding<Bool>) -> some View {
    message.build { deeplink in
        viewModel.handleDeeplink(deeplink)
    } processEvent: { event in
        viewModel.handleEvent(event)
    }
    .onAppear {
        guard !hasAnimationPlayed.wrappedValue else { return }
        // 这里根据你实际的Lottie实现调用播放方法
        // 例如使用LottieSwiftUI的LottieView时:
        // lottieView.play()
        hasAnimationPlayed.wrappedValue = true
    }
}

方法二:用ObservableObject管理Lottie播放状态(更稳定)

如果你的Lottie实现需要更持久的状态管理,可以创建一个单独的播放器管理器:

class LottieAnimationManager: ObservableObject {
    private(set) var hasPlayed = false
    
    func playAnimationIfNeeded(_ lottieView: LottieAnimationView) {
        guard !hasPlayed else { return }
        lottieView.play()
        hasPlayed = true
    }
}

然后在footerMessageView中使用:

@ViewBuilder private func footerMessageView(from message: SignupPageV2FooterMessage) -> some View {
    @StateObject private var animationManager = LottieAnimationManager()
    
    message.build { deeplink in
        viewModel.handleDeeplink(deeplink)
    } processEvent: { event in
        viewModel.handleEvent(event)
    }
    .onAppear {
        // 获取你的LottieAnimationView实例并调用播放方法
        if let lottieView = self.findLottieView() { // 替换为你实际获取Lottie视图的逻辑
            animationManager.playAnimationIfNeeded(lottieView)
        }
    }
}

同时记得按照方法一的方式修改footerView的显示逻辑,避免视图重建。

关键说明

  • 改用透明度和偏移量控制显示隐藏,footerMessageView的实例会被保留,不会随滚动销毁重建,Lottie的状态也会被维持。
  • 新增的状态变量确保动画仅在页面首次加载时播放一次,后续滚动显示/隐藏时不会触发重新播放。

内容的提问来源于stack exchange,提问作者Paras Gorasiya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 21:30:55