I have 2 questions about constraints and auto layout:
My current code:
class cellWithTitleAndDetail: UITableViewCell {
// MARK: - Properties
let title = UILabel()
let detail = UILabel()
let stackView = UIStackView()
// MARK: - Override init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
title.translatesAutoresizingMaskIntoConstraints = false
detail.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fillProportionally
// Set color
title.textColor = .white
detail.textColor = .white
// Highlight StackView
stackView.addBackground(color: .blue)
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(detail)
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.isLayoutMarginsRelativeArrangement = true
self.contentView.addSubview(stackView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Update:
Below added my new code and code from DonMag's answer.
New question: "LayoutMarginsGuide" works perfectly on iPhones with screen width that equal to 375 pt(image 375-1). But on big size screens separator appears earlier than the cell(image 414-2). How I can fix this?
New code:
// MARK: - Override init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Off translatesAutoresizingMaskIntoConstraints
title.translatesAutoresizingMaskIntoConstraints = false
detail.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
// Setup stackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// Hugging
title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
detail.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Resistance
title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
detail.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Set textAlignment
title.textAlignment = .left
detail.textAlignment = .right
// Set numberOfLines
title.numberOfLines = 0
// Highlight stackView and set colors
stackView.addBackground(color: .blue)
title.textColor = .white
detail.textColor = .white
// Add title and detail
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(detail)
// Add to subview
self.contentView.addSubview(stackView)
// Get layoutMarginsGuide
let layoutMarginsGuide = contentView.layoutMarginsGuide
// Set constraints
NSLayoutConstraint.activate([
// constrain all 4 sides of the stack view to the content view's layoutMarginsGuide
stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 0.0),
])
}
You can use the Content View's layoutMarginsGuide
:
// only if you want different margins than the content view's margins
//stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
//stackView.isLayoutMarginsRelativeArrangement = true
self.contentView.addSubview(stackView)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
// constrain all 4 sides of the stack view to the
// content view's layoutMarginsGuide
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),
])
As a side note, it's not clear what you really want (and this would be a separate question if this doesn't provide the layout you want)...
Edit
Using your updated code - the only change I made is giving the labels a background color since you didn't show your stackView.addBackground(color: .blue)
code:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Off translatesAutoresizingMaskIntoConstraints
title.translatesAutoresizingMaskIntoConstraints = false
detail.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
// Setup stackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// Hugging
title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
detail.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Resistance
title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
detail.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Set textAlignment
title.textAlignment = .left
detail.textAlignment = .right
// Set numberOfLines
title.numberOfLines = 0
// Highlight stackView and set colors
//stackView.addBackground(color: .blue)
title.backgroundColor = .blue
detail.backgroundColor = .red
title.textColor = .white
detail.textColor = .white
// Add title and detail
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(detail)
// Add to subview
self.contentView.addSubview(stackView)
// Get layoutMarginsGuide
let layoutMarginsGuide = contentView.layoutMarginsGuide
// Set constraints
NSLayoutConstraint.activate([
// constrain all 4 sides of the stack view to the content view's layoutMarginsGuide
stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 0.0),
])
}
This is what I get:
Edit 2
Table view cell separators can change based on device, iOS version, table view style, etc.
For the most reliable consistency, set your own.
Here's an example...
contentView
not to the content view's layout margins guide.separatorInset
so the left inset matches the stack view's leading anchor..separatorInset
equal to our table view's custom .separatorInset
Here's the full code:
class MyTestTableViewController: UITableViewController {
let testTitles: [String] = [
"Yesterday all my troubles seemed so far away, Now it looks as though they're here to stay.",
"She packed my bags last night pre-flight, Zero hour nine AM.",
"When you're weary, feeling small, When tears are in your eyes, I will dry them all."
]
let testDetails: [String] = [
"The Beatles",
"Elton John",
"Simon & Garfunkel",
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(MyCellWithTitleAndDetail.self, forCellReuseIdentifier: "myCell")
// our custom separatorInset
// left matches cell's stackView leading anchor
tableView.separatorInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyCellWithTitleAndDetail
// set cell separatorInset equal to tableView separatorInset
cell.separatorInset = tableView.separatorInset
cell.title.text = testTitles[indexPath.row]
cell.detail.text = testDetails[indexPath.row]
return cell
}
}
class MyCellWithTitleAndDetail: UITableViewCell {
// MARK: - Properties
let title = UILabel()
let detail = UILabel()
let stackView = UIStackView()
// MARK: - Override init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Off translatesAutoresizingMaskIntoConstraints
title.translatesAutoresizingMaskIntoConstraints = false
detail.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
// Setup stackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fillEqually
// if we want the labels to be 50% of the width,
// set stackView.distribution = .fillEqually
// then we don't need any Content Hugging or Compression Resistance priority changes
// // Hugging
// title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
// detail.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
//
// // Resistance
// title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
// detail.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
// Set textAlignment
title.textAlignment = .left
detail.textAlignment = .right
// Set numberOfLines
title.numberOfLines = 0
// Highlight stackView and set colors
title.backgroundColor = .blue
detail.backgroundColor = .red
//stackView.addBackground(color: .blue)
title.textColor = .white
detail.textColor = .white
// Add title and detail
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(detail)
// Add to subview
self.contentView.addSubview(stackView)
// Set constraints
NSLayoutConstraint.activate([
// constrain all 4 sides of the stack view to the content view
// with your own "margins"
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15.0),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15.0),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12.0),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
and the results: