You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS:两个并排UITableView自定义HeaderView标题显示异常排查

问题:两个并排UITableView的自定义Header标题无法正常区分显示

我有一个视图控制器,其中展示了两个并排的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 22:40:22