如何将 UITableView 的宽度设置为等于其中最大单元格的宽度?

How to set width of a UITableView equal to the maximum cells' width inside of it?

我有一个 table 视图,我使用一个自定义单元格,它有 3 个 UI 元素作为子视图。我已经制作了我的 UI 元素,这些元素是根据内容大小缩小的标签。现在我的问题是将单元格设置为根据其 UI 元素缩小并相对调整 table 视图宽度。

一种方法是:

  • 创建一个单独的视图,例如名为 ThreeElementView 的内容将被添加到单元格的内容视图中
  • 如果所有行都可用,您可以调用 systemLayoutSizeFitting 以获得最大宽度
  • 向 table 视图添加宽度约束 (NSLayoutConstraint)
  • 如果table视图的数据发生变化,调整约束

widthContraint可以这样设置:

private var widthContraint: NSLayoutConstraint?

widthContraint = tableView.widthAnchor.constraint(equalToConstant: 128)
widthContraint?.isActive = true
if let width = calcWidth() {
    widthContraint?.constant = width
}

在使用 tableView.reloadData().

更新 table 视图之前,您还可以调用最后 3 行

假设 data 包含实际的 table 数据,宽度计算可能如下所示:

private func calcWidth() -> CGFloat? {
    let prototypeView = ThreeElementView()
    let widths = data.map { row -> CGFloat in
        prototypeView.label1.text = row[0]
        prototypeView.label2.text = row[1]
        prototypeView.label3.text = row[2]
        prototypeView.setNeedsLayout()
        prototypeView.layoutIfNeeded()
        return prototypeView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).width
    }
    return widths.max()
}

因此,对于每一行,您将计算内容的宽度,最后 return 最大值。

Self-Contained测试

这是对上述内容的 self-contained 测试。 UI 是在代码中以编程方式构建的,因此结果更容易理解。如果您按下按钮,您可以看到 table 视图的宽度也会通过设置约束动态调整。

ThreeElementView.swift

import UIKit

class ThreeElementView: UIView {
    
    let label1 = UILabel()
    let label2 = UILabel()
    let label3 = UILabel()
    
    init() {
        super.init(frame: .zero)
        
        label1.backgroundColor = UIColor(red: 84/255, green: 73/255, blue: 75/255, alpha: 1.0)
        label1.textColor = .white
        label2.backgroundColor = UIColor(red: 131/255, green: 151/255, blue: 136/255, alpha: 1.0)
        label2.textColor = .white
        label3.backgroundColor = UIColor(red: 189/255, green: 187/255, blue: 182/255, alpha: 1.0)
        
        label1.translatesAutoresizingMaskIntoConstraints = false
        label2.translatesAutoresizingMaskIntoConstraints = false
        label3.translatesAutoresizingMaskIntoConstraints = false
        
        self.addSubview(label1)
        self.addSubview(label2)
        self.addSubview(label3)
        
        NSLayoutConstraint.activate([
            label1.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            label1.topAnchor.constraint(equalTo: self.topAnchor),
            label1.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            
            label2.leadingAnchor.constraint(equalTo: label1.trailingAnchor),
            label2.topAnchor.constraint(equalTo: self.topAnchor),
            label2.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            
            label3.leadingAnchor.constraint(equalTo: label2.trailingAnchor),
            label3.topAnchor.constraint(equalTo: self.topAnchor),
            label3.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            label3.trailingAnchor.constraint(equalTo: self.trailingAnchor)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

ThreeElementCell.swift

import UIKit

class ThreeElementCell: UITableViewCell {
    
    static let id = "ThreeElementCellId"
    let threeElementView = ThreeElementView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        threeElementView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(threeElementView)
        NSLayoutConstraint.activate([
            threeElementView.topAnchor.constraint(equalTo: contentView.topAnchor),
            threeElementView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            threeElementView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            threeElementView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        ])
    }
    
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

ViewController.swift

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    private let tableView = UITableView()
    private let addMoreButton = UIButton()
    private var data = [
        ["a", "tiny", "row"],
    ]
    private var widthContraint: NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
        setupButton()
    }
    
    @objc func onAddMore() {
        if data.count < 2 {
            data.append(["a", "little bit", "longer row"])
        } else {
            data.append(["this is", " finally an even longer", "row"])
        }
        if let width = calcWidth() {
            widthContraint?.constant = width
        }
        tableView.reloadData()
    }
    
    // MARK: - UITableViewDataSource
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: ThreeElementCell.id, for: indexPath) as! ThreeElementCell
        let item = data[indexPath.row]
        cell.threeElementView.label1.text = item[0]
        cell.threeElementView.label2.text = item[1]
        cell.threeElementView.label3.text = item[2]        
        return cell
    }
    
    // MARK: - Private
    
    private func setupTableView() {
        tableView.backgroundColor = UIColor(red: 245/255, green: 228/255, blue: 215/255, alpha: 1.0)
        tableView.register(ThreeElementCell.self, forCellReuseIdentifier: ThreeElementCell.id)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)

        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16.0),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16.0),
        ])
        widthContraint = tableView.widthAnchor.constraint(equalToConstant: 128)
        
        widthContraint?.isActive = true
        if let width = calcWidth() {
            widthContraint?.constant = width
        }
        
        tableView.delegate = self
        tableView.dataSource = self
    }
    
    private func setupButton() {
        addMoreButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(addMoreButton)
        NSLayoutConstraint.activate([
            addMoreButton.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant: 32.0),
            addMoreButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            addMoreButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -32.0),
        ])
        addMoreButton.setTitle("Add More Rows", for: .normal)
        addMoreButton.setTitleColor(.blue, for: .normal)
        addMoreButton.addTarget(self, action: #selector(onAddMore), for: .touchUpInside)
    }

    private func calcWidth() -> CGFloat? {
        let prototypeView = ThreeElementView()
        let widths = data.map { row -> CGFloat in
            prototypeView.label1.text = row[0]
            prototypeView.label2.text = row[1]
            prototypeView.label3.text = row[2]
            prototypeView.setNeedsLayout()
            prototypeView.layoutIfNeeded()
            return prototypeView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).width
        }
        return widths.max()
    }
    
}

演示