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
相关产品推荐
相关产品推荐

