SwiftUI使用Combine加载App内图片时卡在进度视图问题排查
故障原因与修复方案
核心问题汇总
- 首次渲染
ApodImageView时,vm.apodData还未加载完成,你传入了url和thumbnailUrl都为空的默认ApodModel,而@StateObject仅会在视图首次创建时初始化一次,后续父视图传入的apodData更新不会触发ViewModel重新初始化,导致始终请求无效空链接,不会返回任何结果 - ViewModel初始化时序不合理,先触发图片请求再添加订阅,可能错过请求返回的回调
- 缺少网络请求失败兼容逻辑,请求出错后加载状态不会自动结束,永久卡住进度条
修复代码
1. 调整图片网络层代码
新增主动触发请求的方法,添加错误兼容逻辑:
import Foundation import SwiftUI import Combine class ApodImageService: ObservableObject { @Published var image: UIImage? private var imageSubscription: AnyCancellable? private var apodData: ApodModel init(apodData: ApodModel) { self.apodData = apodData } // 新增公开请求方法,外部订阅完成后再调用 func fetchImage() { guard let url = URL(string: apodData.thumbnailUrl ?? apodData.url) else { // 无效URL直接返回空,结束加载状态 image = nil return } imageSubscription = NetworkingManager.download(url: url) .tryMap({ data -> UIImage? in return UIImage(data: data) }) .catch { error -> Just<UIImage?> in // 捕获请求错误,返回空,保证订阅能收到回调 print("图片加载失败: \(error.localizedDescription)") return Just(nil) } .sink(receiveCompletion: NetworkingManager.handleCompletion, receiveValue: { [weak self] returnedImage in self?.image = returnedImage self?.imageSubscription?.cancel() }) } // 新增数据更新方法,外部传入新的ApodModel时调用 func updateApodData(_ newData: ApodModel) { imageSubscription?.cancel() image = nil apodData = newData fetchImage() } }
2. 调整图片ViewModel代码
修正初始化时序,新增数据更新入口:
import Foundation import SwiftUI import Combine class ApodImageViewModel: ObservableObject { @Published var image: UIImage? @Published var isLoading: Bool = false private let dataService: ApodImageService private var cancellables = Set<AnyCancellable>() init(apodData: ApodModel) { self.dataService = ApodImageService(apodData: apodData) self.isLoading = true addSubscribers() self.dataService.fetchImage() } private func addSubscribers() { dataService.$image .sink { [weak self] returnedImage in self?.isLoading = false self?.image = returnedImage } .store(in: &cancellables) } // 对外暴露数据更新方法 func updateApodData(_ newData: ApodModel) { isLoading = true dataService.updateApodData(newData) } }
3. 调整图片视图代码
监听apodData变化,自动触发重新加载:
import SwiftUI struct ApodImageView: View { @StateObject var vm: ApodImageViewModel private let apodData: ApodModel init(apodData: ApodModel) { self.apodData = apodData _vm = StateObject(wrappedValue: ApodImageViewModel(apodData: apodData)) } var body: some View { ZStack { if let image = vm.image { Image(uiImage: image) .resizable() .scaledToFit() } else if vm.isLoading { ProgressView() } else { Image(systemName: "questionmark") .foregroundColor(Color.theme.secondaryText) } } .frame(maxWidth: .infinity, maxHeight: 250) .cornerRadius(10) // 监听apodData变化,更新ViewModel数据 .onChange(of: apodData) { newData in vm.updateApodData(newData) } } }
可选优化
如果你的ApodModel没有实现Equatable,onChange无法正常监听,可以等主视图的vm.apodData加载完成后再渲染ApodImageView,避免首次传入空模型:
// 主视图中替换ApodImageView的初始化代码 if let apodData = vm.apodData { ApodImageView(apodData: apodData) } else { ProgressView() }
内容的提问来源于stack exchange,提问作者Jungwon
相关产品推荐
相关产品推荐

