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

使用LazyVGrid展示大量本地图片时的滚动卡顿性能问题

SwiftUI大量本地图片滚动卡顿优化方案

核心问题分析

当前代码卡顿的主要原因:

  • 图片加载与解码在主线程执行,阻塞UI刷新
  • 直接加载400×400原图,但显示尺寸仅为设备宽度的1/3,冗余像素占用内存并增加渲染开销
  • 无缓存机制,滚动时重复读取文件和解码,重复消耗资源

具体优化步骤

1. 后台线程执行图片加载与解码

将图片读取、缩放解码操作移到后台队列,避免阻塞主线程,同时加入缓存机制减少重复操作:

// 全局缓存,自动处理内存不足时的清理
let imageCache = NSCache<NSString, UIImage>()

struct PhotoThumbnailView: View {
    @State private var image: Image?
    let imageFileURL: String
    let size: CGFloat
    
    init(filePath: String, size: CGFloat) {
        self.imageFileURL = filePath
        self.size = size
    }
    
    func readFromFile() {
        let cacheKey = imageFileURL as NSString
        
        // 优先读取缓存,避免重复加载
        if let cachedImage = imageCache.object(forKey: cacheKey) {
            DispatchQueue.main.async {
                self.image = Image(uiImage: cachedImage)
            }
            return
        }
        
        // 后台线程处理图片加载与缩放
        DispatchQueue.global(qos: .userInitiated).async {
            guard let resizedImage = loadResizedImage(at: imageFileURL, targetSize: CGSize(width: size, height: size)) else {
                DispatchQueue.main.async {
                    self.image = nil
                }
                return
            }
            
            // 存入缓存
            imageCache.setObject(resizedImage, forKey: cacheKey)
            
            // 主线程更新UI
            DispatchQueue.main.async {
                self.image = Image(uiImage: resizedImage)
            }
        }
    }
    
    // 使用ImageIO高效加载并缩放图片,比UIGraphicsImageRenderer性能更优
    private func loadResizedImage(at path: String, targetSize: CGSize) -> UIImage? {
        guard let url = URL(fileURLWithPath: path) else { return nil }
        let options: [CFString: Any] = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceThumbnailMaxPixelSize: max(targetSize.width, targetSize.height),
            kCGImageSourceCreateThumbnailWithTransform: true
        ]
        guard let source = CGImageSourceCreateWithURL(url as CFURL, nil),
              let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {
            return nil
        }
        return UIImage(cgImage: cgImage)
    }
    
    var body: some View {
        ZStack {
            if let img = image {
                img
                    .resizable()
                    .scaledToFill()
                    .frame(width: size, height: size)
                    .clipped() // 防止图片超出容器边界
            } else {
                ProgressView()
                    .frame(width: size, height: size)
            }
        }
        .task {
            readFromFile()
        }
        .onDisappear {
            image = nil
        }
    }
}

2. 动态获取容器尺寸,适配多场景

用GeometryReader获取父容器宽度,计算图片尺寸,避免依赖UIScreen导致的适配问题:

var body: some View {
    ScrollView {
        GeometryReader { geo in
            // 减去2个列间距的宽度,保证布局精准
            let itemSize = (geo.size.width - 2) / 3
            LazyVGrid(columns: Array(repeating: GridItem(.fixed(itemSize), spacing: 1), count: 3),
                      alignment: .center,
                      spacing: 1) {
                ForEach(controller.newAlbums, id: \.self) { album in
                    PhotoThumbnailView(filePath: album, size: itemSize)
                }
            }
        }
    }
    .task {
        controller.loadUserAlbums()
    }
}

3. 精简布局层级

移除原代码中不必要的透明矩形占位视图,减少布局计算开销;用scaledToFill+clipped保证图片尺寸精准且不超出容器。

4. 分批加载数据(可选)

如果4000张图片一次性加载内存压力过大,可以修改controller.loadUserAlbums()实现分批加载,比如先加载100张,滚动到列表底部时再加载下一批,降低初始内存占用。


调试工具建议

开启Xcode以下工具定位性能瓶颈:

  • Debug > View Debugging > Show View Hierarchy:检查是否存在冗余视图层级
  • Debug > Performance > Core Animation:查看是否有掉帧、过度渲染问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 17:42:16