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

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.

相关原实现代码:

  1. 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
}
  1. 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
        }
    }
}
问题根因
  1. 核心错误:「选择更多照片」的点击回调中,每次都通过let galleryVC = GalleryViewController()创建了全新的GalleryViewController实例,该实例既没有被push到导航栈,也没有被模态弹出,完全不在当前App的窗口视图层级中。使用这个游离的VC作为源去弹出系统有限照片选择器,必然触发「视图不在窗口层级」的报错。首次点击能正常运行,是因为首次启动时的上下文时序恰好让系统拿到了正确的VC实例,关闭系统选择器后上下文销毁,后续新建的游离VC就会触发报错。
  2. 线程隐患:PHPhotoLibrary.requestAuthorization的回调默认在子线程执行,直接在回调中调用present相关的UI操作,违反了UI操作必须在主线程执行的规则,会进一步加剧弹出失败问题。
  3. 逻辑隐患:每次调用displayLimitedPicker都执行PHPhotoLibrary.shared().register(self),会导致重复注册照片库观察者,引发内存泄漏、回调重复触发的问题。
修复方案
  1. 调整弹窗创建方法的参数,直接传入当前已经在视图层级中的GalleryViewController实例,移除回调中新建VC实例的逻辑,使用传入的合法VC实例调用有限选择器唤起方法。
  2. 在displayLimitedPicker的权限回调中,将所有UI相关操作切换到主线程执行。
  3. 将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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 11:15:42