How to put a stack view below another stack view - ios

I want to place the dateInfo stack view below the userInfoStack view.
This is what it currently looks like
How can I place the stack view vertically one below the other? I tried adjusting the distribution, but it doesn't solve the problem.
I am trying to create reusable banner in the UI.
class UserCell: UIView {
var rootStack = UIStackView()
var userInfoStackView = UIStackView()
var dateInfoStackView = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame)
setUPViews()
addComponents()
layoutComponents()
}
private func setUPViews(){
rootStack = UIStackView()
rootStack.translatesAutoresizingMaskIntoConstraints = false
rootStack.alignment = .center
rootStack.distribution = .fillEqually
rootStack.spacing = 5
userInfoStackView = UIStackView()
userInfoStackView.axis = .horizontal
userInfoStackView.spacing = 10
dateInfoStackView = UIStackView()
dateInfoStackView.axis = .vertical
dateInfoStackView.spacing = 2
}
private func addComponents() {
userInfoStackView.addArrangedSubview(nameLabel)
userInfoStackView.addArrangedSubview(compCode)
dateInfoStackView.addArrangedSubview(captionLabel)
dateInfoStackView.addArrangedSubview(dateLabel2)
}
private func layoutComponents() {
addSubview(rootStack)
rootStack.addArrangedSubview(userInfoStackView)
rootStack.addArrangedSubview(dateInfoStackView)
rootStack.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
}
}

The axis on the rootStack is not set
Try rootStack.axis = .vertical
since the default value for axis of a UIStackView is horizontal your views are laid out horizontally next to each other
Also set to rootStack.alignment = .leading to align everything to the left margin
So your implementation should look like below
rootStack = UIStackView()
rootStack.translatesAutoresizingMaskIntoConstraints = false
rootStack.axis = .vertical
rootStack.alignment = .leading
rootStack.distribution = .fillEqually
rootStack.spacing = 5

Related

Why my 2D UIViews don't appear on screen?

I'm trying to make UIView that contains 12x7 UIViews with margins. I thought that the best way gonna be make 7 Vertical Stacks and then add all them on one big Horizontal stack. And I coded it, but problem is that this Horizontal Stacks doesn't appear on the screen at all (I've tried Xcode feature to see layers there is nothing).
This is my code:
import UIKit
class CalendarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
// array to add in future in columnsStackView
var columnStacks: [UIStackView] = []
for columns in 1...12 {
// array to add in future in columnStackView
var columnViews: [UIView] = []
for cell in 1...7 {
let cellView = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
cellView.backgroundColor = .orange
columnViews.append(cellView)
}
// create columnStackView and add all 7 views
let columnStackView = UIStackView(arrangedSubviews: columnViews)
columnStackView.axis = .vertical
columnStackView.distribution = .fillEqually
columnStackView.alignment = .fill
columnStackView.spacing = 4
columnStacks.append(columnStackView)
}
// create columnsStackView and add those 12 stacks
let columnsStackView = UIStackView(arrangedSubviews: columnStacks)
columnsStackView.axis = .horizontal
columnsStackView.distribution = .fillEqually
columnsStackView.alignment = .fill
columnsStackView.spacing = 4
columnsStackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(columnsStackView)
}
}
Can you please help me with that!!!
Couple things...
A UIStackView uses auto-layout when arranging its subviews, so this line:
let cellView = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
will create a UIView, but the width and height will be ignored.
You need to set those with constraints:
for cell in 1...7 {
let cellView = UIView()
cellView.backgroundColor = .orange
// we want each "cellView" to be 24x24 points
cellView.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
cellView.heightAnchor.constraint(equalTo: cellView.widthAnchor).isActive = true
columnViews.append(cellView)
}
Now, because we've explicitly set the width and height of the "cellViews" we can set the stack view .distribution = .fill (instead of .fillEqually).
Next, we have to constrain the "outer" stack view (columnsStackView) to the view itself:
// constrain the "outer" stack view to self
NSLayoutConstraint.activate([
columnsStackView.topAnchor.constraint(equalTo: topAnchor),
columnsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
columnsStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
otherwise, the view will have 0x0 dimensions.
Here is a modified version of your class:
class CalendarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
// array to add in future in columnsStackView
var columnStacks: [UIStackView] = []
for columns in 1...12 {
// array to add in future in columnStackView
var columnViews: [UIView] = []
for cell in 1...7 {
let cellView = UIView()
cellView.backgroundColor = .orange
// we want each "cellView" to be 24x24 points
cellView.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
cellView.heightAnchor.constraint(equalTo: cellView.widthAnchor).isActive = true
columnViews.append(cellView)
}
// create columnStackView and add all 7 views
let columnStackView = UIStackView(arrangedSubviews: columnViews)
columnStackView.axis = .vertical
columnStackView.distribution = .fill
columnStackView.alignment = .fill
columnStackView.spacing = 4
columnStacks.append(columnStackView)
}
// create columnsStackView and add those 12 stacks
let columnsStackView = UIStackView(arrangedSubviews: columnStacks)
columnsStackView.axis = .horizontal
columnsStackView.distribution = .fill
columnsStackView.alignment = .fill
columnsStackView.spacing = 4
columnsStackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(columnsStackView)
// constrain the "outer" stack view to self
NSLayoutConstraint.activate([
columnsStackView.topAnchor.constraint(equalTo: topAnchor),
columnsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
columnsStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
}
and a simple test controller to show how it can be used:
class CalendarTestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let cv = CalendarView()
cv.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cv)
// the CalendarView will size itself, so we only need to
// provide x and y position constraints
NSLayoutConstraint.activate([
cv.centerXAnchor.constraint(equalTo: view.centerXAnchor),
cv.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
// let's give it a background color so we can see its frame
cv.backgroundColor = .systemYellow
}
}
the result:

How to dynamically resize text view inside a stack view

I'm trying to display a dynamically sized UITextView inside a stack view, but the text view is not adjusting to the size of the content.
First I have the arranged subview:
class InfoView: UIView {
private var title: String!
private var detail: String!
private var titleLabel: UILabel!
private var detailTextView: UITextView!
init(infoModel: InfoModel) {
self.title = infoModel.title
self.detail = infoModel.detail
super.init(frame: .zero)
configure()
setConstraint()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .rounded(ofSize: titleLabel.font.pointSize, weight: .bold)
titleLabel.textColor = .lightGray
titleLabel.sizeToFit()
titleLabel.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(titleLabel)
detailTextView = UITextView()
detailTextView.sizeToFit()
detailTextView.text = detail
detailTextView.font = UIFont.systemFont(ofSize: 19)
detailTextView.isEditable = false
detailTextView.textColor = .lightGray
detailTextView.isUserInteractionEnabled = false
detailTextView.isScrollEnabled = false
detailTextView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(detailTextView)
}
private func setConstraint() {
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: self.topAnchor),
titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5),
titleLabel.heightAnchor.constraint(equalToConstant: 40),
detailTextView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
detailTextView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
detailTextView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
detailTextView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
}
Then I implement the stack view in a view controller:
class MyViewController: UIViewController {
var infoModelArr: [InfoModel]!
var stackView: UIStackView!
var scrollView: UIScrollView!
init(infoModelArr: [InfoModel]) {
self.infoModelArr = infoModelArr
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
var infoViewArr = [InfoView]()
for infoModel in infoModelArr {
let infoView = InfoView(infoModel: infoModel)
infoViewArr.append(infoView)
}
stackView = UIStackView(arrangedSubviews: infoViewArr)
stackView.axis = .vertical
stackView.spacing = 10
stackView.distribution = .fillProportionally
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
stackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = stackView.bounds.size
}
}
Finally, I call the view controller as following:
let myVC = MyViewController(infoModelArr: [InfoModel(title: "title", detail: "detail"), InfoModel(title: "title", detail: "detail")])
self.present(myVC, animated: true, completion: nil)
Notably, if I were to instantiate the stack view with a single arranged subview, the height of the stack view seems to be dynamically adjusted, but as soon as 2 or more subviews are introduced, the height doesn't reflect the content.
When I attempted to set the intrinsic size of the InfoView,
override func layoutSubviews() {
super.layoutSubviews()
height = titleLabel.bounds.height + detailTextView.bounds.height
}
var height: CGFloat! = 200 {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
let originalSize = super.intrinsicContentSize
return CGSize(width: originalSize.width, height: height)
}
detailTextView.bounds.height returns 0.
The fillProportionally distribution tries to scale the heights of the arranged subviews according to their intrinsic content size, as a proportion of of the stack view's height. e.g. if the stack view has a height of 120, and arranged subview A has an intrinsic height of 10, and arranged subview B has an intrinsic height of 20, then A and B will have a height of 40 and 80 respectively in the stack view.
Your stack view doesn't have a defined height, so fillProportionally doesn't make much sense here.
Instead, a distribution of fill should do the job:
stackView.distribution = .fill
(as an experiment, you can try adding a height constraint to the stack view, and you'll see how fillProportionally works)

Is it possible to load UIStackView arrangedSubviews after init

I have a View which contains a UIStackView, this UIStackView arrangedSubviews is an array of UIButtons. What happens is that when I'm on ViewDidLoad of my ViewController, I set an array of UIButtons like this:
let buttons = [button1, button2, button3, button4]
The number of buttons can vary from 1 to 4.
On the View file I have a variable with didSet, on that didSet I set an array in the view to have the contents of the buttons array.
The problem is that the didSet happens after the View's init(), so when I setup the UIStackView, the array is an empty array.
Is there a way to make the view to set the UIStackView again after the didSet, so I will have the array I need?
This is my View with the didSet and the array:
class ProductDetailView: UIView {
var productViewModel: ProductDetailViewModel? {
didSet {
self.quantityButtons = self.productViewModel?.quantityOptions ?? [] <--- This is where I set the buttons array.
}
}
//MARK: - Properties
var quantityButtons: [UIButton] = [] //TODO: This should be a custom button.
}
This is my UIStackView:
private lazy var quantitiesStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: self.quantityButtons)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .horizontal
stack.distribution = .fillEqually
stack.spacing = 2.0
return stack
}()
If it is not possible, how should I approach this issue? how can I have the populated array when I need it?
EDIT: I also tried this approach:
var quantityButtons: [UIButton] {
return productViewModel?.quantityOptions ?? []
}
still got an empty array.
I'm still kinda confused by what exactly you're doing, but I think I know what you want to do. You can copy paste the code into a new Xcode project and after a few second you'll see the buttons appear.
class ProductDetailView: UIView {
// The StackView for the buttons should be in this view.
private lazy var quantitiesStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: self.quantityButtons)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .horizontal
stack.distribution = .fillEqually
stack.spacing = 2.0
return stack
}()
//MARK: - Properties
var quantityButtons: [UIButton] = [] {
didSet {
guard !quantityButtons.isEmpty else { return }
// Might need to modify this if you update the buttons more than once.
guard quantitiesStack.arrangedSubviews.isEmpty else { return }
for button in quantityButtons {
// Adding these subviews will cause the stackview to be redraw with these buttons.
quantitiesStack.addArrangedSubview(button)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
// layout - this view is basically just a stackview.
addSubview(quantitiesStack)
quantitiesStack.translatesAutoresizingMaskIntoConstraints = false
quantitiesStack.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
quantitiesStack.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
quantitiesStack.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
quantitiesStack.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
}
class ViewController: UIViewController {
let productDetailView = ProductDetailView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(productDetailView)
productDetailView.backgroundColor = .lightGray
productDetailView.translatesAutoresizingMaskIntoConstraints = false
productDetailView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
productDetailView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
productDetailView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = true
// Simulate delay loading data.
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: {
var buttons: [UIButton] = []
for i in 0..<4 {
let button = UIButton(type: .custom)
button.setTitle("Button \(i)", for: .normal)
button.titleLabel?.textColor = .black
buttons.append(button)
}
// This will trigger the buttons to be added to the stackView and then they will appear in the UI.
self.productDetailView.quantityButtons = buttons
})
}
}
Screenshot:

Embedd StackView in ScrollView that is embedded in a main StackView

Embedd StackView in ScrollView that is embedded in a main StackView
I am having trouble with a rather complicated detail view that I want to do programmatically. My view hierarchy looks something like this:
Since this might be better explained visualising, I have a screenshot here:
My problem is that I don't know how to set the height constraint on descriptionTextView – right now it's set to 400. What I want though is that it takes up all the space available as the middle item of the main stack view. Once one or more comments are added to the contentStackView, the text field should shrink.
I am not sure which constraints for which views I must set to achieve this...
Here's my take on it so far:
import UIKit
class DetailSampleViewController: UIViewController {
lazy var mainStackView: UIStackView = {
let m = UIStackView()
m.axis = .vertical
m.alignment = .fill
m.distribution = .fill
m.spacing = 10
m.translatesAutoresizingMaskIntoConstraints = false
m.addArrangedSubview(titleTextField)
m.addArrangedSubview(contentScrollView)
m.addArrangedSubview(footerStackView)
return m
}()
lazy var titleTextField: UITextField = {
let t = UITextField()
t.borderStyle = .roundedRect
t.placeholder = "Some Fancy Placeholder"
t.text = "Some Fancy Title"
t.translatesAutoresizingMaskIntoConstraints = false
return t
}()
lazy var contentScrollView: UIScrollView = {
let s = UIScrollView()
s.contentMode = .scaleToFill
s.keyboardDismissMode = .onDrag
s.translatesAutoresizingMaskIntoConstraints = false
s.addSubview(contentStackView)
return s
}()
lazy var contentStackView: UIStackView = {
let s = UIStackView()
s.translatesAutoresizingMaskIntoConstraints = false
s.axis = .vertical
s.alignment = .fill
s.distribution = .equalSpacing
s.spacing = 10
s.contentMode = .scaleToFill
s.addArrangedSubview(descriptionTextView)
s.addArrangedSubview(getCommentLabel(with: "Some fancy comment"))
s.addArrangedSubview(getCommentLabel(with: "Another fancy comment"))
s.addArrangedSubview(getCommentLabel(with: "And..."))
s.addArrangedSubview(getCommentLabel(with: "..even..."))
s.addArrangedSubview(getCommentLabel(with: "...more..."))
s.addArrangedSubview(getCommentLabel(with: "...comments..."))
s.addArrangedSubview(getCommentLabel(with: "Some fancy comment"))
s.addArrangedSubview(getCommentLabel(with: "Another fancy comment"))
s.addArrangedSubview(getCommentLabel(with: "And..."))
s.addArrangedSubview(getCommentLabel(with: "..even..."))
s.addArrangedSubview(getCommentLabel(with: "...more..."))
s.addArrangedSubview(getCommentLabel(with: "...comments..."))
return s
}()
lazy var descriptionTextView: UITextView = {
let tv = UITextView()
tv.font = UIFont.systemFont(ofSize: 17.0)
tv.clipsToBounds = true
tv.layer.cornerRadius = 5.0
tv.layer.borderWidth = 0.25
tv.translatesAutoresizingMaskIntoConstraints = false
tv.text = """
Some fancy textfield text,
spanning over multiple
lines
...
"""
return tv
}()
lazy var footerStackView: UIStackView = {
let f = UIStackView()
f.axis = .horizontal
f.alignment = .fill
f.distribution = .fillEqually
let commentLabel = UILabel()
commentLabel.text = "Comments"
let addCommentButton = UIButton(type: UIButton.ButtonType.system)
addCommentButton.setTitle("Add Comment", for: .normal)
f.addArrangedSubview(commentLabel)
f.addArrangedSubview(addCommentButton)
return f
}()
override func loadView() {
view = UIView()
view.backgroundColor = . systemBackground
navigationController?.isToolbarHidden = true
view.addSubview(mainStackView)
NSLayoutConstraint.activate([
mainStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 12),
mainStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -12),
mainStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12),
mainStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
titleTextField.heightAnchor.constraint(equalToConstant: titleTextField.intrinsicContentSize.height),
contentStackView.leadingAnchor.constraint(equalTo: contentScrollView.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: contentScrollView.trailingAnchor),
contentStackView.topAnchor.constraint(equalTo: contentScrollView.topAnchor),
contentStackView.bottomAnchor.constraint(equalTo: contentScrollView.bottomAnchor),
descriptionTextView.heightAnchor.constraint(equalToConstant: 400),
descriptionTextView.leadingAnchor.constraint(equalTo: mainStackView.leadingAnchor),
descriptionTextView.trailingAnchor.constraint(equalTo: mainStackView.trailingAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Detail View"
}
func getCommentLabel(with text: String) -> UILabel {
let l = UILabel()
l.layer.borderWidth = 0.25
l.translatesAutoresizingMaskIntoConstraints = false
l.text = text
return l
}
}
You're close, but a couple notes:
When using stack views - particularly inside scroll views - you sometimes need to explicitly define which elements can be stretched or not, and which elements can be compressed or not.
To get the scroll view filled before it has enough content, you need to set constraints so the combined content height is equal to the scroll view frame's height, but give that constraint a low priority so auto-layout can "break" it when you have enough vertical content.
A personal preference: I'm generally not a fan of adding subviews inside lazy var declarations. It can become confusing when trying to setup constraints.
I've re-worked your posted code to at least get close to what you're going for. It starts with NO comment labels... tapping the "Add Comment" button will add "numbered comment labels" and every third comment will wrap onto multiple lines.
Not really all that much in the way of changes... and I think I added enough comments to make things clear.
class DetailSampleViewController: UIViewController {
lazy var mainStackView: UIStackView = {
let m = UIStackView()
m.axis = .vertical
m.alignment = .fill
m.distribution = .fill
m.spacing = 10
m.translatesAutoresizingMaskIntoConstraints = false
// don't add subviews here
return m
}()
lazy var titleTextField: UITextField = {
let t = UITextField()
t.borderStyle = .roundedRect
t.placeholder = "Some Fancy Placeholder"
t.text = "Some Fancy Title"
t.translatesAutoresizingMaskIntoConstraints = false
return t
}()
lazy var contentScrollView: UIScrollView = {
let s = UIScrollView()
s.contentMode = .scaleToFill
s.keyboardDismissMode = .onDrag
s.translatesAutoresizingMaskIntoConstraints = false
// don't add subviews here
return s
}()
lazy var contentStackView: UIStackView = {
let s = UIStackView()
s.translatesAutoresizingMaskIntoConstraints = false
s.axis = .vertical
s.alignment = .fill
// distribution needs to be .fill (not .equalSpacing)
s.distribution = .fill
s.spacing = 10
s.contentMode = .scaleToFill
// don't add subviews here
return s
}()
lazy var descriptionTextView: UITextView = {
let tv = UITextView()
tv.font = UIFont.systemFont(ofSize: 17.0)
tv.clipsToBounds = true
tv.layer.cornerRadius = 5.0
tv.layer.borderWidth = 0.25
tv.translatesAutoresizingMaskIntoConstraints = false
tv.text = """
Some fancy textfield text,
spanning over multiple lines.
This textView now has a minimum height of 160-pts.
"""
return tv
}()
lazy var footerStackView: UIStackView = {
let f = UIStackView()
f.axis = .horizontal
f.alignment = .fill
f.distribution = .fillEqually
let commentLabel = UILabel()
commentLabel.text = "Comments"
let addCommentButton = UIButton(type: UIButton.ButtonType.system)
addCommentButton.setTitle("Add Comment", for: .normal)
// add a target so we can add comment labels
addCommentButton.addTarget(self, action: #selector(addCommentLabel(_:)), for: .touchUpInside)
// don't allow button height to be compressed
addCommentButton.setContentCompressionResistancePriority(.required, for: .vertical)
f.addArrangedSubview(commentLabel)
f.addArrangedSubview(addCommentButton)
return f
}()
// just for demo - numbers the added comment labels
var commentIndex: Int = 0
// do all this in viewDidLoad(), not in loadView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = . systemBackground
navigationController?.isToolbarHidden = true
title = "Detail View"
// add the mainStackView
view.addSubview(mainStackView)
// add elements to mainStackView
mainStackView.addArrangedSubview(titleTextField)
mainStackView.addArrangedSubview(contentScrollView)
mainStackView.addArrangedSubview(footerStackView)
// add contentStackView to contentScrollView
contentScrollView.addSubview(contentStackView)
// add descriptionTextView to contentStackView
contentStackView.addArrangedSubview(descriptionTextView)
// tell contentStackView to be the height of contentScrollView frame
let contentStackHeight = contentStackView.heightAnchor.constraint(equalTo: contentScrollView.frameLayoutGuide.heightAnchor)
// but give it a lower priority do it can grow as comment labels are added
contentStackHeight.priority = .defaultLow
NSLayoutConstraint.activate([
// constrain mainStackView top / bottom / leading / trailing to safe area
mainStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 12),
mainStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -12),
mainStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12),
mainStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
// title text field
titleTextField.heightAnchor.constraint(equalToConstant: titleTextField.intrinsicContentSize.height),
// minimum height for descriptionTextView
descriptionTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: 160.0),
// constrain contentStackView top / leading / trailing / bottom to contentScrollView
contentStackView.topAnchor.constraint(equalTo: contentScrollView.topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: contentScrollView.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: contentScrollView.trailingAnchor),
contentStackView.bottomAnchor.constraint(equalTo: contentScrollView.bottomAnchor),
// constrain contentStackView width to contentScrollView frame
contentStackView.widthAnchor.constraint(equalTo: contentScrollView.frameLayoutGuide.widthAnchor),
// activate contentStackHeight constraint
contentStackHeight,
])
// during dev, give some background colors so we can see the frames
contentScrollView.backgroundColor = .cyan
descriptionTextView.backgroundColor = .yellow
}
#objc func addCommentLabel(_ sender: Any?) -> Void {
// commentIndex is just used to number the added comments
commentIndex += 1
// let's make every third label end up with multiple lines, just to
// confirm variable-height labels won't mess things up
var s = "This is label \(commentIndex)"
if commentIndex % 3 == 0 {
s += ", and it has enough text that it should need to wrap onto multiple lines, even in landscape orientation."
}
let v = getCommentLabel(with: s)
// don't let comment labels stretch vertically
v.setContentHuggingPriority(.required, for: .vertical)
// don't let comment labels get compressed vertically
v.setContentCompressionResistancePriority(.required, for: .vertical)
contentStackView.addArrangedSubview(v)
// auto-scroll to bottom to show newly added comment label
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
let r = CGRect(x: 0.0, y: self.contentScrollView.contentSize.height - 1.0, width: 1.0, height: 1.0)
self.contentScrollView.scrollRectToVisible(r, animated: true)
}
}
func getCommentLabel(with text: String) -> UILabel {
let l = UILabel()
l.layer.borderWidth = 0.25
l.translatesAutoresizingMaskIntoConstraints = false
l.text = text
// allow wrapping / multi-line comments
l.numberOfLines = 0
return l
}
}

have a vertical stackview take all the spacing inside another stackview

I am trying to achieve the following layout in a table cell (it has a dynamic height):
The result I am getting is that 2nd stackview doesn't stretch to add more height to fill the multiline label.
lazy var stackView: StackView = {
let subviews: [UIView] = []
let view = StackView(arrangedSubviews: subviews)
view.axis = .horizontal
view.alignment = .center
self.containerView.addSubview(view)
view.snp.makeConstraints({ (make) in
make.edges.equalToSuperview().inset(inset)
})
return view
}()
lazy var detailsStackView: StackView = {
let subviews: [UIView] = []
let view = StackView(arrangedSubviews: subviews)
view.axis = .vertical
stackView.alignment = .fill
return view
}()
lazy var nameLabel: Label = {
let view = Label(style: .styleBoldSize24)
return view
}()
let detailsLabel: Label = {
let label = Label(style: .styleRegular25)
label.textAlignment = .left
label.numberOfLines = 10
label.text = "Where does it come from Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of anything embarrassing hidden in the mid words etc."
return label
}()
lazy var avatarImageView: ImageView = {
let view = ImageView(frame: CGRect())
view.snp.makeConstraints({ (make) in
make.width.height.equalTo(100)
})
return view
}()
override func setup() {
super.setup()
stackView.axis = .horizontal
stackView.alignment = .top
stackView.addArrangedSubview(avatarImageView)
detailsStackView.spacing = self.inset
detailsStackView.addArrangedSubview(nameLabel)
detailsStackView.addArrangedSubview(detailsLabel)
stackView.addArrangedSubview(avatarImageView)
stackView.addArrangedSubview(detailsStackView)
stackView.snp.remakeConstraints({ (make) in
let inset = self.inset
make.edges.equalToSuperview().inset(UIEdgeInsets(top: inset/2, left: inset, bottom: inset/2, right: inset))
})}

Resources