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

SwiftUI结合HorizonCalendar时如何用DispatchQueue解决图片加载滚动卡顿

解决HorizonCalendar滚动时图片加载卡顿的异步实现方案

核心问题分析

当前代码在dayItemProvider中同步调用getFirstPhotoFromGallery加载图片,Core Data查询、图片解码压缩等操作都在主线程执行,直接导致滚动时UI阻塞卡顿。SwiftUI是状态驱动框架,无法直接在闭包内通过DispatchQueue异步更新UI,需要结合ObservableObject管理图片加载状态。

具体实现步骤

- 创建异步图片加载器(ObservableObject)

这个类负责在后台线程完成图片加载逻辑,加载完成后通过@Published属性通知SwiftUI更新视图:

import SwiftUI
import UIKit

class AsyncImageLoader: ObservableObject {
    @Published var image: UIImage?
    
    func loadImage(for dayComponents: DateComponents) {
        // 后台线程执行耗时操作
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            let loadedImage = self?.fetchImage(fromDay: dayComponents)
            // 回到主线程更新UI状态
            DispatchQueue.main.async {
                self?.image = loadedImage
            }
        }
    }
    
    private func fetchImage(fromDay: DateComponents) -> UIImage? {
        guard let freshDate = Calendar.current.date(from: fromDay) else {
            return nil
        }
        
        let dailyData = theDailyData.first(where: { $0.date == freshDate })
        guard let targetDailyData = dailyData,
              let photoData = thePhotoData.first(where: { $0.belongTo == targetDailyData.theID })?.thePhotoData,
              let compressedImageData = UIImage(data: photoData)?.jpegData(compressionQuality: 0.25),
              let compressedImage = UIImage(data: compressedImageData) else {
            return nil
        }
        return compressedImage
    }
}

- 修改日历日项的View实现

在dayItemProvider中使用封装好的异步加载View,初始不显示图片,等待加载完成后自动更新:

func createCalendar() {
    let calendar = Calendar.current
    
    let startDate = calendar.date(from: DateComponents(year: 2023))!
    let endDate = calendar.date(from: DateComponents(year: 2023, month: 12, day: 31))!
    
    let content = CalendarViewContent(calendar: calendar,
                                      visibleDateRange: startDate...endDate,
                                      monthsLayout: .vertical(options: VerticalMonthsLayoutOptions()))
        .interMonthSpacing(25)
        .dayItemProvider { day in
            // 为每个日项创建独立的异步加载View
            AsyncDayView(day: day)
                .calendarItemModel
        }
        .interMonthSpacing(24)
        .verticalDayMargin(8)
        .horizontalDayMargin(8)
    
    // 后续视图添加、约束代码保持不变...
}

// 封装带异步加载逻辑的日项View
struct AsyncDayView: View {
    let day: Day
    @StateObject private var imageLoader = AsyncImageLoader()
    
    var body: some View {
        ZStack {
            if let image = imageLoader.image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .cornerRadius(7)
                    .opacity(0.4)
            }
            Text("\(day.day)")
                .font(.system(size: 17, weight: .semibold))
                .foregroundColor(Color(UIColor(named: "Font Color")!))
        }
        .onAppear {
            // 视图进入屏幕时触发异步加载
            imageLoader.loadImage(for: day.components)
        }
    }
}

- 关键优化点说明

  • 后台线程隔离:所有耗时的Core Data查询、图片处理操作都放在DispatchQueue.global(qos: .userInitiated)后台线程,完全不阻塞主线程。
  • 状态驱动更新:通过@StateObject和@Published实现状态监听,图片加载完成后SwiftUI自动刷新视图,符合框架设计逻辑。
  • 按需加载:利用onAppear在日项视图进入可视区域时才开始加载,减少不必要的资源消耗。

额外优化建议

  • 可以添加图片加载占位符(比如灰色圆角背景),提升滚动时的视觉流畅度。
  • 实现图片内存缓存,避免同一日期图片重复加载,进一步优化滚动性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:24:58