如何调整 UITableView tableHeaderView 的左右 insets 或 margins?

How to adjust left and right insets or margins for UITableView tableHeaderView?

我有一个 "plain" 风格的 UITableView。我将视图设置为 table 视图的 tableViewHeader。 table 还在右侧显示部分索引。

我的问题是弄清楚如何插入 header 视图的左右边缘以考虑安全区域插入,如果 运行 在 iPhone X 上(横向) 和 table 视图的部分索引(如果有的话)。

我创建了一个简单的测试应用程序,其中添加了一些虚拟行、一个部分 header 和部分索引。

这是我使用 UILabel 创建简单 header 视图的代码。我的真实应用不会使用标签,而是使用自定义视图。

let label = UILabel()
label.font = UIFont.systemFont(ofSize: 30)
label.backgroundColor = .green
label.text = "This is a really long Table Header label to make sure it is working properly."
label.sizeToFit()
tableView.tableHeaderView = label

没有特别尝试修复左右边缘,在iPhone X模拟器中的结果如下:

肖像:

横向:

请注意,无需任何额外的努力,单元格和部分 header 会获得所需的插图,但 header 视图不会。

我希望 header 视图的左边缘与部分 header 和单元格的左边缘对齐。

我希望 header 视图的右边缘在与部分索引的左边缘相交处停止。请注意,肖像图片似乎已经这样做了,但如果仔细观察,您会发现 header 视图一直延伸到 table 视图的右边缘。您看不到第三个 . 的省略号,也几乎看不到部分标题视图后面的绿色。

我曾尝试将以下内容添加到我的 table 视图控制器中:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let header = tableView.tableHeaderView {
        var insets = header.layoutMargins
        insets.left = tableView.layoutMargins.left
        insets.right = tableView.layoutMargins.right
        header.layoutMargins = insets
    }
}

该代码无效。

我应该设置哪些属性来确保 header 视图的左右边缘按需要缩进?是否应该应用约束?

请注意,我所做的一切都是代码。所以请不要 post 任何需要故事板或 xib 文件的解决方案。欢迎 Swift 或 Objective-C 中的答案。


对于任何想要复制它的人,请创建一个新的单视图项目。调整主故事板以使用 UITableViewController 而不是计划 UIViewController 并使用以下 ViewController:

import UIKit

class ViewController: UITableViewController {

    // MARK: - UITableViewController methods

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        cell.textLabel?.text = "Row \(indexPath.row)"
        cell.accessoryType = .disclosureIndicator

        return cell
    }

    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

    }

    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "Section Header"
    }

    override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        let coll = UILocalizedIndexedCollation.current()

        return coll.sectionIndexTitles
    }

    override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
        return index
    }

    // MARK: - UIViewController methods

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.sectionIndexMinimumDisplayRowCount = 1

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")

        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 30)
        label.backgroundColor = .green
        label.text = "This is a really long Table Header label to make sure it is working properly."
        label.sizeToFit()
        tableView.tableHeaderView = label
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        if let header = tableView.tableHeaderView {
            var insets = header.layoutMargins
            insets.left = tableView.layoutMargins.left
            insets.right = tableView.layoutMargins.right
            header.layoutMargins = insets
        }
    }
}

将标签添加到 table 视图后,您需要向标签添加一些自动布局约束:

…
tableView.tableHeaderView = label
//Add this
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: (label.superview?.safeAreaLayoutGuide.leadingAnchor)!).isActive = true
label.trailingAnchor.constraint(equalTo: (label.superview?.safeAreaLayoutGuide.trailingAnchor)!).isActive = true

此外,如果您想查看标签中的所有文本,请使用 label.numberOfLines = 0

您可以删除添加到 viewDidLayoutSubviews 的代码。

更新:

为了好玩,我在操场上做了一些实验,发现使用 layoutMarginsGuide 并没有将 header 标签的后缘推得足够远(我想它出来了就在 iPhone X 上,但可能不是在所有设备上,或者 playground 的行为有点不同)。我确实发现,对于至少有一个单元格的 table 视图,我可以使用 aCell.contentView.bounds.width,减去 table 视图的宽度并将结果除以二,然后 header 标签会很好地位于部分索引旁边。因此,我编写了一个用于设置约束的辅助函数。 table 视图是可选的,因此该函数可以与任何具有父视图并需要保持在安全区域内的视图一起使用。如果传入 table 视图,它可以有或没有部分索引,但它确实需要在第 0 行第 0 部分至少有一个单元格:

func constrain(view: UIView, inTableView aTableView: UITableView?) {
    guard let container = view.superview else {
        print("Constrain error! View must have a superview to be constrained!!")
        return
    }
    view.translatesAutoresizingMaskIntoConstraints = false
    view.leadingAnchor.constraint(equalTo: container.safeAreaLayoutGuide.leadingAnchor).isActive = true
    if let table = aTableView, let aCell = table.cellForRow(at: IndexPath(row: 0, section: 0)) {
        let tableWidth = table.bounds.width
        let cellWidth = aCell.contentView.bounds.width
        view.trailingAnchor.constraint(equalTo: table.safeAreaLayoutGuide.trailingAnchor, constant: cellWidth - tableWidth).isActive = true
    } else {
        view.trailingAnchor.constraint(equalTo: container.safeAreaLayoutGuide.trailingAnchor).isActive = true
    }
}

我在使用这个的时候确实发现了一个问题。当在文本中使用设置为 0 行的标签时,它会覆盖第一部分 header 和该部分的第一个单元格。也需要滚动一下才能将它们从 header 下方移出。不过,将标签剪裁成一行效果很好。

对于没有部分索引的 UITableView:

label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
    label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])

对于具有部分索引的 UITableViews:

label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
    label.trailingAnchor.constraint(equalTo: tableView.layoutMarginsGuide.trailingAnchor)
])