PhotosPickerItem的loadTransferable进度监听失效,求解决方案
解决PhotosPickerItem.loadTransferable进度监听失效问题
核心问题分析
你遇到的监听闭包不执行,主要是两个原因:
- Progress对象生命周期过短:原代码中
progress是局部变量,processSelectedVideo方法执行完毕后,progress会被系统释放,导致KVO监听失去目标。 - 线程调度问题:Progress的进度更新默认在后台线程触发,若未指定主线程队列,回调可能无法正常执行或无法更新UI。
解决方案一:用ObservableObject持有Progress并修复KVO监听
通过ObservableObject统一管理加载状态和Progress对象,确保其生命周期与View绑定,同时指定主线程执行回调。
import SwiftUI import Photos import PhotosUI class VideoLoader: ObservableObject { @Published var progressValue: Double = 0.0 private var progress: Progress? private var observation: NSKeyValueObservation? func loadVideo(from item: PhotosPickerItem) { // 清理之前的监听 observation?.invalidate() observation = nil progress = nil progress = item.loadTransferable(type: Data.self) { [weak self] result in guard let self = self else { return } // 处理加载结果 switch result { case .success(let data): if let data = data { // 这里处理视频数据,比如转存到本地或解码 } case .failure(let error): print("视频加载失败: \(error.localizedDescription)") } // 加载完成后清理资源 self.observation?.invalidate() self.observation = nil self.progress = nil } guard let progress = progress else { return } // 指定主线程队列执行回调,确保UI更新正常 observation = progress.observe(\.fractionCompleted, options: [.new], queue: .main) { [weak self] _, change in if let newValue = change.newValue { self?.progressValue = newValue print("当前加载进度: \(newValue)") } } progress.resume() } } struct ContentView: View { @State private var selectedVideo: PhotosPickerItem? @StateObject private var videoLoader = VideoLoader() var body: some View { VStack(spacing: 20) { PhotosPicker(selection: $selectedVideo, matching: .videos) { Text("选择相册视频") .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } ProgressView(value: videoLoader.progressValue) .frame(height: 8) .padding(.horizontal, 20) } .onChange(of: selectedVideo) { newItem in if let item = newItem { videoLoader.loadVideo(from: item) } } } }
解决方案二:使用Combine监听Progress(更符合SwiftUI响应式风格)
利用Combine框架监听Progress的属性变化,替代KVO,代码更简洁且更适配SwiftUI生态。
只需修改VideoLoader类,添加Combine订阅逻辑:
import Combine class VideoLoader: ObservableObject { @Published var progressValue: Double = 0.0 private var progress: Progress? private var cancellable: AnyCancellable? func loadVideo(from item: PhotosPickerItem) { // 清理之前的订阅 cancellable?.cancel() cancellable = nil progress = nil progress = item.loadTransferable(type: Data.self) { [weak self] result in guard let self = self else { return } // 处理加载结果逻辑同上 self.cancellable?.cancel() self.cancellable = nil self.progress = nil } guard let progress = progress else { return } // 通过Combine监听进度变化,切换到主线程更新UI cancellable = progress.publisher(for: \.fractionCompleted) .receive(on: DispatchQueue.main) .sink { [weak self] value in self?.progressValue = value print("当前加载进度: \(value)") } progress.resume() } }
关键优化点说明
- 持有Progress对象:通过ObservableObject的属性持有Progress,避免局部变量被提前释放。
- 主线程调度:无论是KVO还是Combine,都指定主线程执行回调,确保进度值能正确更新UI。
- 资源清理:在加载完成或重新加载时,及时销毁旧的监听/订阅,避免内存泄漏。
内容的提问来源于stack exchange,提问作者milesau
相关产品推荐
相关产品推荐

