iosswiftuikit

Layout problem with UITableViewCell Contraints


this is the outcomeI am trying to build a UITableViewCell and code the constraints. I am having problems getting the StackViews I have layed out properly. Below is my code.

What I want is the imagesViews in the imageStackView to be adjacent to the numberLabel. Then centered in between the left edge of the cell and the vertical separator. I have always struggled to understand the constraints.

class CustomTableViewCell: UITableViewCell {
    // Left Section (number label + images)
    let numberLabel = UILabel()
    let imageStackView = UIStackView()
    let leftGroupStackView = UIStackView() // Combines numberLabel and imageStackView

    // Middle Line
    let verticalLine = UIView()

    // Right Section (stacked labels with separator)
    let topLabel = UILabel()
    let bottomLabel = UILabel()
    let horizontalLine = UIView()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
        setupConstraints()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupViews()
        setupConstraints()
    }

    private func setupViews() {
        // Configure numberLabel
        numberLabel.font = .systemFont(ofSize: 16, weight: .bold)
        numberLabel.textAlignment = .center

        // Configure imageStackView
        imageStackView.axis = .horizontal
        imageStackView.spacing = 8 // Reduced spacing between images
        imageStackView.alignment = .center

        // Combine numberLabel and imageStackView into leftGroupStackView
        leftGroupStackView.axis = .horizontal
        leftGroupStackView.alignment = .center
        leftGroupStackView.spacing = 8 // Reduced spacing between numberLabel and images
        leftGroupStackView.addArrangedSubview(numberLabel)
        leftGroupStackView.addArrangedSubview(imageStackView)
        contentView.addSubview(leftGroupStackView)

        // Configure verticalLine
        verticalLine.backgroundColor = .lightGray
        contentView.addSubview(verticalLine)

        // Configure topLabel
        topLabel.font = .systemFont(ofSize: 14)
        topLabel.textAlignment = .right
        contentView.addSubview(topLabel)

        // Configure bottomLabel
        bottomLabel.font = .systemFont(ofSize: 14)
        bottomLabel.textAlignment = .right
        contentView.addSubview(bottomLabel)

        // Configure horizontalLine
        horizontalLine.backgroundColor = .lightGray
        contentView.addSubview(horizontalLine)
    }

    private func setupConstraints() {
        // Disable autoresizing masks
        [leftGroupStackView, verticalLine, topLabel, bottomLabel, horizontalLine].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }

        // Center the leftGroupStackView horizontally and ensure proper spacing
        NSLayoutConstraint.activate([
            leftGroupStackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), // Center vertically
            leftGroupStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            leftGroupStackView.trailingAnchor.constraint(lessThanOrEqualTo: verticalLine.leadingAnchor, constant: -16)
        ])

        // Vertical line constraints
        NSLayoutConstraint.activate([
            verticalLine.leadingAnchor.constraint(equalTo: leftGroupStackView.trailingAnchor, constant: 16),
            verticalLine.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            verticalLine.widthAnchor.constraint(equalToConstant: 1),
            verticalLine.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.8)
        ])

        // Top label constraints
        NSLayoutConstraint.activate([
            topLabel.leadingAnchor.constraint(equalTo: verticalLine.trailingAnchor, constant: 16),
            topLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            topLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16)
        ])

        // Horizontal line constraints
        NSLayoutConstraint.activate([
            horizontalLine.leadingAnchor.constraint(equalTo: topLabel.leadingAnchor),
            horizontalLine.trailingAnchor.constraint(equalTo: topLabel.trailingAnchor),
            horizontalLine.topAnchor.constraint(equalTo: topLabel.bottomAnchor, constant: 4),
            horizontalLine.heightAnchor.constraint(equalToConstant: 1)
        ])

        // Bottom label constraints
        NSLayoutConstraint.activate([
            bottomLabel.leadingAnchor.constraint(equalTo: topLabel.leadingAnchor),
            bottomLabel.trailingAnchor.constraint(equalTo: topLabel.trailingAnchor),
            bottomLabel.topAnchor.constraint(equalTo: horizontalLine.bottomAnchor, constant: 4),
            bottomLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -16)
        ])
    }



    
    
    // Update the cell with data
    func configure(number: Int, images: [UIImage], topText: String, bottomText: String) {
        numberLabel.text = "\(number)"
        topLabel.text = topText
        bottomLabel.text = bottomText

        // Remove existing image views
        imageStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }

        // Add new image views with larger size
        for image in images {
            let imageView = UIImageView(image: image)
            imageView.contentMode = .scaleAspectFit
            imageView.widthAnchor.constraint(equalToConstant: 36).isActive = true // Slightly larger size
            imageView.heightAnchor.constraint(equalToConstant: 36).isActive = true // Slightly larger size
            imageStackView.addArrangedSubview(imageView)
        }
    }
}

Solution

  • What I want is the imagesViews in the imageStackView to be adjacent to the numberLabel. Then centered in between the left edge of the cell and the vertical separator.

    Assuming this is your goal:

    target

    You're pretty close. What you need to do is embed leftGroupStackView in a "holder" UIView. You can then allow the holder view to stretch the full width, and center the leftGroupStackView horizontally in the holder view.

    Here's your cell class with that modification:

    class CustomTableViewCell: UITableViewCell {
        // Left Section (number label + images)
        let numberLabel = UILabel()
        let imageStackView = UIStackView()
        let leftGroupStackView = UIStackView() // Combines numberLabel and imageStackView
        
        let lgsvHolder = UIView() // holds the leftGroupStackView so it can be horizontally centered
    
        // Middle Line
        let verticalLine = UIView()
        
        // Right Section (stacked labels with separator)
        let topLabel = UILabel()
        let bottomLabel = UILabel()
        let horizontalLine = UIView()
        
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            setupViews()
            setupConstraints()
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            setupViews()
            setupConstraints()
        }
        
        private func setupViews() {
            // Configure numberLabel
            numberLabel.font = .systemFont(ofSize: 16, weight: .bold)
            numberLabel.textAlignment = .center
            
            // Configure imageStackView
            imageStackView.axis = .horizontal
            imageStackView.spacing = 8 // Reduced spacing between images
            imageStackView.alignment = .center
            
            // Combine numberLabel and imageStackView into leftGroupStackView
            leftGroupStackView.axis = .horizontal
            leftGroupStackView.alignment = .center
            leftGroupStackView.spacing = 8 // Reduced spacing between numberLabel and images
            leftGroupStackView.addArrangedSubview(numberLabel)
            leftGroupStackView.addArrangedSubview(imageStackView)
            
            // add leftGroupStackView to "holder" view
            lgsvHolder.addSubview(leftGroupStackView)
            
            // add the "holder" view to contentView
            contentView.addSubview(lgsvHolder)
            
            // Configure verticalLine
            verticalLine.backgroundColor = .lightGray
            contentView.addSubview(verticalLine)
            
            // Configure topLabel
            topLabel.font = .systemFont(ofSize: 14)
            topLabel.textAlignment = .right
            contentView.addSubview(topLabel)
            
            // Configure bottomLabel
            bottomLabel.font = .systemFont(ofSize: 14)
            bottomLabel.textAlignment = .right
            contentView.addSubview(bottomLabel)
            
            // Configure horizontalLine
            horizontalLine.backgroundColor = .lightGray
            contentView.addSubview(horizontalLine)
        }
        
        private func setupConstraints() {
            // Disable autoresizing masks
            [lgsvHolder, leftGroupStackView, verticalLine, topLabel, bottomLabel, horizontalLine].forEach {
                $0.translatesAutoresizingMaskIntoConstraints = false
            }
            
            // Center the leftGroupStackView horizontally in the lgsvHolder view
            NSLayoutConstraint.activate([
                lgsvHolder.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
                lgsvHolder.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
                lgsvHolder.trailingAnchor.constraint(equalTo: verticalLine.leadingAnchor, constant: -16),
                
                leftGroupStackView.centerXAnchor.constraint(equalTo: lgsvHolder.centerXAnchor),
                leftGroupStackView.topAnchor.constraint(equalTo: lgsvHolder.topAnchor, constant: 0),
                leftGroupStackView.bottomAnchor.constraint(equalTo: lgsvHolder.bottomAnchor, constant: 0)
            ])
    
            // Vertical line constraints
            NSLayoutConstraint.activate([
                verticalLine.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
                verticalLine.widthAnchor.constraint(equalToConstant: 1),
                verticalLine.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.8)
            ])
            
            // Top label constraints
            NSLayoutConstraint.activate([
                topLabel.leadingAnchor.constraint(equalTo: verticalLine.trailingAnchor, constant: 16),
                topLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
                topLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16)
            ])
            
            // Horizontal line constraints
            NSLayoutConstraint.activate([
                horizontalLine.leadingAnchor.constraint(equalTo: topLabel.leadingAnchor),
                horizontalLine.trailingAnchor.constraint(equalTo: topLabel.trailingAnchor),
                horizontalLine.topAnchor.constraint(equalTo: topLabel.bottomAnchor, constant: 4),
                horizontalLine.heightAnchor.constraint(equalToConstant: 1)
            ])
            
            // Bottom label constraints
            NSLayoutConstraint.activate([
                bottomLabel.leadingAnchor.constraint(equalTo: topLabel.leadingAnchor),
                bottomLabel.trailingAnchor.constraint(equalTo: topLabel.trailingAnchor),
                bottomLabel.topAnchor.constraint(equalTo: horizontalLine.bottomAnchor, constant: 4),
                bottomLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -16)
            ])
        }
        
        // Update the cell with data
        func configure(number: Int, images: [UIImage], topText: String, bottomText: String) {
            numberLabel.text = "\(number)"
            topLabel.text = topText
            bottomLabel.text = bottomText
            
            // Remove existing image views
            imageStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
            
            // Add new image views with larger size
            for image in images {
                let imageView = UIImageView(image: image)
                imageView.contentMode = .scaleAspectFit
                imageView.widthAnchor.constraint(equalToConstant: 36).isActive = true // Slightly larger size
                imageView.heightAnchor.constraint(equalToConstant: 36).isActive = true // Slightly larger size
                imageStackView.addArrangedSubview(imageView)
            }
        }
    }
    

    You may need to implement one more detail...

    It appears that your "Ground" and "Unit" labels are dynamic? If so, you may end up with something like this:

    misaligned