Reputation: 537
I'm trying to fill an unknown, but calculated number of stackViews, filled with an unknown but calculated number of UIImageViews, equally, with equal spacing.
I have a main, vertical stackView that has a height and width constraint of 75. Alignment: Center; Distribution: Fill Equally.
For Each subStackView:
newStack.axis = .horizontal
newStack.alignment = .center
newStack.distribution = .fillEqually
newStack.spacing = 1
newStack.sizeToFit()
mainStack.addArrangedSubview(newStack)
I then have a different loop that goes through and adds the correct number of imageViews into each subStackView:
let image = UIImageView()
image.contentMode = .scaleAspectFit
image.image = UIImage(systemName: R.Image.System.circle)
subStackView.addArrangedSubview(image)
Below, is a screenshot of the result. It appears that all the imageViews are the same size, but for some reason they appear to be spaced out equally rather than have a spacing of 1.
The bottom 3 images should all be pentagons, but as you can see, the more items that are added, the more distorted the shapes become.
Upvotes: 0
Views: 1261
Reputation: 77477
This is a little tricky...
We need to allow the "dot rows" (horizontal stack views) to center themselves, rather than stretching the width of the main stack view.
We also need to actually calculate the dot widths instead of using .fillEqually
so we don't end up with parital-point sizes.
For example:
Available width is stack view width minus number of "spaces" x spacing:
75 - 2 = 73
73 / 3 = 24.333333...
On a @2x
scale device, the actual widths of the 3 views will be:
24.5 : 24 : 24.5
Not very noticeable with just 3 "dots" but it becomes very noticeable when we get to a 1, 3, 5, 7, 9, 8, 7, 6, 5
pattern.
.fillEqually
is on the left, calculated whole-number point sizes on the right:
Here's some example code:
class DotsViewController: UIViewController {
let patterns: [[Int]] = [
[3, 3, 3],
[3],
[1, 2, 3],
[1, 3, 5, 4, 3],
[1, 3, 5, 7, 9, 8, 7, 6, 5],
]
let mainStack: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.alignment = .fill
v.distribution = .equalSpacing
return v
}()
// space between dots
let dotSpacing: CGFloat = 1
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mainStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain main stack view 20-pts from top / leading / bottom
mainStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
mainStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
mainStack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
// width: 75
mainStack.widthAnchor.constraint(equalToConstant: 75.0),
])
patterns.forEach { a in
// create a vertical stack view
// to hold the "rows" of horizontal stack views (containing the dots)
let vBlockStack = UIStackView()
vBlockStack.axis = .vertical
vBlockStack.alignment = .center
vBlockStack.distribution = .fill
vBlockStack.spacing = dotSpacing
// add it to the main stack view
mainStack.addArrangedSubview(vBlockStack)
// calculate dot size
// needs to be a whole number so we don't get
// half-point sizes
let maxDots:CGFloat = CGFloat(a.max()!)
let availableWidth:CGFloat = 75.0 - ((maxDots - 1) * dotSpacing)
let w:CGFloat = floor(availableWidth / maxDots)
a.forEach { numDots in
// create a horizontal stack view
let hDotStack = UIStackView()
hDotStack.axis = .horizontal
hDotStack.alignment = .fill
hDotStack.distribution = .fill
hDotStack.spacing = dotSpacing
// add it to the vertical block stack view
vBlockStack.addArrangedSubview(hDotStack)
for _ in 0..<numDots {
let v = UIImageView()
v.contentMode = .scaleAspectFit
v.image = UIImage(systemName: "circle.fill")
v.tintColor = .red
// add view to dot stack view
hDotStack.addArrangedSubview(v)
// set dots image view size constraints
v.widthAnchor.constraint(equalToConstant: w).isActive = true
v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
}
}
}
}
}
That produces this output:
Upvotes: 2