Create custom UITableViewCell with using stack view and auto layout - swift

I have 2 questions about constraints and auto layout:
How programmatically create a cell-like in the picture below? I didn't understand how to programmatically add auto-layout and constraints for my cell.
How to assign default Apple layout margins to the cell? (for example, left inset in default cell equal 20 pt for big screens and 16 pt for small).
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)
}
}
Result:
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)...
Do you want your cells to look like "columns"
Do you want the "detail" to be right-aligned?
Might the detail label be multi-line?
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...
we set the cell's stackView constraints relative to the contentView not to the content view's layout margins guide.
we set the table view's separatorInset so the left inset matches the stack view's leading anchor.
we also need to set each cell's .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:

Related

Expand UITableViewCell (custom) on tap - autosize cell - some issues

In my project (UIKit, programmatic UI) I have a UITableView with sections. The cells use a custom class. On load all cells just show 3 lines of info (2 labels). On tap, all contents will be displayed. Therefor I've setup my custom cell class to have two containers, one for the 3 line preview and one for the full contents. These containers are added/removed from the cell's content view when needed when the user taps the cell by calling a method (toggleFullView) on the custom cell class. This method is called from the view controller in didSelectRowAt:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let annotation = annotationsController.getAnnotationFor(indexPath)
//Expandable cell
guard let cell = tableView.cellForRow(at: indexPath) as? AnnotationCell else { return }
cell.toggleFullView()
tableView.reloadRows(at: [indexPath], with: .none)
// tableView.reloadData()
}
Basically it works, but there are some issues:
I have to double tap the cell for it to expand and again to make it collapse again. The first tap will perform the row animation of tableView.reloadRows(at: [indexPath], with: .none) and the second tap will perform the expanding. If I substitute reloadRows with tableView.reloadData() the expanding and collapsing will happen after a single tap! But that is disabling any animations obviously, it just snaps into place. How Do I get it to work with one tap?
When the cell expands, some other random cells are also expanded. I guess this has something to do with reusable cells, but I have not been able to remedy this. See the attached Video (https://www.youtube.com/watch?v=rOkuqMnArEU).
I want to be the expanded cell to collapse once I tap another cell to expand, how do I perceive that?
My custom cell class:
import UIKit
class AnnotationCell: UITableViewCell, SelfConfiguringAnnotationCell {
//MARK: - Properties
private let titleLabelPreview = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let titleLabelDetails = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let detailsLabelShort = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 2)
private let detailsLabelLong = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 0)
private let mapImageLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let lastEditedLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let checkmarkImageView = UIImageView()
private var checkmarkView = UIView()
private var previewDetailsView = UIStackView()
private var fullDetailsView = UIStackView()
private var showFullDetails = false
//MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureContents()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
let padding: CGFloat = 5
if contentView.subviews.contains(previewDetailsView) {
//Constrain the preview view
NSLayoutConstraint.activate([
previewDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
previewDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
previewDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
previewDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding)
])
} else {
//Constrain the full view
NSLayoutConstraint.activate([
fullDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
fullDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
fullDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
fullDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding)
])
}
}
//MARK: - Actions
///Expand and collapse the cell
func toggleFullView() {
showFullDetails.toggle()
if showFullDetails {
//show the full version
if contentView.subviews.contains(previewDetailsView) {
previewDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(fullDetailsView) {
contentView.addSubview(fullDetailsView)
}
} else {
//show the preview version
if contentView.subviews.contains(fullDetailsView) {
fullDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(previewDetailsView) {
contentView.addSubview(previewDetailsView)
}
}
UIView.animate(withDuration: 1.2) {
self.layoutIfNeeded()
}
}
//MARK: - Layout
private func configureContents() {
backgroundColor = .clear
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
selectionStyle = .none
detailsLabelShort.adjustsFontSizeToFitWidth = false
detailsLabelLong.adjustsFontSizeToFitWidth = false
checkmarkView.translatesAutoresizingMaskIntoConstraints = false
checkmarkView.addSubview(checkmarkImageView)
checkmarkImageView.tintColor = .systemOrange
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView = UIStackView(arrangedSubviews: [titleLabelPreview, detailsLabelShort])
previewDetailsView.axis = .vertical
previewDetailsView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView.addBackground(.blue)
fullDetailsView = UIStackView(arrangedSubviews: [titleLabelDetails, detailsLabelLong, mapImageLabel, lastEditedLabel])
fullDetailsView.axis = .vertical
fullDetailsView.translatesAutoresizingMaskIntoConstraints = false
fullDetailsView.addBackground(.green)
//By default only add the preview View
contentView.addSubviews(checkmarkView, previewDetailsView)
let padding: CGFloat = 5
NSLayoutConstraint.activate([
//Constrain the checkmark image view to the top left with a fixed height and width
checkmarkImageView.widthAnchor.constraint(equalToConstant: 24),
checkmarkImageView.heightAnchor.constraint(equalTo: checkmarkImageView.widthAnchor),
checkmarkImageView.centerYAnchor.constraint(equalTo: checkmarkView.centerYAnchor),
checkmarkImageView.centerXAnchor.constraint(equalTo: checkmarkView.centerXAnchor),
checkmarkView.widthAnchor.constraint(equalToConstant: 30),
checkmarkView.heightAnchor.constraint(equalTo: checkmarkView.widthAnchor),
checkmarkView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
checkmarkView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: padding)
])
self.layoutIfNeeded()
}
//MARK: - Configure cell with data
func configure(with annotation: AnnotationsController.Annotation) {
titleLabelPreview.text = annotation.title
titleLabelDetails.text = annotation.title
detailsLabelShort.text = annotation.details
detailsLabelLong.text = annotation.details
checkmarkImageView.image = annotation.complete ? ProjectImages.Annotation.checkmark : nil
lastEditedLabel.text = annotation.lastEdited.customMediumToString
mapImageLabel.text = annotation.mapImage?.title ?? "No map image attached"
}
}
Ok, got it fixed, a fully expanding tableview cell. Key things are invalidating the layout in the custom cell class and calling beginUpdates() and endUpdates() on the tableView!
In my viewController:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Expandable cell
guard let cell = tableView.cellForRow(at: indexPath) as? AnnotationCell else { return }
cell.toggleFullView()
tableView.beginUpdates()
tableView.endUpdates()
}
and my custom cell class with the toggleFullView() method:
class AnnotationCell: UITableViewCell, SelfConfiguringAnnotationCell {
//MARK: - Properties
private let titleLabelPreview = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let titleLabelDetails = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let detailsLabelShort = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 2)
private let detailsLabelLong = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 0)
private let mapImageLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let lastEditedLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let checkmarkImageView = UIImageView()
private var checkmarkView = UIView()
private var previewDetailsView = UIStackView()
private var fullDetailsView = UIStackView()
let padding: CGFloat = 5
var showFullDetails = false
//MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureContents()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Actions
///Expand and collapse the cell
func toggleFullView() {
//Show the full contents
print("ShowFullDetails = \(showFullDetails.description.uppercased())")
if showFullDetails {
print("Show full contents")
if contentView.subviews.contains(previewDetailsView) {
previewDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(fullDetailsView) {
contentView.addSubview(fullDetailsView)
}
NSLayoutConstraint.activate([
fullDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
fullDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
fullDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
fullDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding)
])
//Show preview contents
} else {
print("Show preview contents")
if contentView.subviews.contains(fullDetailsView) {
fullDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(previewDetailsView) {
contentView.addSubview(previewDetailsView)
}
NSLayoutConstraint.activate([
previewDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
previewDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
previewDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
previewDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
showFullDetails.toggle()
//Invalidate current layout &
self.setNeedsLayout()
}
override func prepareForReuse() {
//Make sure reused cells start in the preview mode!
// showFullDetails = false
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
NSLayoutConstraint.activate([
//Constrain the checkmark image view to the top left with a fixed height and width
checkmarkImageView.widthAnchor.constraint(equalToConstant: 24),
checkmarkImageView.heightAnchor.constraint(equalTo: checkmarkImageView.widthAnchor),
checkmarkImageView.centerYAnchor.constraint(equalTo: checkmarkView.centerYAnchor),
checkmarkImageView.centerXAnchor.constraint(equalTo: checkmarkView.centerXAnchor),
checkmarkView.widthAnchor.constraint(equalToConstant: 30),
checkmarkView.heightAnchor.constraint(equalTo: checkmarkView.widthAnchor),
checkmarkView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
checkmarkView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: padding)
])
}
//MARK: - Layout
private func configureContents() {
//Setup Views
backgroundColor = .clear
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
selectionStyle = .none
detailsLabelShort.adjustsFontSizeToFitWidth = false
detailsLabelLong.adjustsFontSizeToFitWidth = false
checkmarkView.translatesAutoresizingMaskIntoConstraints = false
checkmarkView.addSubview(checkmarkImageView)
checkmarkImageView.tintColor = .systemOrange
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView = UIStackView(arrangedSubviews: [titleLabelPreview, detailsLabelShort])
previewDetailsView.axis = .vertical
previewDetailsView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView.addBackground(.blue)
fullDetailsView = UIStackView(arrangedSubviews: [titleLabelDetails, detailsLabelLong, mapImageLabel, lastEditedLabel])
fullDetailsView.axis = .vertical
fullDetailsView.translatesAutoresizingMaskIntoConstraints = false
fullDetailsView.addBackground(.green)
//By default only show the preview View
contentView.addSubviews(checkmarkView)
//Setup preview/DetailView
toggleFullView()
}
//MARK: - Configure cell with data
func configure(with annotation: AnnotationsController.Annotation) {
titleLabelPreview.text = annotation.title
titleLabelDetails.text = annotation.title
detailsLabelShort.text = annotation.details
detailsLabelLong.text = annotation.details
checkmarkImageView.image = annotation.complete ? ProjectImages.Annotation.checkmark : nil
lastEditedLabel.text = annotation.lastEdited.customMediumToString
mapImageLabel.text = annotation.mapImage?.title ?? "No map image attached"
}
}
HTH!

Separator line appear early than cell title and image goes beyond of UIImageView

I programmatically created UITableView and Cell. All work good, but I have a couple questions about constraints and auto layout:
Constraints with layoutMarginsGuide:
In screens where width 375, lower separator and cell starts from one point. On big size screens, separator appears earlier than the cell title(you can see this in attached screenshot). How I can fix this?
The image of the red rectangle with round corners in the attached screenshot goes beyond of UIImageView. I added constraints to UIImageView, but this not help me. How I can fix this?
Constraints without layoutMarginsGuide:
I want that stackView to cover the whole cell. How can I add constraints without using layoutMarginsGuide and avoid console warnings?
How can I add height constraints? I want that my stackView or title was 100 pt.
Screenshot, cell icon, and code you can found below.
Thanks for your answer.
Rectangle with rounded corners
Screenshot:
Code:
import Foundation
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// MARK: - Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
let tableViewstyle = UITableView.Style.insetGrouped
let tableView = UITableView(frame: CGRect(), style: tableViewstyle)
tableView.delegate = self
tableView.dataSource = self
tableView.register(Cell1.self, forCellReuseIdentifier: String(Cell1.description()))
tableView.tableFooterView = UIView()
tableView.translatesAutoresizingMaskIntoConstraints = false
self.view.backgroundColor = .systemBackground
self.view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.widthAnchor.constraint(equalTo: self.view.widthAnchor),
tableView.heightAnchor.constraint(equalTo: self.view.heightAnchor)
])
}
// MARK: - numberOfSections
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// MARK: - numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 7
}
// MARK: - cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(Cell1.description())) as! Cell1
cell.title.text = "Cell \(indexPath.row)"
cell.icon2.image = UIImage(named: "Icon")
return cell
}
}
// MARK: - Cell
class Cell1: UITableViewCell {
// MARK: - Properties
let title = UILabel()
let icon2 = UIImageView()
let stackView = UIStackView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Off translatesAutoresizingMaskIntoConstraints
title.translatesAutoresizingMaskIntoConstraints = false
icon2.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
// Set constraints
icon2.addConstraint(NSLayoutConstraint(item: icon2, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30))
icon2.addConstraint(NSLayoutConstraint(item: icon2, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30))
// StackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// TitleAndDetailStackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// Hugging
title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
// Resistance
title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
icon2.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
title.textAlignment = .left
title.numberOfLines = 0
// Highlight stackView and set colors
stackView.addBackground(color: .green)
title.textColor = .label
icon2.backgroundColor = .cyan
// Add title and detail
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(icon2)
icon2.contentMode = .center
// 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),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
// MARK: - AddBackground
extension UIStackView {
func addBackground(color: UIColor) {
let subView = UIView(frame: bounds)
subView.backgroundColor = color
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
insertSubview(subView, at: 0)
}
}
To achieve this we need to change constraints to contentView. and set leadingAnchor.constraint constant equal to 16.0.
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
])
Change icon2.contentMode = .center to icon2.contentMode = .scaleAspectFit
StackView that cover the whole cell:
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
])
To change cell height we can simply add height constraint to any object inside the cell:
title.addConstraint(NSLayoutConstraint(item: title, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100))

Button Constraints within a StackView (Swift Programmatically)

I'm new to setting up StackViews and Buttons programmatically. I am getting some strange behavior with my constraints I cannot figure out what I'm doing wrong. It feels like I'm missing something simple. Any help is greatly appreciated!
I am trying to add two buttons to a StackView to create a custom tab bar. However, when I add the constraints to the buttons they are showing up outside the bottom of StackView. It's like the top constraint of Earth image isn't working. Any ideas? See image and code below.
// View to put in the StackView
class ProfileBottomTabBarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = .blue
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// Calculate the screen height
public var screenHeight: CGFloat {
return UIScreen.main.bounds.height
}
// StackView height set to a proporation of screen height
let stackViewHeight = screenHeight * 0.07
// Views to put in the StackView
let profileIconView = ProfileBottomTabBarView()
let actIconView = ActBottomTabBarView()
let achieveIconView = AchieveBottomTabBarView()
let growIconView = GrowBottomTabBarView()
// Buttons to put in the Views
let profileButton = UIButton(type: .system)
let actButton = UIButton(type: .system)
let achieveButton = UIButton(type: .system)
let growButton = UIButton(type: .system)
let profileButtonText = UIButton(type: .system)
let actButtonText = UIButton(type: .system)
let achieveButtonText = UIButton(type: .system)
let growButtonText = UIButton(type: .system)
// Stackview setup
lazy var stackView: UIStackView = {
let stackV = UIStackView(arrangedSubviews: [profileIconView, actIconView, achieveIconView, growIconView])
stackV.translatesAutoresizingMaskIntoConstraints = false
stackV.axis = .horizontal
stackV.spacing = 20
stackV.distribution = .fillEqually
return stackV
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
// Add StackView
view.addSubview(stackView)
stackView.bottomAnchor.constraint(equalTo: view.safeBottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.safeLeadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.safeTrailingAnchor).isActive = true
// Set height of the bottom tab bar as a proportion of the screen height.
stackView.heightAnchor.constraint(equalToConstant: stackViewHeight).isActive = true
profileIconView.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
profileIconView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
profileIconView.heightAnchor.constraint(equalToConstant: stackViewHeight).isActive = true
// Add Buttons to the View
profileIconView.addSubview(profileButton)
profileIconView.addSubview(profileButtonText)
profileButton.translatesAutoresizingMaskIntoConstraints = false
profileButtonText.translatesAutoresizingMaskIntoConstraints = false
// Profile Button with Earth Image Setup
profileButton.setImage(UIImage(named: "earthIcon"), for: .normal)
profileButton.imageView?.contentMode = .scaleAspectFit
profileButton.topAnchor.constraint(equalTo: profileIconView.topAnchor).isActive = true
profileButton.bottomAnchor.constraint(equalTo: profileButtonText.topAnchor).isActive = true
profileButton.centerXAnchor.constraint(equalTo: profileIconView.centerXAnchor).isActive = true
//Set height of icon to a proportion of the stackview height
let profileButtonHeight = stackViewHeight * 0.8
profileButton.heightAnchor.constraint(equalTo: profileIconView.heightAnchor, constant: profileButtonHeight).isActive = true
profileButton.widthAnchor.constraint(equalToConstant: profileButtonHeight).isActive = true
profileButton.imageView?.widthAnchor.constraint(equalToConstant: profileButtonHeight)
profileButton.imageView?.heightAnchor.constraint(equalToConstant: profileButtonHeight)
// Profile Text Button Setup
profileButtonText.setTitle("Profile", for: .normal)
profileButtonText.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
profileButtonText.setTitleColor(.white, for: .normal)
profileButtonText.topAnchor.constraint(equalTo: profileButton.bottomAnchor).isActive = true
profileButtonText.bottomAnchor.constraint(equalTo: profileIconView.bottomAnchor).isActive = true
profileButtonText.centerXAnchor.constraint(equalTo: profileIconView.centerXAnchor).isActive = true
//Set height of icon to a proportion of the stackview height
let profileButtonTextHeight = stackViewHeight * 0.2
profileButton.heightAnchor.constraint(equalTo: profileIconView.heightAnchor, constant: profileButtonTextHeight).isActive = true
profileButtonText.widthAnchor.constraint(equalToConstant: 40).isActive = true
}
A few things wrong with your constraints...
You're calculating heights / widths and using them as constants, but those values may (almost certainly will) change based on view lifecycle.
Better to use only related constraints. For example:
// constrain profile image button top, centerX and width relative to the iconView
profileButton.topAnchor.constraint(equalTo: profileIconView.topAnchor),
profileButton.centerXAnchor.constraint(equalTo: profileIconView.centerXAnchor),
profileButton.widthAnchor.constraint(equalTo: profileIconView.widthAnchor, multiplier: 1.0),
// constrain profile text button bottom, centerX and width relative to the iconView
profileButtonText.centerXAnchor.constraint(equalTo: profileIconView.centerXAnchor),
profileButtonText.widthAnchor.constraint(equalTo: profileIconView.widthAnchor, multiplier: 1.0),
profileButtonText.bottomAnchor.constraint(equalTo: profileIconView.bottomAnchor),
// constrain bottom of image button to top of text button (with a padding of 4-pts, change to suit)
profileButton.bottomAnchor.constraint(equalTo: profileButtonText.topAnchor, constant: -4.0),
// constrain height of text button to 20% of height of iconView
profileButtonText.heightAnchor.constraint(equalTo: profileIconView.heightAnchor, multiplier: 0.2),
To make things easier on yourself, I'd suggest creating a BottomTabBarView that handles adding and constraining your buttons:
class BottomTabBarView: UIView {
var theImageButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.imageView?.contentMode = .scaleAspectFit
return v
}()
var theTextButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
v.setTitleColor(.white, for: .normal)
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
convenience init(withImageName imageName: String, labelText: String, bkgColor: UIColor) {
self.init()
self.commonInit()
theImageButton.setImage(UIImage(named: imageName), for: .normal)
theTextButton.setTitle(labelText, for: .normal)
backgroundColor = bkgColor
}
func commonInit() -> Void {
self.translatesAutoresizingMaskIntoConstraints = false
addSubview(theImageButton)
addSubview(theTextButton)
NSLayoutConstraint.activate([
// constrain profile image button top, centerX and width of the iconView
theImageButton.topAnchor.constraint(equalTo: topAnchor),
theImageButton.centerXAnchor.constraint(equalTo: centerXAnchor),
theImageButton.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0),
// constrain profile text button bottom, centerX and width of the iconView
theTextButton.centerXAnchor.constraint(equalTo: centerXAnchor),
theTextButton.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0),
theTextButton.bottomAnchor.constraint(equalTo: bottomAnchor),
// constrain bottom of image button to top of text button
theImageButton.bottomAnchor.constraint(equalTo: theTextButton.topAnchor, constant: -4.0),
// set text button height to 20% of view height (instead of using intrinsic height)
theTextButton.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.2),
])
}
}
Now you can create each view with a single line, as in:
profileIconView = BottomTabBarView(withImageName: "earthIcon", labelText: "Profile", bkgColor: .blue)
And your view controller class becomes much simpler / cleaner:
class BenViewController: UIViewController {
// Views to put in the StackView
var profileIconView = BottomTabBarView()
var actIconView = BottomTabBarView()
var achieveIconView = BottomTabBarView()
var growIconView = BottomTabBarView()
// Stackview setup
lazy var stackView: UIStackView = {
let stackV = UIStackView(arrangedSubviews: [profileIconView, actIconView, achieveIconView, growIconView])
stackV.translatesAutoresizingMaskIntoConstraints = false
stackV.axis = .horizontal
stackV.spacing = 20
stackV.distribution = .fillEqually
return stackV
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
profileIconView = BottomTabBarView(withImageName: "earthIcon", labelText: "Profile", bkgColor: .blue)
actIconView = BottomTabBarView(withImageName: "actIcon", labelText: "Action", bkgColor: .brown)
achieveIconView = BottomTabBarView(withImageName: "achieveIcon", labelText: "Achieve", bkgColor: .red)
growIconView = BottomTabBarView(withImageName: "growIcon", labelText: "Grow", bkgColor: .purple)
// Add StackView
view.addSubview(stackView)
NSLayoutConstraint.activate([
// constrain stackView to bottom, leading and trailing (to safeArea)
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
// Set height of the stackView (the bottom tab bar) as a proportion of the view height (7%).
stackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.07),
])
}
}

Custom cell and stack-view programmatically

I am creating a tableview with a custom cell programmatically. I would like to utilise a stack view with arranged subviews within the custom cell. However, all my efforts have failed. Firstly is it possible to do this?
Secondly I am placing the code after:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: "cellId")
I am using this code to create the stack view:
let thestack: UIStackView = {
let sv = UIStackView()
sv.distribution = .fillEqually
sv.axis = .vertical
sv.spacing = 8
return sv
}()
But I can't add the arranged subviews to this in addition to which after I addsubview(thestack) and list all the constraints - none of my data shows in the custom cell. Any help would be appreciated.
Yes, it is possible. Follow like below:
class CustomTableViewCell: UITableViewCell {
let stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 10
stackView.distribution = .fillEqually
return stackView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
addSubview(stackView)
stackView.leftAnchor.constraint(equalTo: leftAnchor, constant: 10).isActive = true
stackView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
stackView.rightAnchor.constraint(equalTo: rightAnchor, constant: -10).isActive = true
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
let redView = UIView()
redView.backgroundColor = .red
let yellowView = UIView()
yellowView.backgroundColor = .yellow
let blackView = UIView()
blackView.backgroundColor = .black
stackView.addArrangedSubview(redView)
stackView.addArrangedSubview(yellowView)
stackView.addArrangedSubview(blackView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

UIView resize to fit labels inside of it

I have a UIView that I'm appending into a stack view on my main page of my app
This is the class of my view:
class MyCustomView: UIView {
public let leftLabel: UILabel = UILabel(frame: .zero)
public let rightLabel: UILabel = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(leftLabel)
addSubview(rightLabel)
leftLabel.translatesAutoresizingMaskIntoConstraints = false
rightLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
leftLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.4),
leftLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
leftLabel.topAnchor.constraint(equalTo: topAnchor),
leftLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
rightLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.6),
rightLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
rightLabel.topAnchor.constraint(equalTo: topAnchor),
rightLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
])
leftLabel.text = "Short string"
rightLabel.text = "Short string too"
}
}
And I append to my main stack view with:
let myCustomView = MyCustomView(frame: .zero)
stackView.addArrangedSubview(myCustomView)
This loads in my label's correctly and resizes everything as I'd want.
However, in my main class, I am updating myCustomView.rightLabel.text = <New Way Longer Text That Takes 2 Lines Instead of One>
The text is updating properly, but my myCustomView size is not resizing, so part of the text is just being cut-off
I have tried following other answers on here, but none of them seem to work for me.
Am I missing something small in order to force the resize of the customView to fit the label inside of it?
Thank you in advance
Your code does not show that you set the .numberOfLines in the label(s) to 0 to allow for multi-line labels.
Adding only that, should allow your labels to grow in height and to expand your custom view. However... that will also make both labels expand to the size of the tallest label, resulting in the text of the "shorter" label being vertically centered (I added background colors to make it easy to see the frames / bounds of the views):
If you constrain the Bottom of your custom view to the Bottom of each label at greaterThanOrEqualTo you can keep the labels "top-aligned":
You can run this code directly in a Playground page to see the results:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyCustomView: UIView {
public let leftLabel: UILabel = UILabel(frame: .zero)
public let rightLabel: UILabel = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(leftLabel)
addSubview(rightLabel)
// background colors so we can see the view frames
backgroundColor = .cyan
leftLabel.backgroundColor = .yellow
rightLabel.backgroundColor = .green
// we want multi-line labels
leftLabel.numberOfLines = 0
rightLabel.numberOfLines = 0
// use auto-layout
leftLabel.translatesAutoresizingMaskIntoConstraints = false
rightLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain to top
leftLabel.topAnchor.constraint(equalTo: topAnchor),
// constrain to left
leftLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
// constrain width = 40%
leftLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.4),
// constrain to top
rightLabel.topAnchor.constraint(equalTo: topAnchor),
// constrain to right
rightLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
// constrain width = 60%
rightLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.6),
// constrain bottom of view (self) to >= 0 from bottom of leftLabel
bottomAnchor.constraint(greaterThanOrEqualTo: leftLabel.bottomAnchor, constant: 0.0),
// constrain bottom of view (self) to >= 0 from bottom of rightLabel
bottomAnchor.constraint(greaterThanOrEqualTo: rightLabel.bottomAnchor, constant: 0.0),
])
leftLabel.text = "Short string"
rightLabel.text = "Short string too"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MyViewController : UIViewController {
var theButton: UIButton = {
let b = UIButton()
b.setTitle("Tap Me", for: .normal)
b.translatesAutoresizingMaskIntoConstraints = false
b.backgroundColor = .red
return b
}()
var theStackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.spacing = 8
v.distribution = .equalSpacing
return v
}()
var myView = MyCustomView()
// on button tap, change the text in the label(s)
#objc func didTap(_ sender: Any?) -> Void {
myView.leftLabel.text = "Short string with\nA\nB\nC\nD\nE"
myView.rightLabel.text = "Short string too\nA\nB"
}
override func loadView() {
let view = UIView()
self.view = view
view.backgroundColor = .white
view.addSubview(theButton)
// constrain button to Top: 32 and centerX
NSLayoutConstraint.activate([
theButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 32.0),
theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
view.addSubview(theStackView)
// constrain stack view to Top: 100 and Leading/Trailing" 0
// no Bottom or Height constraint
NSLayoutConstraint.activate([
theStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100.0),
theStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
theStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
])
theStackView.addArrangedSubview(myView)
// add an action for the button tap
theButton.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()