View resized even after setting contentHuggingPriority to defaultHigh - ios

I have a vertical stackView.
bodyStackView.translatesAutoresizingMaskIntoConstraints = false
bodyStackView.axis = .vertical
bodyStackView.alignment = .fill
bodyStackView.distribution = .fill
bodyStackView.spacing = 8.0
bodyStackView.backgroundColor = .yellow
and two/three views get added to it.
bodyStackView.addArrangedSubview(titleStackView)
bodyStackView.addArrangedSubview(priceStackView)
bodyStackView.addArrangedSubview(subscriptionInEligibleLabel)
titleStackView.setContentHuggingPriority(.defaultHigh, for: .vertical)
priceStackView.setContentHuggingPriority(.defaultLow, for: .vertical)
I want the last or the bottom-most view to stretch.
I have set the "ContentHuggingPriority", and yet the first or the top-most view is getting stretched.
How do we control this?

It's not clear why you have 3 stack views in your bodyStackView, but assuming you have a valid reason...
You need to set the Content Hugging Priority on the labels, not on the stack views.

Related

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.

Swift UIStackView utilize full width of UIStackView

In above screen, you can see I am using a UIStackView to fill radio buttons vertically. problem is my radio buttons not utilising the full width of UIStackView when I use stackV.alignment = .leading it shows label as "dis..lified" instead of disqualified.
UISTackView Code
let ratingStackView : UIStackView = {
let stackV = UIStackView()
stackV.translatesAutoresizingMaskIntoConstraints = false
stackV.backgroundColor = UIColor.yellow
stackV.axis = .vertical
stackV.distribution = .fillEqually
stackV.alignment = .leading
return stackV
}()
Layout of UIStackView
func setupView(){
view.addSubview(ratingStackView)
ratingStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
ratingStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,constant: 8).isActive = true
ratingStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
ratingStackView.heightAnchor.constraint(equalToConstant: 200).isActive = true
//Add radio buttons to stackview
for ratingButton in ratingRadioButtons{
ratingStackView.addArrangedSubview(ratingButton)
}
}
what property I need to set to utilize full width can you please tell I am new to the Swift for radio buttons. I am using DLRadioButton.
To get this working, you need to make following changes in the layout:
1. Set UIStackView's alignment property to fill, i.e.
stackV.alignment = .fill
2. Set UIButton's Horizontal Alignment to left wherever you are creating the RadioButton either in .xib file or through code.
In .xib, you can find the property in interface here:
if you are creating the button using code, use the following line of code:
ratingButton.contentHorizontalAlignment = .left
Let me know if you still face the issue. Happy coding..🙂
Leave alignment with its default value, i.e. .fill – this stretches arranged views in a direction perpendicular to the stack’s axis.
Actually, I suspect that if you are using .leading alignment and do not specify widths of nested controls you are getting auto layout warnings during runtime (could be checked in Visual Debugger in Xcode).
Try proportional distribution.
One more thing to try...Reduce the content hugging priority of the labels.

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.

Stack View, Collapse Space to Content

I have a vertical StackView with three nested Labels
Stack View
PlusMinus (Label)
Currency (Label)
Amount (Label)
The content in the labels are dynamic. I want the stack view to have the 3 items to be next to each other with very minimal spacing in between each other. Such that an example values would read something like "+ $ 45.67", but it seems that all Distribution options under Stack View do not accomplish this. The individual font and color settings under each label are different, so I can not simply use 1 label for these.
Is there anyway to say that each column in a vertical stack view must only take up how much space is taken up by a nested Label.
You need to set the content hugging and the text alignment of the labels appropriately.
So if the whole thing is to be right aligned you do this:
self.signLabel.setContentHuggingPriority(.init(rawValue: 300), for: .horizontal)
self.currencyLabel.setContentHuggingPriority(.init(rawValue: 400), for: .horizontal)
self.valueLabel.setContentHuggingPriority(.init(rawValue: 500), for: .horizontal)
self.signLabel.textAlignment = .right
self.currencyLabel.textAlignment = .right
self.valueLabel.textAlignment = .right
and if it is to be left aligned you do this:
self.signLabel.setContentHuggingPriority(.init(rawValue: 500), for: .horizontal)
self.currencyLabel.setContentHuggingPriority(.init(rawValue: 400), for: .horizontal)
self.valueLabel.setContentHuggingPriority(.init(rawValue: 300), for: .horizontal)
self.signLabel.textAlignment = .left
self.currencyLabel.textAlignment = .left
self.valueLabel.textAlignment = .left
(you can vary the numbers as long as they stay in the increasing order).
What this does is to give the layout system clues as to which of the UILabels should compress down to hug it's content first.
This should work with a UIStackView set to horizontal and using the default settings.

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)

Resources