I am trying to create a grid of 3xInfinity items in Swift with same size items. The items are square buttons that can be aligned up to 3 items wide and infinite on the Y axis. I have created a function that takes a UIStackView with axis vertical and every 3 items I create a new UIStackView with horizontal axis inside of the other one. This works well but only when all the buttons are multiple of 3. Whenever they are not the row makes the buttons fill all the available space.
I would like to know how to create a grid of 3xInfinity items in Swift so that the buttons are always evenly spaced, even if there is an odd number of buttons.
let stackview = UIStackView()
contentView.addSubview(stackview)
stackview.axis = .vertical
stackview.alignment = .fill
stackview.distribution = .fill
stackview.spacing = 10
for _ in 0...feedButtonRows {
let hstack = UIStackView()
hstack.axis = .horizontal
hstack.alignment = .center
hstack.distribution = .fillEqually
hstack.spacing = 16
hstack.translatesAutoresizingMaskIntoConstraints = false
hstack.widthAnchor.constraint(equalToConstant: stackview.frame.size.width).isActive = true
for _ in 0..<3 {
if(count == buttons.count){
break
}
let button = FoodButtonComponent(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
hstack.addArrangedSubview(button)
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.heightAnchor.constraint(equalToConstant: 100).isActive = true
button.setup(with: buttons[count])
count+=1
}
stackview.addArrangedSubview(hstack)
}
If you set your vertical stack view's .alignment = .leading
and do not give the horizontal "row" stack views width constraints, the buttons will be left-aligned and won't get stretched.
You can also simplify your code a bit like this:
var i: Int = 0
while i < numButtons {
let hstack = UIStackView()
hstack.axis = .horizontal
hstack.spacing = 16
for _ in 0..<3 {
if i < numButtons {
let button = FoodButtonComponent()
button.setTitle("\(i)", for: [])
button.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
button.heightAnchor.constraint(equalTo: button.widthAnchor).isActive = true
hstack.addArrangedSubview(button)
}
i += 1
}
stackview.addArrangedSubview(hstack)
}
Note that when views (labels, buttons, etc) are added as arrangedSubviews
of a stack view, UIKit automatically sets .translatesAutoresizingMaskIntoConstraints = false
-- so no need to explicitly set that.
Here's a complete example - change the numButtons
at the top to see the layouts:
class StacksVC: UIViewController {
let numButtons: Int = 7
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let stackview = UIStackView()
stackview.axis = .vertical
// this will keep the 1- & 2-button rows left-aligned
stackview.alignment = .leading
stackview.distribution = .fill
stackview.spacing = 10
var i: Int = 0
while i < numButtons {
let hstack = UIStackView()
hstack.axis = .horizontal
hstack.spacing = 16
for _ in 0..<3 {
if i < numButtons {
let button = FoodButtonComponent()
button.setTitle("\(i)", for: [])
button.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
button.heightAnchor.constraint(equalTo: button.widthAnchor).isActive = true
hstack.addArrangedSubview(button)
}
i += 1
}
stackview.addArrangedSubview(hstack)
}
stackview.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackview)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stackview.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
stackview.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// set stackview width to (3 x 100) + (2 x 16)
// so if we have only 1 or 2 buttons, they will be "left-aligned"
stackview.widthAnchor.constraint(equalToConstant: 332.0),
])
// let's set the vertical stackview background to light gray
// if we want to see the framing
//stackview.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
}
}
When run: