iPadOS 17 UIKit Popover重复快速呈现崩溃问题求助
iPadOS 17 快速重复呈现Popover崩溃的非侵入式解决方案
在iPadOS 17系统中,关闭Popover后快速触发再次呈现操作时会出现崩溃,常见错误提示有两种:
UIPopoverPresentationController should have a non-nil sourceView or barButtonItem set before the presentation occursApplication tried to present modally a view controller that is already being presented by the [main view controller]
可复现代码
class ViewController: UIViewController { let popoverVC = UIViewController() @IBOutlet var showPopoverButton: UIButton! @IBAction func showPopoverTapped(_ sender: UIButton) { popoverVC.modalPresentationStyle = .popover popoverVC.popoverPresentationController?.sourceView = showPopoverButton present(popoverVC, animated: true) } }
问题补充说明
- 仅iPadOS 17存在此问题,iOS 16及更早版本无异常;
- 为
popoverPresentationController设置.sourceRect无法解决崩溃; - 修改Popover的
frame或preferredContentSize无效; - 无论使用Storyboard还是纯代码编写UI,UIKit中都会触发崩溃;
- SwiftUI不存在此问题,但请勿推荐SwiftUI作为解决方案;
- 呈现前添加
guard校验sourceView非空无效果。
问题原因推测
崩溃源于Popover视图控制器在dismissal动画未完成、内存未完全释放时被重复触发呈现,导致视图控制器状态冲突、PresentationController参数异常。
非侵入式解决方案
方案1:避免复用同一个Popover实例
每次触发呈现时创建新的Popover实例,从根源上避免状态冲突:
class ViewController: UIViewController { @IBOutlet var showPopoverButton: UIButton! @IBAction func showPopoverTapped(_ sender: UIButton) { // 每次点击创建新的Popover实例 let popoverVC = UIViewController() popoverVC.modalPresentationStyle = .popover if let popoverPC = popoverVC.popoverPresentationController { popoverPC.sourceView = sender popoverPC.sourceRect = sender.bounds // 可选,优化弹出位置精度 } present(popoverVC, animated: true) } }
方案2:复用实例时确保状态正确
若需复用同一个Popover实例,需先检查其是否处于被呈现状态,等待dismiss完成后再触发呈现:
class ViewController: UIViewController { let popoverVC = UIViewController() @IBOutlet var showPopoverButton: UIButton! @IBAction func showPopoverTapped(_ sender: UIButton) { // 检查Popover是否正在被呈现 guard popoverVC.presentingViewController == nil else { // 先关闭当前Popover,完成后再重新呈现 popoverVC.dismiss(animated: false) { [weak self] in self?.presentPopover(from: sender) } return } presentPopover(from: sender) } private func presentPopover(from sender: UIButton) { popoverVC.modalPresentationStyle = .popover popoverVC.popoverPresentationController?.sourceView = sender present(popoverVC, animated: true) } }
方案说明
两种方案均无需使用Method Swizzling或重写动画逻辑,属于UIKit规范内的非hacky解决方式:
- 方案1实现简单,适合Popover内容无需复用状态的场景;
- 方案2保留实例复用,适合需要维持Popover内部状态的场景。
内容的提问来源于stack exchange,提问作者Nathan Dudley
相关产品推荐
相关产品推荐

