How to set weight in UIStackView in IOS - ios

UIStackView is similar to Android LinearLayout but I could not figure out how to set weight for the subviews.
Suppose I have a vertical UIStackView and 3 UIImageViews in it. I want to set weights 3, 6, 1 consecutively for the UIImageViews. How do I do that?

UIStackView doesn't have the same concept of weights. It can use a subview's intrinsicContentSize as a weight, but setting a specific intrinsicContentSize typically requires making a subclass and it's used in other situations too (unlike the android:layout_weight attribute you're familiar with, which is only used by LinearLayout).
But since UIStackView works by applying constraints to its arranged subviews, you can get the effect of weights by setting additional constraints between the heights of the subviews. (UIStackView is designed to let you add your own constraints to tweak the layout this way.)
In your case, you want to constrain the height of the top view to be 3 times the height of the bottom view, and you want to constrain the height of the middle view to be 6 times the height of the bottom view.
You can set up a proportional-height constraint in a storyboard by creating an equal-height constraint, then editing the constraint's multiplier.
In code, you could do it like this (on iOS 9.0 or later):
NSLayoutConstraint.activateConstraints([
top.heightAnchor.constraintEqualToAnchor(bottom.heightAnchor, multiplier: 3),
middle.heightAnchor.constraintEqualToAnchor(bottom.heightAnchor, multiplier: 6),
])

Intrinsic content size, etc, is totally uninvolved.
To set fractional heights, just set fractional heights:
Fix the height of the stack view (say, the whole screen)
Put in the three views A, B, C
For A, make an height constraint 0.3 of the height of the stack view
For B, make an height constraint 0.6 of the height to the stack view
Set the stack view to distribution:Fill.
If you run this on a powerful iPhone, it will figure out that C is "0.1".
You're done.

First off, do you really need a stack view for this? It would be much easier to arrange this simply using proportional height constraints directly.
However, you can do this with a stack view if you really want to use a stack view. The secret is that the "weight" in question is simply the arranged view's intrinsicContentSize().height. Knowing this, I was easily able to set up a stack view consisting of three image views in the proportions you request:
Those, for purposes of the demonstration, are the same image repeated three times: one at 3x height, one at 6x height, and one at 1x height.
How did I do it? I gave the three image views tag values of 300, 600, and 100 respectively in the storyboard. (Of course I could have used an IBInspectable custom property for this, and in real life, I would do so.) Then I made them all instances of my UIImageView subclass, MyImageView, whose code looks like this:
class MyImageView: UIImageView {
override func intrinsicContentSize() -> CGSize {
print(self.tag)
return CGSizeMake(CGFloat(self.tag), CGFloat(self.tag))
}
}
The stack view's Distribution is configured as Fill Proportionally. The image views are configured with their Content Mode as Scale To Fill. Result: The stack view, in laying out its arranged views, consults the intrinsicContentSize method, and thus does the right thing.

Related

Dynamically sized stackView for less content

I have custom XIB's which I will be using to create my ViewController at runtime. Which all XIB's are to be included inside the ViewController will be decided at runtime.
I am using a stackView for that purpose which I have added to the storyboard and constrained it to leading-triling-top-bottom of the superView. That means the stackView will be stretched to the height of the viewController.
Now when i have few Custom Views(or XIB), say 3, it should not cover the complete screen but rather stick to it's size and be added to the stackView
But my problem arises here: Since I have constrained the stackView to complete screen(as sometimes the number of XIB can be a lot more and will surpass the size of the screen and hence I might need a scrollable stackView), it shows like this
I have found kind of a hack here but I don't think that's gonna cut
StackView's Alignment - Fill, Distribution - Equal Spacing (0)
step 1. make the bottom constraint between super view and your stack view as Equal or more
step 2.
make the height constraint to your stack view with priority 1 and constant 0
step 3.
ensure you views inside stack view layouts correctly - all edges should be installed in each views
I solved it by :
Removing the bottom constraint of the stackView
Changing the Intrinsic size under Size Inspector from System Defined to Placeholder and setting the height to 1

Hide arrangedSubviews in UIStackView instead of clipping contents

I have a UIStackView with three labels whose height is determined using Dynamic Type and text that have can wildly varying lengths. The container for the stack view has a fixed width and height depending on device screen size (small on iPhone SE, for example.) I want to center the stack view within the container (with some outer margins.)
The problem is that depending on the font size and container height, some of the labels in the stack view will be clipped. Here is an example with the third label:
I have experimented with layout constraint priorities for both the stack view and the labels, but this doesn't appear to be the right approach. Instead setting the visibility of the labels works better: correct spacing between elements is maintained.
My question is then what is the right time to detect that the label's height isn't fully displayed and to hide it.
The label height is close to, but not exactly equal to the UIFont's lineHeight so there's some rounding involved that makes this a little difficult.
The biggest problem is that after a layout pass in the UIStackView's layoutSubviews the heights of the arranged subviews can be detected, but you can't hide the arranged views at that point because it causes another layout pass and recursion.
So what am I missing? :-)
Here's a test project - build for iPhone Xs in the Simulator and you'll see the same results in the screenshot above.
Solution
Tom Irving's gist below pointed me in the right direction. The trick is to enumerate the subviews after a layout pass and remove them if height requirements aren't met.
The updated project shows how to do this in DebugStackView's layoutSubviews. And yes, UIStackView is a worthy adversary.
Could you act on viewDidLoad?
My intuition would be to add up the height of all visible subviews in the stack view and then hide the last if there's a problem.
In the sample you've provided I would recommend getting a CGSize with [self.firstLabel textRectForBounds:self.view.bounds limitedToNumberOfLines:0] for each visible label, making sure to take the margin between items into acount, and determining if the total height is greater than the constant height you've assigned to the stack view. If so, hide the elements that go beyond the stack view's height.
Of course, there might be more to the problem than I understand, but that would allow you to calculate before the layoutSubview pass happens.

Make Vertical Space Constraint Constant equal to percentage of screen height

I want the Vertical Spacing Constraint Constant value between two of my subviews to be a percentage of the screen height. This way the design looks about the same on all devices.
How do I do that in storyboard? Any well known tricks?
Perhaps there is some trick to achieve this with content hugging priority, compression resistance, or a second constraint of a lesser priority?
The solution I had in mind is to introduce a third subview to sit in between my two subviews - and set an aspect height constraint on that third subview with the superview. The two subviews would have a space constraint with the third subview with a constant of zero.
However - I don't like having a storyboard cluttered with make-belief invisible subviews.
create an IBOutlet of your vertical space constraints.then check what is the device using screen size then assign value to your constraint's constant. EX:
if(screenSize.height == 480)
{
self.verticalspaceConstraints.constant = 100 ;//this is an example
}
//like this add your other conditions
I have created a small video tutorial to get the basic idea try this
small video tutorial
hope this will help to you.
I have 2 solutions but am using the first one.
1) Put a UIView with the desired gap height between the two elements and give the gap view as proportional height to the main view of controller. This way it will increase according to device but this idea may feels complex. Because to design a complex screen you will have to put lots of gap view and it will be messy.
2) Mark the constraint in design time to be dynamic by assigning some character to the identifier property of the constraint. Create a category class of NSLayoutConstraint and in the class inherit its function and check if the identifier is started with that character and modify the value like multiplying with device scaling.
To have a adapting vertical spacing between two views, you can :
Use UIStackview and define spacing dynamically
Use Vertical Spacing Constraint between the two and change priority to 250. You need to have top constraint on the first and bottom on the second.
Use Equal Height relation between each subviews and superview. You set "proportional height" with like 0.4 on each, which let you 20% margin between. To do so, CTRL + clic on subviews then draw line to superview.
Then adjust "multiplier" making sure that the first element is the subview.

How to make a ScrollView with AutoLayout in Xcode5

In xcode 5 using storyboards how would one make a fully operational vertical scrolling scrollview, with AutoLayout ON?
Considering the subviews have hierarchy:
1.UIView
2.UIScrollView
3.UIView (lets call this UIDetailView to make things easier)
Please be specific from code to constraints to wether any of the views HAS to be smaller etc.
UIScrollView with Autolayout within Storyboards Just Works
I've seen a number of people recommending the 'Container View' approach, AKA brute force, to solving the problem that they don't understand. It is non-optimal since you now have lost a big advantage of the scrollview by making it think the content is the entire scrollview rather than the subviews immediately attached to the scrollview.
Here is how I did it in the example that follows
--UIScrollView
|-> UITextView
|-> UILabel
|-> UIOtherStuff
When placing a UIScrollView into a UIView in a Storyboard just pin the edges to the 4 sides of the UIScrollView to the UIView. Now add your content to the UIScrollView making sure that you provide a minimum of two constraints for each dimension. The great thing about Autolayout is that it figures out how big the contentSize of the scrollview, or UILabels for that matter, needs to be based upon the size of the content inside it. AKA intrinsicContentSize. So if you are given a warning 'Ambiguous content size for scrollView' you know that you have not given the content enough constraints. For example, you might have given Top, Bottom, Left, Right spacing distance between views but the subview you're constraining needs a height too since an infinite vertical plane like this UIScrollView could assume your view was from zero to infinitely high.
To put it another way the Apple guide on Autolayout by Example makes a simple 3 point plan for success:
Create the scroll view.
Place the UI element inside it.
Create constraints that fully define the width and height of the scroll view content.
That top TextView with 'Min melding til' is also growing as you type more lines into it and the whole ScrollView grows to contain it. While I override the UITextView class to return a modified height constraint, the ScrollView itself works correctly without coding.
One last thing, lots of posts related to Autolayout try the magical fix-all incantation translatesAutoresizingMaskIntoConstraints = NO. This is only necessary if the view is created programmatically.
This blog post details how to use a UIScrollView with Autolayout ON, using a pure autolayout approach. Note though that all constraints in the blog post are defined through the Storyboard.
The approach in the post assumes the following hierarchy:
1. View (main view of my UIViewController)
2. Scroll View (UIScrollView)
3. Container View (UIView)
4. Content View (e.g. UIImageView)
I guess the Container View will be your UIDetailView, and the Content View will be any UIView inside your UIDetailView.
https://happyteamlabs.com/blog/ios-how-to-use-uiscrollview-with-auto-layout-pure-auto-layout/
The documentation clearly states how to do this:
A UIScrollView in auto-layout will always resize itself to fit the content (UIDetailView).
So you have to set up your views like this:
UIView: Position with constraints.
UIScrollView: Bind to UIView with constraints.
UIDetailView: Set size (intrinsic content size), max out compression-resistance, set top-, bottom-, leading- and trailing constraints to UIScrollView to 0 manually.
I had a similar problem and i found relative simple solution similar to DJ S's from within Interface Builder using pure Autolayout without any code.
For proof-of-concept at first remove any constraint in View Controller to if see this works.
This is sample layout:
View (main view of my UIViewController)
Scroll View (UIScrollView)
Container View (UIView)
Content View (e.g. UIImageView)
A. Scroll View width/height should be smaller that Container View width/height
B. Container View should have some determinated width/height (may be explicit width/height )
C. Do Control-drag Container View to Scroll View and add only:
Leading Space to Container
Trailing Space to Container
D. Check out those two constraints and set "constant" value for both to 0
E. Run app and
Because of the new iPhone 6 and 6+ screen sizes, I had to make a few tweaks to DJ S's solution.
The goal
Position a UITextView inside a UIScrollView, and also have 15 pt spaces from the left/right screen edges.
Views
1. Main View (main view of my UIViewController)
2. Scroll View (UIScrollView)
3. Container View (UIView)
4. Text View (UITextView)
Solution
For the spaces, I added 15 pt horizontal trailing/leading spaces from UIScrollView->Main View. To make the UITextView's width relative to the screen width, I added an Equal Widths constraint from UITextView -> Main View and set the value to -30 (2 * the 15 pt horizontal space). Now, the UITextView's width will dynamically adjust for any screen size.
The UIScrollView should have Scrolling Enabled. The UITextView should not.

resize superview after subviews change dynamically using autolayout

I cant for the love of god the the hang of this resizing superview.
I have a UIView *superview with 4 UILabels. 2 function as header for the 2 others.
The content in all 4 are dynamic coming from database.
SizeToFit vs SizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize.
I use autolayout programatically and have set the superview height to be equal or greater to a dummy number.
where and how do I use these SizeToFit vs sizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize. I have read a lot of tips here on stack but ended up with nothing.
DO I need to recalculate the constraints for the superview somewhere specific. Maby setting the height to be ยด#property` in its controller class and remove and readd it?
Atm I have tried to put everything everywhere and then some. Still I get the same size end result with the dummy height and text floating outside. Even after setting clipsToBound on subview.
I am scratching my hair of.. help
If you're using Auto Layout, here's what you need to do:
Make sure you aren't adding fixed width and/or height constraints to any of your subviews (depending on which dimension(s) you want to dynamically size). The idea is to let the intrinsic content size of each subview determine the subview's height. UILabels come with 4 automatic implicit constraints which will (with less than Required priority) attempt to keep the label's frame at the exact size required to fit all the text inside.
Make sure that the edges of each label are connected rigidly (with Required priority constraints) to the edges of each other and their superview. You want to make sure that if you imagine one of the labels growing in size, this would force the other labels to make room for it and most importantly force the superview to expand as well.
Only add constraints to the superview to set its position, not size (at least, not for the dimension(s) you want it to size dynamically). Remember that if you set the internal constraints up correctly, its size will be determined by the sizes of all the subviews, since its edges are connected to theirs in some fashion.
That's it. You don't need to call sizeToFit or systemLayoutSizeFittingSize: to get this to work, just load your views and set the text and that should be it. The system layout engine will do the calculations for you to solve your constraints. (If anything, you might need to call setNeedsLayout on the superview...but this shouldn't be required.)
Use container views
In the following example I have a 30x30 image, and the UILabel is smaller than the containing view with the placeholder text. I needed the containing view to be at least as big as the image, but it needed to grow to contain multi-line text.
In visual format the inner container looks like this:
H:|-(15.0)-[image(30.0)]-(15.0)-[label]-(15.0)-|
V:|[image(30.0)]|
V:|[label(>=30.0)]|
Then, set the containing view to match the height of the label. Now the containing view will ride the size of the label.
As #smileyborg pointed out in his answer, connecting the content rigidly to the superview informs the layout engine that the simple container view should cause it to grow.
Yellow alignment rectangles
If you want the yellow alignment rectangles add -UIViewShowAlignmentRects YES in your scheme's list of run arguments.
This almost follows #smileyborg answer and comes with a concrete example.
Won't describe all constraints, but those related to the calculation of the height of UI objects.
[Label] Labels must not have a fixed height constraint, in this case, AutoLayout won't resize labels to fit the text, so setting edge constraints is the key. (green arrows)
[Subview] Steps 1 and 3 are very easy to follow, but this step can be misunderstood. As in the case with labels, subviews must not have height constraint set. All subviews must have top constraint set, ignoring bottom constraint, which can make you think will trigger unsatisfied constraint exception at runtime, but it won't if you set bottom constraint for the last subview. Missing to do so will blow the layout. (red arrows)
[Superview] Set all constraints the way you need, but pay big attention to the
height constraint. Assign it a random value, but make it optional, AutoLayout will set the height exactly to fit the subviews. (blue arrows)
This works perfectly, there is no need to call any additional system-layout update methods.
This was made dramatically easier with the introduction of Stack Views in iOS 9. Use a stack view inside your view to contain all your content that resizes, and then simply call
view.setNeedsUpdateConstraints()
view.updateConstraintsIfNeeded()
view.setNeedsLayout()
view.layoutIfNeeded()
after changing your content. Then you can get your new size by calling
view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
if you ever need to calculate the exact size required for a view.

Resources