Search code examples
iosswiftuitableviewnslayoutconstraint

How do I make my constraints properly with a UIImageView and multiple labels in a cell?


I am currently trying to achieve the dynamic cells functionality. I have in fact achieved it but using only a dateLabel, nameLabel and the detailLabel. I want to include an image on the left (authorProfileImg). I have tried multiple iterations and combinations of constraints for my 4 elements and I have not succeeded yet.

class BookTableViewCell: UITableViewCell {

    let nameLabel = UILabel(frame: .zero)
    let detailLabel = UILabel(frame: .zero)

    let dateLabel = UILabel(frame: .zero)
    let authorProfileImg = UIImageView(frame: .zero)

    // MARK: Initalizers
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        let marginGuide = contentView.layoutMarginsGuide

        // configure titleLabel //the upper element
        contentView.addSubview(nameLabel)
        nameLabel.translatesAutoresizingMaskIntoConstraints = false

        nameLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 60).isActive = true
        nameLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
        nameLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true

        nameLabel.numberOfLines = 0
        nameLabel.font = UIFont(name: "Arial", size: 16)

        // configure authorLabel //the lower element
        contentView.addSubview(detailLabel)
        detailLabel.translatesAutoresizingMaskIntoConstraints = false

        detailLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 60).isActive = true
        //        detailLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
        detailLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: -20).isActive = true
        detailLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 5).isActive = true

        detailLabel.numberOfLines = 0
        detailLabel.font = UIFont(name: "Arial", size: 13)
        detailLabel.textColor = UIColor.lightGray

        // configure dateLabel
        contentView.addSubview(dateLabel)
        dateLabel.translatesAutoresizingMaskIntoConstraints = false

        dateLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 60).isActive = true
        dateLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
        dateLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
        dateLabel.topAnchor.constraint(equalTo: detailLabel.bottomAnchor, constant: 5).isActive = true

        dateLabel.numberOfLines = 0
        dateLabel.font = UIFont(name: "Arial", size: 12)
        dateLabel.textColor = UIColor.red
        dateLabel.text = "Jun 5"

        // configure author image
        contentView.addSubview(authorProfileImg)
        authorProfileImg.translatesAutoresizingMaskIntoConstraints = false

        authorProfileImg.widthAnchor.constraint(equalToConstant: 50).isActive = true
        authorProfileImg.heightAnchor.constraint(equalToConstant: 50).isActive = true
        authorProfileImg.layer.cornerRadius = 50

        authorProfileImg.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor).isActive = true
        authorProfileImg.topAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true

        authorProfileImg.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
//        authorProfileImg.trailingAnchor.constraint(equalTo: detailLabel.leadingAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

What am I doing wrong here?


Solution

  • You weren't too far off.

    I find it very helpful to group related lines of code - particularly when defining constraints. To me, it's much easier to track what's going on.

    So, in the following code, I've grouped:

    • adding subviews
    • setting element properties
    • defining constraints

    You'll see in the comments that I have one constraint for vertically-centering the authorProfileImg, and another constraint for top-aligning it.

    class BookTableViewCell: UITableViewCell {
    
        let nameLabel = UILabel(frame: .zero)
        let detailLabel = UILabel(frame: .zero)
    
        let dateLabel = UILabel(frame: .zero)
        let authorProfileImg = UIImageView(frame: .zero)
    
        // MARK: Initalizers
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
            let marginGuide = contentView.layoutMarginsGuide
    
            // add the subviews
            contentView.addSubview(nameLabel)
            contentView.addSubview(detailLabel)
            contentView.addSubview(dateLabel)
            contentView.addSubview(authorProfileImg)
    
            // we will use auto-layout constraints
            nameLabel.translatesAutoresizingMaskIntoConstraints = false
            detailLabel.translatesAutoresizingMaskIntoConstraints = false
            dateLabel.translatesAutoresizingMaskIntoConstraints = false
            authorProfileImg.translatesAutoresizingMaskIntoConstraints = false
    
            // configure labels and image view properties
            nameLabel.numberOfLines = 0
            nameLabel.font = UIFont(name: "Arial", size: 16)
    
            detailLabel.numberOfLines = 0
            detailLabel.font = UIFont(name: "Arial", size: 13)
            detailLabel.textColor = UIColor.lightGray
    
            dateLabel.numberOfLines = 0
            dateLabel.font = UIFont(name: "Arial", size: 12)
            dateLabel.textColor = UIColor.red
            dateLabel.text = "Jun 5"
    
            authorProfileImg.layer.cornerRadius = 25
            authorProfileImg.backgroundColor = .blue
    
            // set the constraints
    
            NSLayoutConstraint.activate([
    
                // image view leading is marginGuide leading
                authorProfileImg.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 0.0),
    
                // image view centered vertically in cell
                authorProfileImg.centerYAnchor.constraint(equalTo: marginGuide.centerYAnchor, constant: 0.0),
    
                // if we want image view constrained to top of marginGuide (aligned to top of cell),
                // comment the above line (the centerYAnchor line) and un-comment this next line
                //authorProfileImg.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 0.0),
    
                // image view is 50x50
                authorProfileImg.widthAnchor.constraint(equalToConstant: 50.0),
                authorProfileImg.heightAnchor.constraint(equalTo: authorProfileImg.widthAnchor),
    
                // name label constrained to top
                // leading is 10-pts from image view trailing
                // trailing is marginGuide trailing
                nameLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 0.0),
                nameLabel.leadingAnchor.constraint(equalTo: authorProfileImg.trailingAnchor, constant: 10.0),
                nameLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 0.0),
    
                // detail label top constrained 5-pts from bottom of name label
                // leading is equal to name label leading
                // trailing is marginGuide trailing -20 pts
                detailLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 5.0),
                detailLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor, constant: 0.0),
                detailLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: -20.0),
    
                // date label top constrained 5-pts from bottom of detail label
                // leading is equal to name label leading
                // trailing is marginGuide trailing
                dateLabel.topAnchor.constraint(equalTo: detailLabel.bottomAnchor, constant: 5.0),
                dateLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor, constant: 0.0),
                dateLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 0.0),
    
                // date label bottom constrained to marginGuide bottom
                dateLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: 0.0),
    
                ])
    
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    }
    
    class BookTableViewController: UITableViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            tableView.register(BookTableViewCell.self, forCellReuseIdentifier: "BookCell")
    
            // use a larger value if we expect rows to usually have multiple lines of text
            tableView.estimatedRowHeight = 60
    
        }
    
        override func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
    
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 10
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "BookCell", for: indexPath) as! BookTableViewCell
    
            if indexPath.row % 2 == 0 {
                cell.nameLabel.text = "Row: \(indexPath.row) Name"
                cell.detailLabel.text = "Row: \(indexPath.row) Detail"
                cell.dateLabel.text = "Row: \(indexPath.row) Date"
            } else {
                cell.nameLabel.text = "Row: \(indexPath.row) Name with\nembedded new-line character."
                cell.detailLabel.text = "Row: \(indexPath.row) Detail label with enough text that it will need to word-wrap. Height of label will expand as needed."
                cell.dateLabel.text = "Row: \(indexPath.row) Date"
            }
    
            return cell
    
        }
    
    }
    

    And the results are (vertically-centered imageView):

    enter image description here

    enter image description here

    top-aligned imageView:

    enter image description here