在TableViewCell中弹窗报错求助:视图不在窗口层级中
解决TableViewCell点击按钮弹窗时“view is not in the window hierarchy”的问题
这个问题我之前也碰到过,本质是你尝试调用弹窗的ViewController实例要么不是当前屏幕上活跃的那个,要么它的视图还没被添加到窗口层级里。直接在Cell里持有ViewController引用很容易踩这个坑,推荐用闭包或者代理模式来传递点击事件,让所属的ViewController自己处理弹窗,这样既解耦又能避免层级错误。
方法1:用闭包传递点击事件
这种方式代码更简洁,适合简单的事件传递:
- 先在你的TableViewCell类里定义一个闭包属性,用来传递Kik用户名:
class YourTableViewCell: UITableViewCell { // 替换成你的User模型类型 var user: User? // 定义闭包,点击按钮时触发 var kikButtonTapped: ((String) -> Void)? @objc func kik() { guard let kikUsername = user?.KikUsername else { return } // 触发闭包,把用户名传递给ViewController kikButtonTapped?(kikUsername) } }
- 在ViewController的
cellForRowAt方法里,给Cell的闭包赋值,在这里处理弹窗逻辑:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID", for: indexPath) as! YourTableViewCell // 给Cell设置用户数据 cell.user = yourUserDataSource[indexPath.row] // 绑定闭包,[weak self]避免循环引用 cell.kikButtonTapped = { [weak self] kikUsername in guard let self = self else { return } let targetUser = self.yourUserDataSource[indexPath.row] let title = "Kik Username" let message = "The Kik username from \(targetUser.username ?? "") is: \(kikUsername)." let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) // 这里的self是当前活跃的ViewController,视图肯定在窗口层级里 self.present(alert, animated: true) } return cell }
方法2:用代理模式传递事件
如果你的Cell有多个点击事件需要传递,代理模式会更清晰:
- 先定义一个代理协议:
// 代理协议,用AnyObject确保只能被类遵守 protocol KikCellDelegate: AnyObject { func cell(_ cell: YourTableViewCell, didTapKikButtonWithUsername username: String) }
- 在Cell里添加代理属性,并在按钮点击时调用代理方法:
class YourTableViewCell: UITableViewCell { var user: User? // 用weak修饰避免循环引用 weak var delegate: KikCellDelegate? @objc func kik() { guard let kikUsername = user?.KikUsername else { return } delegate?.cell(self, didTapKikButtonWithUsername: kikUsername) } }
- 在ViewController里遵守代理协议,实现代理方法并处理弹窗:
class SignInViewController: UIViewController, KikCellDelegate { // 假设这是你的TableView和数据源 @IBOutlet weak var tableView: UITableView! var yourUserDataSource: [User] = [] override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self tableView.register(YourTableViewCell.self, forCellReuseIdentifier: "YourCellID") } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID", for: indexPath) as! YourTableViewCell cell.user = yourUserDataSource[indexPath.row] // 设置代理 cell.delegate = self return cell } // 实现代理方法 func cell(_ cell: YourTableViewCell, didTapKikButtonWithUsername username: String) { // 通过Cell获取对应的indexPath和用户数据 guard let indexPath = tableView.indexPath(for: cell), let targetUser = yourUserDataSource[safe: indexPath.row] else { return } let title = "Kik Username" let message = "The Kik username from \(targetUser.username ?? "") is: \(username)." let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) self.present(alert, animated: true) } } // 可选:给数组加个安全下标扩展,避免越界 extension Collection { subscript(safe index: Index) -> Element? { return indices.contains(index) ? self[index] : nil } }
为什么之前的代码会报错?
你之前应该是在Cell里直接创建或者引用了一个SignInViewController实例,但这个实例可能不是当前屏幕上正在显示的那个——要么是新创建的(视图还没加载),要么是已经被销毁的,所以调用present时会提示“视图不在窗口层级”。而上面两种方法都是让当前活跃的、视图已经在窗口里的ViewController来处理弹窗,就不会有这个问题了。
内容的提问来源于stack exchange,提问作者Vine Tube
相关产品推荐
相关产品推荐

