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

SwiftUI:如何在LazyVGrid单元格加载图片且不阻塞主线程?

SwiftUI网格图片加载滚动卡顿优化方案

问题描述

我编写了一个简单的网格组件,用于测试在SwiftUI中正确下载图片并加载到单元格的方法,以避免性能下降。目前无法实现滚动保持响应的效果,目标是在图片加载过程中仍能获得流畅的滚动体验。

测试代码如下:

struct DView: View {
    
    var columnGridItems = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    @State var stuff = [Int]()
    
    
    var body: some View {
        
        NavigationView {
            
            GeometryReader { geo in
                
                let w = geo.size.width / 4
                ScrollView {
                    LazyVGrid(columns: columnGridItems, alignment: .center, spacing: 2) {
                        
                        ForEach(stuff, id:\.self) { s in
                            TestView1(i: s)
                                .frame(width:w, height:w)
                                .clipped()
                        }
                    }
                }
                .background(.red)
                .onAppear {
                    for i in 0..<100 {
                        stuff.append(i)
                    }
                }
            }
            
        }
        .navigationViewStyle(StackNavigationViewStyle())
        
    }
    
}


let localImageUrl = Bundle.main.url(forResource: "f1", withExtension: "jpg")!
let localImage = UIImage(contentsOfFile: localImageUrl.path)!
let f1Image = "https://images.igdb.com/igdb/image/upload/t_original/t7kg7iu6e2sv8h0vjcip.jpg"
let tileImageURL = URL(string: f1Image)!

struct TestView1:View {
    
    @State var i:Int
    
    var body: some View {
        
        ZStack {
            
            Color.black
            
            AsyncImage(url: tileImageURL, scale:2)
//            AsyncImage(url: localImageUrl)
//            Image(uiImage: localImage)
            
            Text("Some Text")
                .foregroundColor(.white)
            
        }
        
    }
}

加载图片时滚动卡顿效果:
滚动卡顿效果


优化方案

卡顿核心原因是图片下载和解码操作阻塞主线程,系统默认AsyncImage在部分场景下会将解码逻辑放在主线程执行,导致滚动时UI响应延迟。以下是针对性优化措施:

1. 自定义图片加载逻辑,后台处理下载与解码

手动实现图片的后台下载、解码流程,确保仅最终图片渲染操作在主线程执行,避免主线程被阻塞。

2. 实现内存缓存

避免重复下载相同图片,减少网络请求和重复解码的性能开销。

3. 绑定视图生命周期管理异步任务

用.task修饰符绑定图片加载任务到视图生命周期,自动管理任务的创建与销毁,避免无效任务占用资源。


修改后的完整代码

import SwiftUI

// 简易内存图片缓存工具
class ImageCache {
    static let shared = ImageCache()
    private let cache = NSCache<NSString, UIImage>()
    
    func get(forKey key: String) -> UIImage? {
        cache.object(forKey: key as NSString)
    }
    
    func set(_ image: UIImage, forKey key: String) {
        cache.setObject(image, forKey: key as NSString)
    }
}

struct DView: View {
    
    var columnGridItems = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    @State var stuff = [Int]()
    
    var body: some View {
        NavigationView {
            GeometryReader { geo in
                let w = geo.size.width / 4
                ScrollView {
                    LazyVGrid(columns: columnGridItems, alignment: .center, spacing: 2) {
                        ForEach(stuff, id:\.self) { _ in
                            TestView1(imageURL: tileImageURL)
                                .frame(width:w, height:w)
                                .clipped()
                        }
                    }
                }
                .background(.red)
                .onAppear {
                    stuff = Array(0..<100)
                }
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

let f1Image = "https://images.igdb.com/igdb/image/upload/t_original/t7kg7iu6e2sv8h0vjcip.jpg"
let tileImageURL = URL(string: f1Image)!

struct TestView1: View {
    let imageURL: URL
    @State private var loadedImage: UIImage?
    
    var body: some View {
        ZStack {
            Color.black
            if let image = loadedImage {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFill()
            }
            Text("Some Text")
                .foregroundColor(.white)
        }
        .task {
            await loadImage()
        }
    }
    
    private func loadImage() async {
        let cacheKey = imageURL.absoluteString
        // 优先读取缓存
        if let cachedImage = ImageCache.shared.get(forKey: cacheKey) {
            loadedImage = cachedImage
            return
        }
        
        // 后台下载图片数据
        guard let (data, _) = try? await URLSession.shared.data(from: imageURL) else { return }
        
        // 后台解码图片,避免阻塞主线程
        await Task.detached(priority: .userInitiated) {
            guard let image = UIImage(data: data) else { return }
            ImageCache.shared.set(image, forKey: cacheKey)
            // 回到主线程更新UI
            await MainActor.run {
                loadedImage = image
            }
        }.value
    }
}

关键优化点说明

  • 后台解码:通过Task.detached将图片解码操作放到后台线程,主线程仅负责最终UI更新,避免滚动时卡顿。
  • 内存缓存:用NSCache实现图片内存缓存,重复单元格无需重复下载解码相同图片。
  • 生命周期绑定:.task修饰符自动在视图出现时启动加载任务,消失时取消任务,避免资源浪费。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 03:01:08