iOS调用presentLimitedLibraryPicker报视图不在窗口层级错误
问题现象
- 实现了带「管理」按钮的图片选择器组件,点击按钮弹出权限操作菜单,包含三个操作项:允许访问所有照片、选择更多照片、保留当前选择
- 图片选择器启动时菜单自动弹出,首次点击「选择更多照片」可正常唤起iOS有限照片权限选择器,功能运行正常
- 关闭有限权限选择器后,再次点击「管理」按钮触发对应操作时,控制台抛出如下错误:
App[39324:2140958] [Presentation] Attempt to present <UIImagePickerController: 0x111012000> on <App.GalleryViewController: 0x11101b200> (from <App.GalleryViewController: 0x11101b200>) whose view is not in the window hierarchy.
相关原实现代码:
- GalleryWorker.swift 权限弹窗创建方法:
func createAlertLimited(delegate: GalleryDisplayLogic?) -> UIAlertController { let alertLimitedController = UIAlertController(title: "The app needs access to your photos", message: "", preferredStyle: .alert) let allPhotosAction = UIAlertAction(title: "Allow access to all pictures", style: .default) { _ in DispatchQueue.main.async { guard let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) else { assertionFailure("Not able to open privacy settings") return } UIApplication.shared.open(url, options: [:], completionHandler: nil) } } let selectPhotosAction = UIAlertAction(title: "Select more pictures", style: .default) { _ in DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { let galleryVC = GalleryViewController() galleryVC.displayLimitedPicker() } } let keepSelectionAction = UIAlertAction(title: "Keep current selection", style: .cancel, handler: { _ in }) alertLimitedController.addAction(allPhotosAction) alertLimitedController.addAction(selectPhotosAction) alertLimitedController.addAction(keepSelectionAction) return alertLimitedController }
- GalleryViewController.swift 有限照片库唤起方法:
func displayLimitedPicker() { PHPhotoLibrary.requestAuthorization() { result in if #available(iOS 14, *) { PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self) PHPhotoLibrary.shared().register(self) } else { // Fallback on earlier versions } } }
问题根因
- 核心错误:「选择更多照片」的点击回调中,每次都通过
let galleryVC = GalleryViewController()创建了全新的GalleryViewController实例,该实例既没有被push到导航栈,也没有被模态弹出,完全不在当前App的窗口视图层级中。使用这个游离的VC作为源去弹出系统有限照片选择器,必然触发「视图不在窗口层级」的报错。首次点击能正常运行,是因为首次启动时的上下文时序恰好让系统拿到了正确的VC实例,关闭系统选择器后上下文销毁,后续新建的游离VC就会触发报错。 - 线程隐患:
PHPhotoLibrary.requestAuthorization的回调默认在子线程执行,直接在回调中调用present相关的UI操作,违反了UI操作必须在主线程执行的规则,会进一步加剧弹出失败问题。 - 逻辑隐患:每次调用
displayLimitedPicker都执行PHPhotoLibrary.shared().register(self),会导致重复注册照片库观察者,引发内存泄漏、回调重复触发的问题。
修复方案
- 调整弹窗创建方法的参数,直接传入当前已经在视图层级中的GalleryViewController实例,移除回调中新建VC实例的逻辑,使用传入的合法VC实例调用有限选择器唤起方法。
- 在
displayLimitedPicker的权限回调中,将所有UI相关操作切换到主线程执行。 - 将PHPhotoLibrary的注册逻辑移到GalleryViewController的
viewDidLoad生命周期中,在deinit中添加注销逻辑,避免重复注册和内存泄漏。
修复后代码如下:
// GalleryWorker.swift func createAlertLimited(presentingVC: GalleryViewController) -> UIAlertController { let alertLimitedController = UIAlertController(title: "The app needs access to your photos", message: "", preferredStyle: .alert) let allPhotosAction = UIAlertAction(title: "Allow access to all pictures", style: .default) { _ in DispatchQueue.main.async { guard let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) else { assertionFailure("Not able to open privacy settings") return } UIApplication.shared.open(url, options: [:], completionHandler: nil) } } let selectPhotosAction = UIAlertAction(title: "Select more pictures", style: .default) { _ in DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // 直接使用传入的、已在视图层级的VC实例唤起选择器 presentingVC.displayLimitedPicker() } } let keepSelectionAction = UIAlertAction(title: "Keep current selection", style: .cancel, handler: { _ in }) alertLimitedController.addAction(allPhotosAction) alertLimitedController.addAction(selectPhotosAction) alertLimitedController.addAction(keepSelectionAction) return alertLimitedController }
// GalleryViewController.swift override func viewDidLoad() { super.viewDidLoad() // 生命周期内仅注册一次照片库观察者 if #available(iOS 14, *) { PHPhotoLibrary.shared().register(self) } } deinit { // 页面销毁时注销观察者,避免内存泄漏 if #available(iOS 14, *) { PHPhotoLibrary.shared().unregisterChangeObserver(self) } } func displayLimitedPicker() { PHPhotoLibrary.requestAuthorization() { result in if #available(iOS 14, *) { // 切主线程执行UI弹出操作 DispatchQueue.main.async { PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self) } } } }
调用createAlertLimited方法时,直接传入当前的GalleryViewController实例即可,例如在GalleryViewController中调用时传self。
内容的提问来源于stack exchange,提问作者ldo1985
相关产品推荐
相关产品推荐

