Home > Software design >  How to set width of a UITableView equal to the maximum cells' width inside of it?
How to set width of a UITableView equal to the maximum cells' width inside of it?

Time:05-18

I have a table view and I using a custom cell which has 3 UI elements as subviews. I have made my UIelements which are labels to shrink as per there content size. Now my problem is to set cell to shrink as per its UIElements and relatively adjust tableview width.enter image description here

CodePudding user response:

One way could be to:

  • create a separate view e.g. named ThreeElementView which will be added to the content view of the cell
  • with all rows available you could call systemLayoutSizeFitting to get the maximum width
  • add an width constraint (NSLayoutConstraint) to the table view
  • if the data of the table view changes, adjust the constraint

The widthContraint can be setup like this:

private var widthContraint: NSLayoutConstraint?

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

You would also call the last 3 lines before updating the table view with tableView.reloadData().

Assuming that data contains the actual table data, the width calculation could look like this:

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()
}

So for each line you would calculate the width of the contents and finally return the maximum value.

Self-Contained Test

Here is a self-contained test of the above. The UI has been built programmatically in code so that the result is easier to follow. If you press the button, you can see that the width of the tableview then also dynamically adjusts just by setting the constraint.

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()
    }
    
}

Demo

demo

  • Related