I 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)
}
}
}
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:
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: