子视图UITableView滚动触发父视图cellForRowAt问题及解决
我有两个ViewController,每个视图中都包含UITableView并已设置代理和数据源。当在第二个视图中向上滚动UITableView时,父视图的cellForRowAt方法被调用,但numberOfRowsInSection方法不会触发。我尝试修改UITableView名称后问题仍然存在,且必须以卡片样式(非.fullScreen)模态展示第二个ViewController。请问该现象的原因是什么?如何解决?
第一个ViewController代码
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var dataTable: UITableView! override func viewDidLoad() { super.viewDidLoad() dataTable.delegate = self dataTable.dataSource = self } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 20 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell(style: .default, reuseIdentifier: "firstCell") cell.textLabel?.text = String(indexPath.row) return cell } }
跳转至SecondViewController的代码
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let secondSB = UIStoryboard(name: "Second", bundle: nil) let secondVC = secondSB.instantiateViewController(withIdentifier: "second") let nc = UINavigationController(rootViewController: secondVC) nc.modalPresentationStyle = .automatic present(nc, animated: true) }
第二个ViewController代码
class SecondVC: UIViewController , UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var dataTable: UITableView! override func viewDidLoad() { super.viewDidLoad() dataTable.delegate = self dataTable.dataSource = self } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 10 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell(style: .default, reuseIdentifier: "firstCell") cell.textLabel?.text = "Second" + String(indexPath.row) return cell } }
在iOS 13及以上版本,卡片式模态(.pageSheet或.automatic默认的卡片样式)展示ViewController时,父视图控制器不会被完全从视图层级中移除,仍存在于底层。由于两个ViewController都实现了UITableViewDelegate和UITableViewDataSource协议,且代理方法未对传入的tableView参数做实例判断,当第二个页面的TableView滚动时,系统响应链可能误触发父视图的cellForRowAt方法。而numberOfRowsInSection不会被触发,是因为第二个TableView已有自身数据源返回行数,无需调用父视图的对应方法。
在所有UITableView的代理/数据源方法中,先判断传入的tableView是否为当前ViewController持有的实例,不是则返回默认值或跳过处理。
修改后的第一个ViewController代码
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var dataTable: UITableView! override func viewDidLoad() { super.viewDidLoad() dataTable.delegate = self dataTable.dataSource = self } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard tableView === self.dataTable else { return 0 } return 20 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard tableView === self.dataTable else { return UITableViewCell() } let cell = UITableViewCell(style: .default, reuseIdentifier: "firstCell") cell.textLabel?.text = String(indexPath.row) return cell } }
修改后的第二个ViewController代码
class SecondVC: UIViewController , UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var dataTable: UITableView! override func viewDidLoad() { super.viewDidLoad() dataTable.delegate = self dataTable.dataSource = self } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard tableView === self.dataTable else { return 0 } return 10 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard tableView === self.dataTable else { return UITableViewCell() } let cell = UITableViewCell(style: .default, reuseIdentifier: "firstCell") cell.textLabel?.text = "Second" + String(indexPath.row) return cell } }
通过这种方式,可确保每个ViewController只处理自身持有的TableView的代理/数据源请求,避免跨页面的方法误触发。
内容的提问来源于stack exchange,提问作者Eric

