Custom distribution for UIStackView - ios

I have the following UIStackView.
Here is the pseude-code:
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fill
Blue, Yellow, Violet and Green views have their correct sizes (autosized labels or buttons). However, red one is being used to fill the remaining space.
I want that Violet one to fill the empty space instead of Red. Is there any way to specify which view should fill the empty space?

You'll need to weight each view using contentHuggingPriority.
Try making the red view's contentHuggingPriority higher than the violet.

Related

Swift, how to add X number of views with constraints dynamically

I am developing a swift application which will fit 10 items on the screen. If I wanted to do this on a screen that would not change size i.e. the user doesn't change orientation or an iPad user does not use split screen, I would be able to detect the width by doing let size = bounds.width/19.
The problem is as the screen size is dynamic so therefor I need to do it with constraints. I would not like to use UICollectionView as that is too heavy and would also not like to use UIStackView if possible as I don't think it supports aspect ratio which I need for circles. I am trying to use UIViews.
Edit:
This is how I want them to look. This will be about 50 high and other information will be underneath.
UIStackView is the right tool for this job. In the future, I recommend more rigorously defining what you want to happen first, then dive into the documentation.
let sv = UIStackView()
sv.spacing = 10
// this means each arranged subview will take up the same amount of space
sv.distribution = .fillEqually
for _ in 0..<50 {
// omitting the rounded corners or any other styling because
// it's not the point of this question
let subview = UIView()
// The stack view will determine the width based on the screen size
// we just need to say that height == width
subview.widthAnchor.constraint(equalTo: subview.heightAnchor).isActive = true
sv.addArrangedSubview(subview)
}
// add your stackview to your view hierarchy and constrain it

Force a view to take as much space as possible inside UIStackView

I'm creating part of my application's UI with Swift and the problem I'm facing is I have a UIStackView with 3 sub views: 2 UILabels and an UIImageView. Here is my first code
let switchview = UISwitch()
let nodelableview = UILabel()
nodelableview.textAlignment = NSTextAlignment.right
nodelableview.numberOfLines = 0
nodelableview.text = nodes[i].type + " " + nodes[i].node_name
let statLabel = UILabel()
statLabel.textAlignment = NSTextAlignment.left
statLabel.text = nodes[i].stat
let stack = UIStackView()
stack.axis = .horizontal
stack.spacing = 16
stack.addArrangedSubview(statLabel)
stack.addArrangedSubview(nodelableview)
stack.addArrangedSubview(switchview)
cell.nodesView.addArrangedSubview(stack)
the problem with this code is that when the nodelabelview has long text the UIStackView not extending to make space for 2 or more lines. So I set the alignment to .center and here is the result
There is empty space left but the first UILabel is using it for nothing. How can I force the second UILabel to use available spaces?
A setup that would give priority to your second label (the one with unlimited number of lines), would be a stackview set to "Fill Proportionally" distribution (which means that views are sized based on their intrinsic size & hugging/resistance priorities)
combined with a horizontal "Content Compression Resistance Priority" of 1000 ('required') for the left label & the switch (which means 'do not compress')
which is resolved to this:
You may need to set the horizontal contentHuggingPriority and contentCompressionResistance for each label / switch to something different from the others, ensuring that the one you wish to expand to fill remaining available space has the lowest hugging value.

Stack view - but with "proportional" gaps

Imagine a stack view with four items, filling something. (Say, filling the screen).
Notice there are three gaps, ABC.
(Note - the yellow blocks are always some fixed height each.)
(Only the gaps change, depending on the overall height available to the stack view.)
Say UISV is able to draw everything, with say 300 left over. The three gaps will be 100 each.
In the example, 9 is left over, so A B and C are 3 each.
However.
Very often, you want the gaps themselves to enjoy a proportional relationship.
Thus - your designer may say something like
If the screen is too tall, expand the spaces at A, B and C. However. Always expand B let's say 4x as fast as the gaps at A and B."
So, if "12" is left over, that would be 2,8,2. Whereas when 18 is left over, that would be 3,12,3.
Is this concept available in stack view? Else, how would you do it?
(Note that recently added to stack view, you can indeed specify the gaps individually. So, it would be possible to do it "manually", but it would be a real mess, you'd be working against the solver a lot.)
You can achieve that by following workaround. Instead of spacing, for each space add a new UIView() that would be a stretchable space. And then just add constraints between heights of these "spaces" that would constrain their heights together based on the multipliers you want, so e.g.:
space1.heightAnchor.constraint(equalTo: space2.heightAnchor, multiplier: 2).isActive = true
And to make it work I think you'd have to add one constraint that would try to stretch those spaces in case there is free space:
let stretchingConstraint = space1.heightAnchor.constraint(equalToConstant: 1000)
// lowest priority to make sure it wont override any of the rest of constraints and compression resistances
stretchingConstraint.priority = UILayoutPriority(rawValue: 1)
stretchingConstraint.isActive = true
The "normal" content views would have to have intrinsic size or explicit constraints setting their heights to work properly.
Here is an example:
class MyViewController: UIViewController {
fileprivate let stack = UIStackView()
fileprivate let views = [UIView(), UIView(), UIView(), UIView()]
fileprivate let spaces = [UIView(), UIView(), UIView()]
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(stack)
// let stack fill the whole view
stack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: self.view.topAnchor),
stack.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
stack.leftAnchor.constraint(equalTo: self.view.leftAnchor),
stack.rightAnchor.constraint(equalTo: self.view.rightAnchor),
])
stack.alignment = .fill
// distribution must be .fill
stack.distribution = .fill
stack.spacing = 0
stack.axis = .vertical
for (index, view) in views.enumerated() {
stack.addArrangedSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
// give it explicit height (or use intrinsic height)
view.heightAnchor.constraint(equalToConstant: 50).isActive = true
view.backgroundColor = .orange
// intertwin it with spaces
if index < spaces.count {
stack.addArrangedSubview(spaces[index])
spaces[index].translatesAutoresizingMaskIntoConstraints = false
}
}
// constraints for 1 4 1 proportions
NSLayoutConstraint.activate([
spaces[1].heightAnchor.constraint(equalTo: spaces[0].heightAnchor, multiplier: 4),
spaces[2].heightAnchor.constraint(equalTo: spaces[0].heightAnchor, multiplier: 1),
])
let stretchConstraint = spaces[0].heightAnchor.constraint(equalToConstant: 1000)
stretchConstraint.priority = UILayoutPriority(rawValue: 1)
stretchConstraint.isActive = true
}
}
Remarkably, #MilanNosáľ 's solution works perfectly.
You do not need to set any priorities/etc - it works perfectly "naturally" in the iOS solver!
Set the four content areas simply to 50 fixed height. (Use any intrinsic content items.)
Simply don't set the height at all of "gap1".
Set gap2 and gap3 to be equal height of gap1.
Simply - set the ratios you want for gap2 and gap3 !
Versus gap1.
So, gap2 is 0.512 the height of gap1, gap3 is 0.398 the height of gap1, etc.
It does solve it in all cases.
Fantastic!!!!!!!!!!
So: in the three examples (being phones with three different screen heights). In fact the relative heights of the gaps, is always the same. Your design department will rejoice! :)
Created: a gist with a storyboard example
The key here is Equal Heights between your arranged views and your reference view:
And then change the 'Multiplier` to your desired sizes:
In this example I have 0.2 for the main view sizes (dark grey), 0.05 within the pairs (black), and 0.1 between the pairs (light grey)
Then simply changing the size of the containing view will cause the views to re-size proportionally:
This is entirely within the storyboard, but you could do the same thing in code.
Note that I'm using only proportions within the StackView to avoid having an incorrect total size, (and making sure they add up to 1.0), but it should be possible to also have some set heights within the StackView if done correctly.

UIStackView; Equal Spacing Between & Outside Elements

UIStackView is awesome, I love Equal Spacing Distribution.
But how to achieve the same space also outside of elements dynamically?
In my case all elements will have same ratio 1:1
You can add equal spacing using the story board as shown here:
Source: https://stackoverflow.com/a/32862693/3393964
#Declan has the right idea. Here's that answer programatically where you add extra views on either side so the stack view gives correct outside spacing with any number of buttons.
stackView.alignment = .center
stackView.axis = .horizontal
stackView.distribution = .equalCentering
// Then when I add the views...
let leftView = UIView()
stackView.addArrangedSubview(leftView)
content.buttons.forEach { (button) in
stackView.addArrangedSubview(button)
}
let rightView = UIView()
stackView.addArrangedSubview(rightView)
Here's what my view looks like with 2 items using equalSpacing
And here it is with equalCentering distribution, also a nice look.
I prefer to let the UIStackView handle the spacing. Create a UIStackView with equal spacing and add two 0px wide (0px high if using a vertical stackview) transparent views to the the far sides of your stack view.
You can use constraints and give then same height and width. So when you change the dimension of anyone of the component then all components are changed with same dimension.
I think what you want is to have the same spacing outside of the stack view with the spacing outside.
What I would do is the put stack view inside another view (GRAY VIEW) and set the leading and trailing constraint of the stack view to be equal to the spacing of the stack view.
Spacing of the Stack View
Constraints of the Stack View from its super view (Gray View)

UIStackView Spacing - Don't push to top and bottom anchors

I am trying to distribute some nested stack views and I think I'm missing a property to help me align the various views the way I want them.
Here's the current output:
The issue with this is that the two arranged subviews that are added to each column (a stackview), are distributed so that the first subview aligns to the top, and the second subview aligns to the bottom (leaving a variable space in between them).
But here's what I am hoping for - always a fixed space (say 10 px) in between the first and second arranged subview in each column, and the extra space below the second arranged subview is just left to be what it needs to be.
The view is arranged as follows:
outerStackView = the green view: (20px off top, 64px off left, 20px off bottom,
64px off right - present in both screenshots but only highlighted on the top one) with properties:
outerStackView.axis = .Horizontal
outerStackView.distribution = .FillEqually
outerStackView.spacing = 10
leftStackView, middleStackView, rightStackView added to the outerStackView each with properties:
columnStackView.axis = .Vertical
columnStackView.distribution = .Fill
columnStackView.alignment = UIStackViewAlignment.Top
columnStackView.spacing = 10
Then each column has 2 stackViews inside of it, represented by the darker gray box directly around the red and blue boxes. With properties:
redBlueStackView.axis = .Horizontal
redBlueStackView.distribution = .FillProportionally
redBlueStackView.alignment = UIStackViewAlignment.Top
redBlueStackView.spacing = 4
You should pin your stack view to you containing view. That will give you a more consistent look. You can also best all three of your stack view in a horizontal stack view them selves if you want all 3 to be equal sizing. Another tip is to mess with the content hugging priority and compression resistance variables. Hope that helps let me know if you have more questions.

Resources