Search code examples
iosswiftuitableviewuikituilabel

How to dynamically resize UILabel in a UITableView Cell?


I defined a CustomCell type in my UITableView (shown below), which contains two labels in a stackView. The second label in the stackView has its numberOfLines set to 2. However, when this line has 1 line of data, it causes the second label to be cut off vertically (shown below).

How can I update the constraints so that the second label can dynamically adjust based on if there are 1 or 2 lines of data?

What I tried:

  • Setting the stackView's distribution to fillProportionally, this only works if there are 2 lines of data in the second label
  • Adding spacing to the stackView to separate the two labels
  • Removing the stackView, and constraining the two labels individually

enter image description here

struct Data {
    var type, name: String
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    var data = [Data]()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupView()
    }

    lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(CustomCell.self, forCellReuseIdentifier: "cell")
        
        tableView.backgroundColor = .white
        tableView.separatorStyle = .none
        
        return tableView
    }()
    
    func setupView() {
        data = [
            Data(type: "Breakfast", name: "Poached Egg & Avocado Toast with Sliced Cherry Tomatoes"),
            Data(type: "Lunch", name: "Poached Egg & Avocado Toast")
        ]
        
        view.backgroundColor = .white
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            tableView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9),
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
        let data = data[indexPath.row]
        
        cell.configure(with: data)
        cell.backgroundColor = .white

        return cell
    }
}
                 
class CustomCell: UITableViewCell {
   override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
       super.init(style: style, reuseIdentifier: reuseIdentifier)
       setupView()
   }
   
   required init?(coder: NSCoder) {
       fatalError("init(coder:) has not been implemented")
   }
   
   override func layoutSubviews() {
       super.layoutSubviews()
       contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0))
   }
   
   lazy var typeLabel: UILabel = {
       let label = UILabel()
       label.font = UIFont.systemFont(ofSize: 25, weight: .bold)
       label.translatesAutoresizingMaskIntoConstraints = false
       
       return label
   }()
   
   lazy var nameLabel: UILabel = {
       let label = UILabel()
       label.font = UIFont.systemFont(ofSize: 20)
       label.numberOfLines = 2
       label.lineBreakMode = .byWordWrapping
       label.translatesAutoresizingMaskIntoConstraints = false
       
       return label
   }()
   
   lazy var titleStack: UIStackView = {
       let stack = UIStackView(arrangedSubviews: [typeLabel, nameLabel])
       stack.axis = .vertical
       stack.distribution = .fillProportionally
       stack.translatesAutoresizingMaskIntoConstraints = false

       return stack
   }()
   
   lazy var iv: UIImageView = {
       let iv = UIImageView()
       iv.contentMode = .scaleAspectFit
       iv.translatesAutoresizingMaskIntoConstraints = false
       iv.backgroundColor = .systemMint
       
       return iv
   }()
   
   func configure(with mealInfo: Data) {
       typeLabel.text = mealInfo.type.capitalized
       nameLabel.text = mealInfo.name
       iv.image = UIImage(systemName: "star")
   }

   func setupView() {
       contentView.backgroundColor = .systemPurple
       contentView.addSubview(titleStack)
       contentView.addSubview(iv)

       NSLayoutConstraint.activate([
           titleStack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
           titleStack.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor, constant: 10),
           titleStack.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.9)
       ])
       
       NSLayoutConstraint.activate([
           iv.topAnchor.constraint(equalTo: titleStack.bottomAnchor, constant: 10),
           iv.leftAnchor.constraint(equalTo: titleStack.leftAnchor),
           iv.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.55),
           iv.heightAnchor.constraint(equalTo: iv.widthAnchor, multiplier: 0.6),
           iv.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10)
       ])
   }
}

Solution

  • class CustomCell: UITableViewCell {
        private let typeLabel: UILabel = {
            let label = UILabel()
            label.font = UIFont.systemFont(ofSize: 25, weight: .bold)
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }()
        
        private let nameLabel: UILabel = {
            let label = UILabel()
            label.font = UIFont.systemFont(ofSize: 20)
            label.numberOfLines = 2
            label.lineBreakMode = .byWordWrapping
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }()
        
        private let iv: UIImageView = {
            let iv = UIImageView()
            iv.contentMode = .scaleAspectFit
            iv.translatesAutoresizingMaskIntoConstraints = false
            iv.backgroundColor = .systemMint
            return iv
        }()
        
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            setupView()
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        private func setupView() {
            contentView.backgroundColor = .systemPurple
            
            contentView.addSubview(typeLabel)
            typeLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true
            typeLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
            typeLabel.widthAnchor.constraint(equalTo: contentView.widthAnchor, constant: -20).isActive = true
            
            contentView.addSubview(nameLabel)
            nameLabel.topAnchor.constraint(equalTo: typeLabel.bottomAnchor, constant: 5).isActive = true
            nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
            nameLabel.widthAnchor.constraint(equalTo: contentView.widthAnchor, constant: -20).isActive = true
            
            contentView.addSubview(iv)
            iv.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 10).isActive = true
            iv.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
            iv.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.55).isActive = true
            iv.heightAnchor.constraint(equalTo: iv.widthAnchor, multiplier: 0.6).isActive = true
            iv.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10).isActive = true
        }
        
        func configure(with mealInfo: Data) {
            typeLabel.text = mealInfo.type.capitalized
            nameLabel.text = mealInfo.name
            iv.image = UIImage(systemName: "star")
        }
     }