iOS:两个并排UITableView自定义HeaderView标题显示异常排查
我有一个视图控制器,其中展示了两个并排的UITableView。同时定义了一个继承自UITableViewHeaderFooterView的ButtonHeaderFooterView类,希望为两个UITableView设置不同的Header标题,但当前实现无法正常生效。
ViewController.swift
import UIKit class ViewController: UIViewController { @IBOutlet private var leftTableView: UITableView! @IBOutlet private var rightTableView: UITableView! private var cachedHeader: ButtonHeaderFooterView? override func viewDidLoad() { super.viewDidLoad() ButtonHeaderFooterView.register(forTable: leftTableView) ButtonHeaderFooterView.register(forTable: rightTableView) } } extension ViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 2 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if tableView == leftTableView { return "" } return "" } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if let cachedHeader = cachedHeader { return cachedHeader } guard let headerView = ButtonHeaderFooterView.dequeue(forTable: tableView) else { fatalError("Failed to dequeue header/footer view buttonHeaderFooterView") } cachedHeader = headerView return headerView } func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { guard let header = view as? ButtonHeaderFooterView else { return } if tableView == leftTableView { header.textLabel?.text = "Test 1" } else { header.textLabel?.text = "Test 2" } } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 36 } }
ButtonHeaderFooterView.swift
import Foundation import UIKit protocol ButtonHeaderFooterDelegate: AnyObject { func buttonTapped() } class ButtonHeaderFooterView: UITableViewHeaderFooterView { static let identifier = "ButtonHeaderFooterView" @IBOutlet private var titleLabel: UILabel! static func dequeue(forTable tableView: UITableView) -> ButtonHeaderFooterView? { tableView.dequeueReusableHeaderFooterView(withIdentifier: ButtonHeaderFooterView.identifier) as? ButtonHeaderFooterView } static func register(forTable tableView: UITableView) { tableView.register(UINib(nibName: "ButtonHeaderFooterView", bundle: nil), forHeaderFooterViewReuseIdentifier: ButtonHeaderFooterView.identifier) } }
错误原因及修正方案
1. 错误复用单个Header实例
你用cachedHeader变量缓存了一个HeaderView,导致两个TableView共用同一个实例。当第二个TableView请求Header时,直接返回了第一个TableView的Header,后续的标题设置会覆盖之前的内容,完全破坏了UITableView的复用机制。
修正:移除全局的cachedHeader变量,让每个TableView通过自身的复用池获取HeaderView:
// 删掉这行缓存代码 // private var cachedHeader: ButtonHeaderFooterView? // 改写viewForHeaderInSection方法 func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { guard let headerView = ButtonHeaderFooterView.dequeue(forTable: tableView) else { fatalError("Failed to dequeue header/footer view buttonHeaderFooterView") } return headerView }
2. Label引用错误
自定义Header里的标题Label是titleLabel(IBOutlet),但你在willDisplayHeaderView里调用了系统默认的textLabel,这是两个完全不同的Label,所以设置了也不会显示。另外titleLabel是private属性,直接访问不符合封装原则,建议添加公共方法来设置标题。
修正:在ButtonHeaderFooterView中添加公共方法:
class ButtonHeaderFooterView: UITableViewHeaderFooterView { // ... 原有代码 ... func setHeaderTitle(_ title: String?) { titleLabel?.text = title } }
然后在ViewController中调用这个方法设置标题:
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { guard let header = view as? ButtonHeaderFooterView else { return } if tableView == leftTableView { header.setHeaderTitle("Test 1") } else { header.setHeaderTitle("Test 2") } }
3. 多余的titleForHeaderInSection实现
这个方法返回空字符串,虽然你用了自定义Header,但该方法可能会干扰系统对Header的渲染逻辑,建议直接移除,或者返回nil。
修正:删掉整个titleForHeaderInSection方法,或者改为:
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return nil }
优化建议
你可以直接在viewForHeaderInSection里设置标题,不需要等到willDisplayHeaderView,逻辑更直接:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { guard let headerView = ButtonHeaderFooterView.dequeue(forTable: tableView) else { fatalError("Failed to dequeue header/footer view buttonHeaderFooterView") } headerView.setHeaderTitle(tableView == leftTableView ? "Test 1" : "Test 2") return headerView }
这样可以去掉willDisplayHeaderView方法,代码更简洁。
内容的提问来源于stack exchange,提问作者Felipe Peña

