Search code examples
iosswiftuitableviewautolayoutnslayoutconstraint

Adding top and bottom constraints causes UILable to be squished


Programmatically I created a custom UITableViewCell and tried centering two UILabels vertically inside it. But the UILabel ended up being squished. Doing the same thing in Interface Builder with a prototype cell works well. What is wrong with my code?

The Custom view cell class

import UIKit

class TopViewCell: UITableViewCell {
    let df: DateFormatter = {
        let df = DateFormatter()
        df.dateFormat = NSLocalizedString("DATE_WEEKDAY", comment: "show date and weekday")
        return df
    }()
    var dateLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    var costLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        let margin = contentView.layoutMarginsGuide
        
        contentView.addSubview(dateLabel)
        dateLabel.leadingAnchor.constraint(equalTo: margin.leadingAnchor).isActive = true
        dateLabel.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
        dateLabel.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
        
        contentView.addSubview(costLabel)
        costLabel.trailingAnchor.constraint(equalTo: margin.trailingAnchor).isActive = true
        costLabel.topAnchor.constraint(equalTo: dateLabel.topAnchor).isActive = true
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        dateLabel.text = df.string(from: Date())
        costLabel.text = "total: five thousand"
    }
}


The Custom UITableViewController class

import UIKit

class ItemViewController: UITableViewController {
    var itemStore: ItemStore!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(TopViewCell.self, forCellReuseIdentifier: "top_cell")
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return itemStore.allItems.count + 1
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell: UITableViewCell!
        
        if indexPath.row == 0 {
            cell = tableView.dequeueReusableCell(withIdentifier: "top_cell", for: indexPath)
        } else {
            cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
            cell.textLabel?.text = itemStore.allItems[indexPath.row - 1].name
            cell.textLabel?.font = cell.textLabel!.font.withSize(30)
            cell.detailTextLabel?.text = "$\(itemStore.allItems[indexPath.row - 1].valueInDolloar)"
        }
        
        return cell
    }
}

enter image description here

enter image description here


Solution

  • Your TopViewCell is not auto-sizing correctly because you're setting the text in layoutSubviews(). Move those two lines to init and it will size properly:

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        let margin = contentView.layoutMarginsGuide
        
        contentView.addSubview(dateLabel)
        dateLabel.leadingAnchor.constraint(equalTo: margin.leadingAnchor).isActive = true
        dateLabel.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
        dateLabel.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
        
        contentView.addSubview(costLabel)
        costLabel.trailingAnchor.constraint(equalTo: margin.trailingAnchor).isActive = true
        costLabel.topAnchor.constraint(equalTo: dateLabel.topAnchor).isActive = true
    
        // set the text here
        dateLabel.text = df.string(from: Date())
        costLabel.text = "total: five thousand"
    }
    

    As a side note, you should specify the class when you use TopViewCell:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
        if indexPath.row == 0 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "top_cell", for: indexPath) as! TopViewCell
            return cell
        }
            
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = itemStore.allItems[indexPath.row - 1].name
        cell.textLabel?.font = cell.textLabel!.font.withSize(30)
        cell.detailTextLabel?.text = "$\(itemStore.allItems[indexPath.row - 1].valueInDolloar)"
    
        return cell
    }
    

    As another side note... you can create two prototype cells in your Storyboard.