PHPickerViewControllerDelegate有限权限模式下didFinishPicking回调失效问题
解决iOS15+有限相册权限下PHPickerViewController代理不触发的问题
问题根源
当用户首次选择「仅使用选中的照片」时,弹出的选择器是系统权限选择器,而非你创建的PHPickerViewController,因此不会触发你的didFinishPicking代理方法。这个系统选择器的作用是让用户授权应用访问哪些照片,而非直接选择聊天附件。授权完成后,你需要重新唤起自己的选择器,让用户从已授权的照片里挑选要发送的附件。
解决方案
1. 先检查相册权限状态
在唤起选择器前,先通过PHPhotoLibrary检查当前权限,判断是否需要处理首次授权后的重选逻辑:
import Photos func showPhotoPicker() { PHPhotoLibrary.requestAuthorization(for: .readWrite) { [weak self] status in DispatchQueue.main.async { guard let self = self else { return } switch status { case .authorized, .limited: // 已有权限,唤起自定义选择器 self.presentCustomPHPicker() case .denied, .restricted: // 权限被拒绝,提示用户去设置开启 self.showPermissionDeniedAlert() case .notDetermined: // 理论上不会走到这里,因为已经调用了requestAuthorization break @unknown default: break } } } } private func presentCustomPHPicker() { var configuration = PHPickerConfiguration(photoLibrary: .shared()) configuration.filter = PHPickerFilter.any(of: [.images]) configuration.preferredAssetRepresentationMode = .compatible configuration.selection = .ordered configuration.selectionLimit = 8 configuration.preselectedAssetIdentifiers = self.selectedAssetIdentifiers let picker = PHPickerViewController(configuration: configuration) picker.delegate = self // 建议用当前控制器present,而非rootViewController,避免层级问题 self.present(picker, animated: true) } private func showPermissionDeniedAlert() { let alert = UIAlertController(title: "需要相册权限", message: "请在设置中开启相册权限,以便选择聊天附件", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "取消", style: .cancel)) alert.addAction(UIAlertAction(title: "去设置", style: .default, handler: { _ in UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) })) self.present(alert, animated: true) }
2. 修正代理方法的问题
你的代理实现中有两个潜在问题:
- 强制解包
assetIdentifier:在有限权限下,部分PHPickerResult可能无法获取到assetIdentifier(比如iCloud照片未同步),需要用安全的方式处理。 - 同步请求图片:
options.isSynchronous = true会阻塞主线程,导致UI卡顿,改成异步请求更合理。
修正后的代理方法:
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) // 更新选中记录,避免强制解包 let existingSelection = self.selection var newSelection = [String: PHPickerResult]() for result in results { guard let identifier = result.assetIdentifier else { continue } newSelection[identifier] = existingSelection[identifier] ?? result } self._selection = newSelection self.selectedAssetIdentifiers = newSelection.keys.map { $0 } self.selectedAssetIdentifierIterator = self.selectedAssetIdentifiers.makeIterator() // 异步处理图片加载 let imageManager = PHImageManager.default() let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: self.selectedAssetIdentifiers, options: nil) fetchResult.enumerateObjects { [weak self] asset, index, stop in guard let self = self else { return } let options = PHImageRequestOptions() options.deliveryMode = .highQualityFormat options.resizeMode = .fast options.isNetworkAccessAllowed = true options.isSynchronous = false // 改成异步 imageManager.requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, info in guard let image = image else { // 处理图片加载失败的情况 return } DispatchQueue.main.async { let handled = self.attachmentManager.handleInput(of: image) if !handled { // 处理添加附件失败的逻辑 } } } } }
3. 处理权限变更后的场景
如果用户之后在设置中修改了相册权限(比如从「仅使用选中的照片」改成「所有照片」,或反之),建议在应用进入前台时重新检查权限,确保选择器行为正常:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in // 根据权限状态更新UI或逻辑 } }
关键逻辑梳理
- 首次打开应用时,先请求相册权限,用户选择「仅使用选中的照片」后,系统会弹出选择器让用户选授权的照片,这个过程不会触发你的代理。
- 授权完成后,你的代码会自动唤起自定义的
PHPickerViewController,此时用户选择照片就会正常触发didFinishPicking方法。 - 后续用户再打开选择器时,直接使用自定义选择器,无需再走系统权限流程。
内容的提问来源于stack exchange,提问作者Msencenb
相关产品推荐
相关产品推荐

