Floating view into second row with autolayout - ios

Can I setup constrains on storyboard view controller views so that I will have three views one next to other when there is enough horizontal space. In other case it will "float" one of views to next line?
Like on this example:
Enough space
Texts causing third view to "float"
Maybe stack views can help here?

There is no reason to expect the auto layout system to be able to accomplish this, nor can it.
What you want to use here is a collection view (UICollectionView) and have each of your views be a cell in the collection view. In fact, if you look at your views, they look quite the reusable views, where each one has an icon, a title and a detail text. A collection view can be set up to put cells one next to the other until there is no more space, after which cells are added below, and so on.
You can then setup your scene to layout around your collection view using auto layout, where other views would reposition correctly below the collection view content when the collection view grows in height.
See this layout as an example of what you need.

No. There is no way to setup views and constraints in a storyboard that automatically float into a new row if they don't fit into the same row.
(In my opinion that's a big weakness of the Autolayout system.)
The reason for that lies in the concept of constraints: Mathematically they represent linear equations that are normally independent for the x and y dimension. The only exception are aspect ratio constraints that connect the width of a view with the height of that (or another) view. But I cannot think of a way how you could use an aspect ratio constraint to break views into a new line if needed.
When the system resolves your constraints at run-time and computes the actual frames of your views it simply solves the system of linear equations for each dimension (or for both dimensions if an aspect ratio constraint is present). Adding the option of floating views to the Autolayout system would make the whole layout process a hell of a lot more complicated because you cannot describe that behavior as a simple linear equation.
(Stack views won't help as they only work for one dimension as well: either x or y.)
Recently, I needed the very same floating behavior you described and created a FloatingContainerView subclass of UIView. I generalized it so you can use it for any kind of view and translated it into Swift.
You can now find it on GitHub:
https://github.com/mischa-hildebrand/FloatingContainerView

Related

Is it possible to use constraints to specify a views height as percentage of the parent view, but also limit the maximum resulting height?

I am new to XCode and have set up a view controller using the interface builder. The root is a stack view set to Alignment: Fill and Distribution: Fill Proportionally.
Each item in the stack view is pinned to the parent stack view, and constraints are used to specify the height as percentage of the stack view (i.e. the entire screen) using multipliers of Superview.Height.
This part works great, all the views scale to the desired height percentage and it looks good on devices like iPhone 5s.
However, on devices like XS Max some of the controls, such as text fields, simply look too tall and the text inside gets too much space above and below (compared to the text field rectangle).
The desired result would be that all the views scale with the screen, but in case the resolution and aspect ratio is too great the text fields would remain a bit smaller and yield the remaining space to the other views.
My first idea was to add a second constraint to the text fields where the height is set as Less Than or Equal to (for instance) 30 in conjunction with the existing height percentage. But I have tried that out, and tried different Priority values, but no luck so far.
Is there any way to achieve the desired result using constraints and auto layouts on the current (flat) view hierarchy?
Or is it better to look into adding nested stack views and set the percentage height constraint on the nested stack view, while letting the text field inside handle its own height?
Suggestions appreciated!
Distribution: Fill Proportionally is perhaps the most misunderstood property of stack views, and is likely not doing what is expected (or what is wanted).
As per OP's comments... changing the Distribution property (I'm assuming it was changed to Fill) fixes the issue.

Why are my autolayout constraints conflicting?

I have an app using scroll view on a page with numerous icons within that scroll view. I don't understand auto layout very well, but I have constrained every single icon to its current canvas value to the view (after measuring and placing each icon in the correct spot). Why are there still conflicts (188 to be precise) and unambiguous positions?
As an example of what I have done in case the above is not clear, I have placed an icon in the top left of the scroll view. I have then constrained that icon on top, bottom, left and right of its current canvas value from the view.
This extremely hard to explain without showing (as in "live-demonstrating"), but I'll give it a try:
Autolayout for Scrollviews works differently than you probably think. You can't simply specify where to place its contents in relation to its bounds like that. After all, the "canvas" of the scroll view determines its size from the content you scroll over, whereas the size of the actual scroll view (lets call it its viewport, or "window onto the canvas") is determined in a different way (for example by constraining it to the elements around it).
This the opposite of how autolayout works for other views, since they are usually constrained by the size of their parent (up to the top view of a view controller and/or the screen bounds).
What you want to do is add all your items in a single view. From the smallest known size, constrain them to one another. Finally their container has a size resulting from its contents, i.e. your items. This is then the size of the "canvas" of the scroll view, so add it and constrain its position somehow (for example centering it horizontally and vertically). Now, the actual size of the scroll view and its position should be constrained by elements surrounding it.
It might take some thinking and training, so maybe you will want to test around in a small training project that only has a limited number of views to figure it out.
Add scroll view on the screen with constraints to parent view
Add content inside of scroll view
Put constraints between added view and parent view (NOT SCROLL VIEW)

How to arrange UIStackViews?

I'm starting a new project that supports iOS9 upwards and after looking at Apple's constraint guidelines they appear to suggest using StackViews whenever possible. After reading a few articles and the apple documentation I've a basic understanding of how to create them and their benefits but I'm still not sure when not to use them and how to arrange them.
For example in the below view should I use:
One big StackView on a vertical axis that covers the entire super view.
Three StackViews with regular constraints pinning them to each other and the super view.
One big StackView that covers the entire super view with three stack views within that view
No StackViews, this view isn't suitable
In general how do I decide how I layout my stackviews and whether to use them?
I've started to use stack views more and more, especially since Xcode 8.x. Every stack view you add saves you adding some auto layout constraints (3 vertically stacked labels in a view would probably need 9 constraints, that could be just 3 with a stack view)
If all elements are in vertical stack views, it's unlikely you'd need to embed one inside another - you'd usually do that when you have a horizontal one inside a vertical, or vice-versa. So in the example above, I'd start with one large stack view.
In Xcode 7.x there were issues with the intrinsic sizes of UILabels not being calculated correctly. In these cases, you can set a placeholder intrinsic size for each label in the size inspector.
That problem aside, get stacking!
I have a problem and can't see your screenshot but I have some points that help you decide:
Do use stack views for all linear arranged views
I prefer set the root stack view to the size that contains exactly the content without whitespace (so constraint it to be as the superview size only if that's the content size)
The stack view uses auto layout to determine the size of it's subviews, so you should validate that your subviews do tell their best suitable size - maybe by using intrinsicContentSize() [only when needed!] (be careful with it)
you can practice stack views in interface builder, try to change stack view properties, hide subviews (with hidden property), and play with constraints, it's great!
Good luck =]
Have a nice play

sizeThatFits on view with constraints (auto layout view inside frame layout view)

View A layouts it's subviews by calculating and setting their frames in layoutSubviews.
View B uses constraints to layout it's subviews.
View A contains a number of View B instances.
View A needs to know the size of its subviews when it layouts them, so it calls sizeThatFits: on them.
View B's height depends on it's width. In my example it has a multiline UILabel inside it.
How should B implement it's sizeThatFits: so it returns the correct size taking both the size parameter passed to sizeThatFits: and the constraints in account?
What I've tried/considered so far:
systemLayoutSizeFittingSize: - doesn't work because it doesn't take into account an exact size passed to it. It can't be used to specify an exact width for e.g a view that grows vertically as it shrinks horizontally. It only gives either the most compressed size or the most expanded size.
intrinsicContentSize- this is used to pass information to Auto Layout as far as i understand from the docs. What we want is to get information from Auto Layout.
This is how i've reasoned so far. Please correct me if i've made a mistake. And please help me, i'd be very grateful if someone could point me to how this is done!
edit: Also, maybe there is some entirely different way to do this, without even using sizeThatFits:? The only requirement i have is that i can continue using frame layout in the "outer" view (View A) and auto layout in the "inner view" (View B).
edit2: Added a picture to illustrate the exact case i must solve
edit3: Forgot to mention, i need to support iOS 7.0
From the picture it would appear that a UIStackView might be a better approach for View A?
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIStackView_Class_Reference/
and then just constrain with a margin offset the bottom of the View A UIStackView to the bottom of the last View B
I would use Autolayout for the lot. You can still specify exact sizes and positions, you just do it with constraints instead of with frames.

Autolayout how to get computed value from view

There is one thing that is really making me crazy about auto layout , I'm doing my tests and found that if you subclass a UIView and you put some views with their constraints is impossible to know the computed value.
Let't say that we have a TestViewClass that inherits from UIView, we've got some subviews inside. we want to use it for iPhone and iPad, so we use in two sizes, let's suppose 100x100 and 200x200 we made our constraints to make everything work. We build this view placing those subviews in some positions.
Now we need to build another subviews that contains a number of buttons as subviews (contentView) which number is given at runtime.
The algorithm will pick the size of this content view and put the correct number of buttons calculating the space between them in a way that the will be at the same distance from each other but covering the whole content view width. For example, the contentView is 200 point in width, buttons are 3 and squared with a side of 60. Together they cover 180, so we have 20 points left that should be placed as a space between the buttons->10 points.
That's pretty easy, made thousand of times before auto layout. To do that I need the width of the contentView which has some constraints to its superview that make it resize its width according to superview size, the problem is that I can't find any place inside UIView implementation where I can get the final value of the size of the contentView.
I tried view -layoutSubviews, -updateConstraints, -didMoveToSuperview, value show are alway the original one. When the new frames are calculated?
I clearly didn't get something about auto layout...
I discovered this problem trying to set table view cell height, trying to make them appear all on screen independently by the size of the table view. here is the related question other question
Finally I've get what I was missing about auto layout and it is a fundamental concept.
Autolayout doesn't work like autoresizing masks, the new frames aren't
updated instantaneously but by request.
This makes a huge difference, it seems to be in sync with the CATransaction render cycle, that makes sense, because layout calculation could be an expensive task and is useless to do until you really need it, most of the time during rendering.
Of course there are few exceptions, like this one and how can we force auto layout to make this calculation? we can do setting -setNeedsLayout method and -layoutIfNeeded. the first method marks the view as "dirty" and the second forces an immediate layout. If you set call those two methods in -didMoveToSuperview right after you get correct updated frames. Hope this helps,
Andrea
You can use view controller's viewDidLayoutSubviews, which will notify you when the views are laid out. If all of this is adding of constraints is happening in a custom UIView, you could simply write a method for your view called viewDidLayoutSubviews and have the view controller call that when the view controller receives it. At this point, the containers should have their dimensions properly configured.
But, there are a couple of approaches of even spacing a bunch of buttons without needing to bother knowing the size of the container view:
One approach I've used for even spacing controls in a container is to create what I call "spacer" views (views that are present, but just have a clearColor background, so you can't visually see them). You can then create a series of constraints using, effectively, something like:
H:|[spacer1][button1(60)][spacer2(==spacer1)][button2(60)][spacer3(==spacer1)][button3(60)][spacer4(==spacer1)]|
If your number of buttons is fixed, you can do this in a single constraintsWithVisualFormat, like above. If it's a variable number of buttons, you can iterate through them, building a VFL string for each pair of button and spacer (e.g. the first button would use VFL of
H:|[spacer][button(60)]
all the subsequent ones would create another button and another spacer and use the following VFL:
H:[previousButton][spacer(==originalSpacer)][button(60)]
and then I add one final spacer at the end:
H:[lastButton][spacer(==originalSpacer)]|
This is a little cumbersome, but as you can see, your controls are perfectly laid out, evenly spaced, and you never needed to know the dimensions of the container.
Another approach is to use the NSLayoutFormatAlignAllCenterX, attribute. This bypasses the need to create all of those spacers, but if you use a simple algorithm like I do below, the centers will be evenly distributed amongst themselves. Thus, in addition to the standard vertical constraints and the horizontal width constraint for the button, you can then control the horizontal placement of the buttons with:
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:button[i]
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeCenterX
multiplier:2.0 * (double) (i + 0.5) / (double) n
constant:0.0]];
where i is the zero-based index of which button you're setting this horizontal constraint for, and n is the number of buttons.

Resources