自定义ImagePickerDelegate扩展后didFinishPickingMediaWithInfo不触发问题
你遇到的这个问题,核心在于Swift协议扩展和Objective-C Runtime的兼容性问题,结合那个警告其实已经给了你关键线索,咱们一步步拆解:
为什么协议扩展的方法不触发?
UIImagePickerControllerDelegate是一个@objc协议,它的所有可选方法都是通过Objective-C Runtime来调用的。而Swift的协议扩展是Swift独有的特性——Objective-C完全看不到协议扩展里的默认实现。
当你把遵守ImagePickerDelegate的ViewController设置为UIImagePicker的代理时,Objective-C Runtime会去检查这个ViewController对象是否实现了imagePickerController(_:didFinishPickingMediaWithInfo:)方法。但你只是在协议扩展里写了这个方法,ViewController本身并没有真正实现它(只是遵守了协议),所以Runtime找不到这个方法,自然不会触发回调。
那个警告也明确说明了:你在协议扩展里的方法没有标记@objc,无法满足@objc协议的可选要求——哪怕你给ImagePickerDelegate加上@objc标记,扩展里的方法默认也不会被暴露给Objective-C,因为协议扩展的方法本质上是Swift层面的“默认实现”,不是对象的实例方法。
为什么用具体类就可以?
具体类(比如你参考的方案)通常会继承自NSObject,而NSObject是Objective-C体系的基类。当这个类实现了UIImagePicker的代理方法时,这些方法会被Objective-C Runtime识别并注册,UIImagePicker就能正常找到并调用这些方法了。
修复方案:用代理类封装通用逻辑
推荐你创建一个专门的代理处理类,把UIImagePicker的通用代理逻辑封装在这里,然后通过闭包或者自定义回调把结果传递给ViewController,这样既避免了重复代码,又能保证回调正常触发。
示例代码:
1. 创建ImagePicker代理处理类
@objc class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { // 用闭包传递结果 private let resultHandler: (Result<(localIdentifier: String?, imageURL: URL?), Error>) -> Void init(resultHandler: @escaping (Result<(localIdentifier: String?, imageURL: URL?), Error>) -> Void) { self.resultHandler = resultHandler super.init() } // 通用的didFinish实现 func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { // 提取你需要的信息 if let localId = info[.phAssetLocalIdentifier] as? String { resultHandler(.success((localId, nil))) } else if let imageUrl = info[.imageURL] as? URL { resultHandler(.success((nil, imageUrl))) } else { let error = NSError(domain: "ImagePicker", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法获取图片数据"]) resultHandler(.failure(error)) } picker.dismiss(animated: true) } // 处理取消事件 func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) let error = NSError(domain: "ImagePicker", code: 0, userInfo: [NSLocalizedDescriptionKey: "用户取消选择"]) resultHandler(.failure(error)) } }
2. 在ViewController里使用
class YourViewController: UIViewController { // 持有handler,避免被提前释放 private var imagePickerHandler: ImagePickerHandler? func showPhotoPicker() { let picker = UIImagePickerController() picker.sourceType = .photoLibrary // 创建handler,处理回调 imagePickerHandler = ImagePickerHandler { [weak self] result in guard let self = self else { return } switch result { case .success(let data): if let localId = data.localIdentifier { // 调用你自己的相机成功逻辑 self.successActionFromCamera(with: localId, picker: picker) } else if let imageUrl = data.imageURL { // 调用你自己的相册成功逻辑 self.successActionFromPhotoLibrary(with: imageUrl, picker: picker) } case .failure(let error): // 处理错误,比如提示用户 print(error.localizedDescription) } // 用完可以置空handler self.imagePickerHandler = nil } picker.delegate = imagePickerHandler present(picker, animated: true) } // 你自己定义的业务方法 func successActionFromCamera(with localIdentifier: String, picker: UIImagePickerController) { // 相机返回后的业务逻辑 } func successActionFromPhotoLibrary(with imageURL: URL, picker: UIImagePickerController) { // 相册返回后的业务逻辑 } }
总结
Swift协议扩展虽然能帮我们减少重复代码,但在处理Objective-C的@objc协议时会有局限性——因为Objective-C Runtime无法识别协议扩展里的默认实现。用具体的NSObject子类作为代理,是目前最稳妥的兼容方案,既能封装通用逻辑,又能保证回调正常触发。
内容的提问来源于stack exchange,提问作者Jeff Zhang

