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

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或逻辑
    }
}

关键逻辑梳理

  1. 首次打开应用时,先请求相册权限,用户选择「仅使用选中的照片」后,系统会弹出选择器让用户选授权的照片,这个过程不会触发你的代理。
  2. 授权完成后,你的代码会自动唤起自定义的PHPickerViewController,此时用户选择照片就会正常触发didFinishPicking方法。
  3. 后续用户再打开选择器时,直接使用自定义选择器,无需再走系统权限流程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 07:35:55