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

PHPicker二次打开取消选中照片时所有UIImage返回nil问题求助

问题分析与解决方案

核心问题

  1. 错误的状态同步逻辑:PHPicker每次返回的results是当前所有选中的照片集合,并非本次操作新增/取消的单个照片。原遍历results增删图片的逻辑,在第二次取消选中时完全不适用。
  2. 不可靠的图片对比方式:用pngData()判断图片重复,不仅性能低下(每张图都要生成png数据),同一照片在不同加载场景下生成的png数据也可能不一致,导致匹配失败。
  3. 异步加载与picker关闭时机冲突:遍历加载图片的同时直接关闭picker,异步回调可能在picker销毁后执行,引发self!.parent异常,甚至导致图片加载失败。

修正后的代码

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    guard let parent = self.parent else {
        picker.dismiss(animated: true)
        return
    }
    
    // 1. 获取当前选中的所有assetIdentifier
    let currentSelectedIdentifiers = results.compactMap(\.assetIdentifier)
    let previousIdentifiers = parent.imageIdentifierArray
    
    // 2. 找出新增和需要移除的identifier
    let addedIdentifiers = Set(currentSelectedIdentifiers).subtracting(previousIdentifiers)
    let removedIdentifiers = Set(previousIdentifiers).subtracting(currentSelectedIdentifiers)
    
    // 3. 先处理移除操作:根据identifier找到对应的图片删除
    DispatchQueue.main.async {
        parent.selectedPhotos.removeAll { photo in
            // 建议提前给图片绑定identifier,比如用字典存储映射关系 [UIImage: String]
            guard let photoIdentifier = parent.photoIdentifierMap[photo] else { return false }
            return removedIdentifiers.contains(photoIdentifier)
        }
    }
    
    // 4. 处理新增的照片:只加载新增的项
    for result in results {
        guard let identifier = result.assetIdentifier, addedIdentifiers.contains(identifier) else {
            continue
        }
        
        if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
            result.itemProvider.loadObject(ofClass: UIImage.self) { [weak parent] newImage, error in
                guard let parent = parent else { return }
                
                if let error = error {
                    print("加载图片失败:\(error.localizedDescription)")
                    return
                }
                
                guard let image = newImage as? UIImage else {
                    print("无法将资源转为UIImage")
                    return
                }
                
                DispatchQueue.main.async {
                    parent.selectedPhotos.append(image)
                    // 存储图片与identifier的映射,方便后续删除
                    parent.photoIdentifierMap[image] = identifier
                }
            }
        } else {
            print("无法加载该资源")
        }
    }
    
    // 5. 更新存储的identifier数组
    parent.imageIdentifierArray = currentSelectedIdentifiers
    
    // 6. 关闭picker,确保必要逻辑已启动
    DispatchQueue.main.async {
        parent.isPresented = false
    }
}

关键改动说明

  • 用assetIdentifier做唯一标识:照片的assetIdentifier是系统分配的唯一ID,比对比图片数据可靠得多,必须用它管理选中状态。
  • 区分新增/移除项:通过对比前后的identifier集合,只加载新增图片,直接移除需取消的项,避免无效加载操作。
  • 优化数据结构:新增字典photoIdentifierMap: [UIImage: String]绑定图片与identifier,删除时可快速匹配。
  • 安全的异步回调:用[weak parent]避免循环引用,关闭picker的操作放到主队列异步执行,确保异步加载逻辑已启动。
  • 移除强制解包:用guard let替代self!.parent,避免空指针崩溃。

额外提示

如果不想维护映射字典,也可以把selectedPhotos改成[String: UIImage](key为assetIdentifier):

  • 新增时直接selectedPhotos[identifier] = image
  • 删除时直接selectedPhotos.removeValue(forKey: identifier)
  • 展示图片时取Array(selectedPhotos.values)即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 06:42:55