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

如何让SwiftUI列表仅首次显示的缩略图加载时执行动画

SwiftData缩略图加载动画优化方案

核心思路

要实现仅首次可见区域的缩略图执行淡入动画,后续滚动(包括回滚)不再重复动画,需同时控制全局首次加载状态和单个缩略图的动画执行标记:

  • 用全局状态标记首次加载阶段是否结束,完成后所有新出现的缩略图不再触发动画
  • 给每个图片数据添加已执行动画的标记,避免回滚时已显示过的缩略图重复执行动画

具体实现步骤

  1. 给数据模型添加动画标记
    在SwiftData的图片模型中新增布尔属性,记录该图片是否已经执行过加载动画。

  2. 父视图控制全局加载状态
    在ImageLibraryView中添加状态变量,标记首次加载阶段是否结束。

  3. 缩略图视图根据条件触发动画
    在ThumbnailView中,仅当全局处于首次加载阶段且该图片未执行过动画时,才触发淡入动画。

代码示例

1. SwiftData模型定义

import SwiftData

@Model
class ImageAsset {
    let id: UUID
    let imageFilePath: String
    var hasCompletedAnimation: Bool // 标记是否已执行过加载动画
    
    init(id: UUID = UUID(), imageFilePath: String, hasCompletedAnimation: Bool = false) {
        self.id = id
        self.imageFilePath = imageFilePath
        self.hasCompletedAnimation = hasCompletedAnimation
    }
}

2. 缩略图视图实现

struct ThumbnailView: View {
    @Bindable var asset: ImageAsset
    let shouldAnimate: Bool
    
    private var thumbnailImage: UIImage? {
        UIImage(contentsOfFile: asset.imageFilePath)
    }
    
    var body: some View {
        Group {
            if let image = thumbnailImage {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFill()
            } else {
                Color.gray.opacity(0.3)
            }
        }
        .frame(width: 100, height: 100)
        .clipped()
        .opacity(shouldAnimate ? 0 : 1)
        .animation(shouldAnimate ? .easeIn(duration: 0.3) : nil, value: shouldAnimate)
        .onAppear {
            guard shouldAnimate else { return }
            // 动画触发后标记该图片已完成动画
            DispatchQueue.main.async {
                withAnimation {
                    asset.hasCompletedAnimation = true
                }
            }
        }
    }
}

3. 图片库列表视图实现

struct ImageLibraryView: View {
    @Query private var imageAssets: [ImageAsset]
    @State private var isFirstLoadFinished = false
    @State private var firstLoadAssetIDs = Set<UUID>()
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3)) {
                ForEach(imageAssets) { asset in
                    let animateCondition = !isFirstLoadFinished && !asset.hasCompletedAnimation && !firstLoadAssetIDs.contains(asset.id)
                    ThumbnailView(asset: asset, shouldAnimate: animateCondition)
                        .onAppear {
                            guard !isFirstLoadFinished else { return }
                            firstLoadAssetIDs.insert(asset.id)
                            // 延迟标记首次加载完成,确保可见区域的缩略图都有机会触发动画
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
                                isFirstLoadFinished = true
                            }
                        }
                }
            }
            .padding()
        }
    }
}

关键细节说明

  • 全局状态isFirstLoadFinished:首次加载完成后,所有后续出现的缩略图直接跳过动画逻辑
  • 模型属性hasCompletedAnimation:确保回滚时已显示过的缩略图不会重复执行动画
  • 延迟标记首次加载完成:LazyVGrid的单元格按需加载,首次可见单元格不会同时触发onAppear,0.6秒延迟可保证大部分可见缩略图完成动画后再关闭全局动画开关

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:25:14