Swift 5 iOS TableViewCell Items Horizontal Overlapping

Viewed 45

I am writing a programatic table view with custom cells for an iPad only app, I have the data coming through and the layout almost there but I am struggling with the widths of UILabel in the UITableViewCell I have content overlapping.

iPad Mini (6th Generation) Showing UITableView

The image at the start of the cell is an know width and height 128 x 64 and so is the last image, QR Code, which is 64 x 64

What I am trying to do is using constraints is make the three UILabel between the two UIImage views layout in a n equal manner with the first contentTitleLabel occupying the most space and the next two occupying an equal space each. Hopefully removing the overlapping I am seeing

This is my table cell code which contains the constraints, I am aware that it's not best practice to set hardcoded widths but I am at a point where I cannot work this out.

import UIKit

class CustomCell: UITableViewCell {
    
    var contentImageView    = UIImageView()
    var contentTitleLabel   = UILabel()
    var contentAuthorLabel  = UILabel()
    var contentDetailsLabel = UILabel()
    var contentQRCodeView   = UIImageView()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        print(style)
        addSubview(contentImageView)
        addSubview(contentTitleLabel)
        addSubview(contentAuthorLabel)
        addSubview(contentDetailsLabel)
        addSubview(contentQRCodeView)
        
        configureImageView()
        configureTitleLabel()
        configureAuthorLabel()
        configureDetailsLabel()
        configureQRCodeView()
        
        setImageConstraints()
        setTitleLabelConstraints()
        setAuthorLabelConstraints()
        setDetailsLabelConstraints()
        setQRCodeConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func set(content: ContentCellItem) {
        contentImageView.image = content.image
        contentTitleLabel.text = content.name
        contentAuthorLabel.text = content.author
        contentDetailsLabel.text = content.details
        contentQRCodeView.image = content.qrCode
    }
    
    func configureImageView() {
        contentImageView.layer.cornerRadius = 10
        contentImageView.clipsToBounds = true
    }
    
    func configureTitleLabel() {
        contentTitleLabel.numberOfLines = 0
        contentTitleLabel.adjustsFontSizeToFitWidth = true
    }
    
    func configureAuthorLabel() {
        contentAuthorLabel.numberOfLines = 0
        //contentAuthorLabel.adjustsFontSizeToFitWidth = true
    }
    
    func configureDetailsLabel() {
        contentDetailsLabel.numberOfLines = 0
        //contentDetailsLabel.adjustsFontSizeToFitWidth = true
    }
    
    func configureQRCodeView() {
        contentQRCodeView.layer.cornerRadius = 10
        contentQRCodeView.clipsToBounds = true
    }

    func setImageConstraints() {
        contentImageView.translatesAutoresizingMaskIntoConstraints                                                  = false
        contentImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive                                  = true
        contentImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive                    = true
        contentImageView.heightAnchor.constraint(equalToConstant: 64).isActive                                      = true
        contentImageView.widthAnchor.constraint(equalToConstant: 128).isActive                                      = true
    }
    
    func setTitleLabelConstraints() {
        contentTitleLabel.translatesAutoresizingMaskIntoConstraints                                                 = false
        contentTitleLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive                                 = true
        contentTitleLabel.leadingAnchor.constraint(equalTo: contentImageView.trailingAnchor, constant: 20).isActive = true
        contentTitleLabel.heightAnchor.constraint(equalToConstant: 64).isActive                                     = true
        contentTitleLabel.widthAnchor.constraint(equalToConstant: 220).isActive                                     = true
        //contentTitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive              = true
    }
    
    func setAuthorLabelConstraints() {
        contentAuthorLabel.translatesAutoresizingMaskIntoConstraints                                                    = false
        contentAuthorLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive                                    = true
        contentAuthorLabel.leadingAnchor.constraint(equalTo: contentTitleLabel.trailingAnchor, constant: 20).isActive   = true
        contentAuthorLabel.heightAnchor.constraint(equalToConstant: 64).isActive                                        = true
        contentAuthorLabel.widthAnchor.constraint(equalToConstant: 50).isActive                                     = true
        //contentAuthorLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive                 = true
    }
    
    func setDetailsLabelConstraints() {
        contentDetailsLabel.translatesAutoresizingMaskIntoConstraints                                                   = false
        contentDetailsLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive                                   = true
        contentDetailsLabel.leadingAnchor.constraint(equalTo: contentAuthorLabel.trailingAnchor, constant: 20).isActive = true
        contentDetailsLabel.heightAnchor.constraint(equalToConstant: 64).isActive                                       = true
        contentDetailsLabel.widthAnchor.constraint(equalToConstant: 50).isActive                                        = true
        //contentDetailsLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive                    = true
    }
    
    func setQRCodeConstraints() {
        contentQRCodeView.translatesAutoresizingMaskIntoConstraints                                                         = false
        contentQRCodeView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive                                         = true
        contentQRCodeView.leadingAnchor.constraint(equalTo: contentDetailsLabel.leadingAnchor, constant: 20).isActive       = true
        contentQRCodeView.heightAnchor.constraint(equalToConstant: 64).isActive                                             = true
        contentQRCodeView.widthAnchor.constraint(equalToConstant: 64).isActive                                              = true
        contentQRCodeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive                        = true
    }
}
1 Answers

You may want to use a horizontal UIStackView for your cell layout.

Since you are explicitly setting the widths of the images and the Author and Details labels, the Title label will take up the remains space.

So, for example, to get this result (I gave the labels background colors so we can see the frames):

enter image description here

Try this code:

struct ContentCellItem {
    var image: UIImage!
    var name: String!
    var author: String!
    var details: String!
    var qrCode: UIImage!
}

class CustomCell: UITableViewCell {
    
    private let contentImageView    = UIImageView()
    private let contentTitleLabel   = UILabel()
    private let contentAuthorLabel  = UILabel()
    private let contentDetailsLabel = UILabel()
    private let contentQRCodeView   = UIImageView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        configureImageView()
        configureTitleLabel()
        configureAuthorLabel()
        configureDetailsLabel()
        configureQRCodeView()
        
        // let's put everything in a horizontal stack view
        let stackView = UIStackView()
        // set spacing as desired
        stackView.spacing = 8
        // change alignment to .center if you want vertical centering of the labels
        stackView.alignment = .top
        
        // add the image views and labels to the stack view
        [contentImageView, contentTitleLabel, contentAuthorLabel, contentDetailsLabel, contentQRCodeView].forEach { v in
            stackView.addArrangedSubview(v)
        }
        
        // add the stack view to the content view
        stackView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(stackView)
        
        // we'll use the built-in margins guide for the "edge padding"
        let g = contentView.layoutMarginsGuide
        
        NSLayoutConstraint.activate([
            
            // width and height of contentImageView
            contentImageView.widthAnchor.constraint(equalToConstant: 128.0),
            contentImageView.heightAnchor.constraint(equalToConstant: 64.0),
            
            // width and height of contentQRCodeView
            contentQRCodeView.widthAnchor.constraint(equalToConstant: 64.0),
            contentQRCodeView.heightAnchor.constraint(equalToConstant: 64.0),
            
            // width of contentAuthorLabel
            //  using 120.0 for example
            contentAuthorLabel.widthAnchor.constraint(equalToConstant: 120.0),
            
            // width of contentDetailsLabel
            //  using 120.0 for example
            contentDetailsLabel.widthAnchor.constraint(equalToConstant: 120.0),
            
            // we will let the Title label stretch to fill the rest of the space

            // constrain all 4 sides of the stack view
            //  change constants as desired
            stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
            
        ])

        // so we can see the element frames
        contentImageView.backgroundColor = .systemRed
        contentTitleLabel.backgroundColor = .cyan
        contentAuthorLabel.backgroundColor = .yellow
        contentDetailsLabel.backgroundColor = .green
        contentQRCodeView.backgroundColor = .systemBlue
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func set(content: ContentCellItem) {
        if content.image != nil {
            contentImageView.image = content.image
        }
        contentTitleLabel.text = content.name
        contentAuthorLabel.text = content.author
        contentDetailsLabel.text = content.details
        if content.qrCode != nil {
            contentQRCodeView.image = content.qrCode
        }
    }
    
    func configureImageView() {
        contentImageView.layer.cornerRadius = 10
        contentImageView.clipsToBounds = true
    }
    
    func configureTitleLabel() {
        contentTitleLabel.numberOfLines = 0
    }
    
    func configureAuthorLabel() {
        contentAuthorLabel.numberOfLines = 0
    }
    
    func configureDetailsLabel() {
        contentDetailsLabel.numberOfLines = 0
    }
    
    func configureQRCodeView() {
        contentQRCodeView.layer.cornerRadius = 10
        contentQRCodeView.clipsToBounds = true
    }
}

class CustomCellTestVC: UITableViewController {
    
    var myData: [ContentCellItem] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // sample data
        for i in 0..<20 {
            var item: ContentCellItem = ContentCellItem()
            item.name = "Name \(i)"
            item.author = "Author \(i)"
            item.details = "Details \(i)"
            myData.append(item)
        }
        
        tableView.register(CustomCell.self, forCellReuseIdentifier: "c")
        tableView.dataSource = self
        tableView.delegate = self
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! CustomCell
        c.set(content: myData[indexPath.row])
        return c
    }
}

We've greatly simplified the constraints... it should be easy for you to see what values you might want to change (I used 120 for the widths of the Author and Details labels to get this result).

Related