点击TableView行弹出ViewController时出现视图层级错误求助
解决Attempt to present...whose view is not in the window hierarchy错误
问题根源
你代码里的TutorialsTableView是一个UIViewController子类,但你仅仅实例化了它,既没有把它的视图添加到任何父视图中,也没有通过导航栈或present操作展示它——这意味着它的视图根本不在应用的窗口层级里。当你在didSelectRowAt方法中调用present时,是在这个从未被展示的控制器上执行的,系统自然会抛出那个错误。
而且你完全没必要让table的数据源/代理继承UIViewController,这是设计上的冗余,也是导致问题的核心原因。
解决方案1:让主控制器直接承担数据源和代理职责(最适合新手)
把数据定义、table的数据源/代理方法全部移到TutorialsViewController中,这样所有操作都在已经正确加载到窗口层级的控制器上执行:
class TutorialsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate { @IBOutlet weak var tutorialsTable: TableViewAdjustedHeight! // 把数据结构移到主控制器内 struct Tutorials { let name: String let description: String } let data: [Tutorials] = [ Tutorials(name: "ParkProfessorHelp", description: "Park Professor Features"), Tutorials(name: "LightningLane", description: "Booking and Modifying Lightning Lane Reservations"), Tutorials(name: "MobileOrder", description: "Guide to Mobile Ordering Food"), Tutorials(name: "Parking", description: "Parking Tips") ] override func viewDidLoad() { super.viewDidLoad() // 设置当前控制器为table的数据源和代理 tutorialsTable.delegate = self tutorialsTable.dataSource = self } // MARK: - UITableView DataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return data.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let tutorials = data[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "tutorialstableCell", for: indexPath) as! tutorialstableCell cell.tutorialsImage.image = UIImage(named: tutorials.name + ".png") cell.tutorialsLabel.text = tutorials.description return cell } // MARK: - UITableView Delegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let tutorials = data[indexPath.row] let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main) // 增加安全校验,避免找不到控制器崩溃 guard let popupVC = mainStoryboard.instantiateViewController(withIdentifier: tutorials.name) else { print("未找到标识符为\(tutorials.name)的ViewController") return } popupVC.modalPresentationStyle = .popover // 用当前主控制器present,它的视图已经在窗口层级中 present(popupVC, animated: true, completion: nil) } } class tutorialstableCell: UITableViewCell { @IBOutlet weak var tutorialsImage: UIImageView! @IBOutlet weak var tutorialsLabel: UILabel! }
解决方案2:将数据源/代理分离为独立类(代码解耦场景)
如果希望把table的逻辑和主控制器分离,不要让数据源类继承UIViewController,而是让它持有主控制器的弱引用,通过主控制器来present新页面:
class TutorialsViewController: UIViewController, UIScrollViewDelegate { @IBOutlet weak var tutorialsTable: TableViewAdjustedHeight! // 实例化独立的数据源类 private let tutorialsDataSource = TutorialsTableDataSource() override func viewDidLoad() { super.viewDidLoad() // 将主控制器赋值给数据源类,用于present操作 tutorialsDataSource.presentingVC = self tutorialsTable.delegate = tutorialsDataSource tutorialsTable.dataSource = tutorialsDataSource } } // 继承NSObject而非UIViewController,仅实现数据源/代理协议 class TutorialsTableDataSource: NSObject, UITableViewDataSource, UITableViewDelegate { // 用弱引用持有主控制器,避免循环引用 weak var presentingVC: UIViewController? struct Tutorials { let name: String let description: String } let data: [Tutorials] = [ Tutorials(name: "ParkProfessorHelp", description: "Park Professor Features"), Tutorials(name: "LightningLane", description: "Booking and Modifying Lightning Lane Reservations"), Tutorials(name: "MobileOrder", description: "Guide to Mobile Ordering Food"), Tutorials(name: "Parking", description: "Parking Tips") ] // MARK: - UITableView DataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return data.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let tutorials = data[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "tutorialstableCell", for: indexPath) as! tutorialstableCell cell.tutorialsImage.image = UIImage(named: tutorials.name + ".png") cell.tutorialsLabel.text = tutorials.description return cell } // MARK: - UITableView Delegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // 先校验主控制器是否存在 guard let presentingVC = presentingVC else { print("未设置负责present的控制器") return } let tutorials = data[indexPath.row] let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main) guard let popupVC = mainStoryboard.instantiateViewController(withIdentifier: tutorials.name) else { print("未找到标识符为\(tutorials.name)的ViewController") return } popupVC.modalPresentationStyle = .popover // 用主控制器执行present操作 presentingVC.present(popupVC, animated: true, completion: nil) } } class tutorialstableCell: UITableViewCell { @IBOutlet weak var tutorialsImage: UIImageView! @IBOutlet weak var tutorialsLabel: UILabel! }
内容的提问来源于stack exchange,提问作者Wasko Ltd
相关产品推荐
相关产品推荐

