Views are Horizontally and Vertically Ambiguous with complex layout - ios

I have a UIViewController on my storyboard that has 2 subviews side-to-side horizontally. I added constraints to fix the leading and trailing edges to a constant (20 pts), and another constraint to keep the widths equal. If I assume the following, it should be possible to calculate what the width of each subview will need to be:
the subviews do not overlap
there are no other views present (horizontally, at least)
the width of the screen (the superview) is known
However, XCode gives me a warning that my views are horizontally ambiguous. I'm guessing that means that XCode is not making one of these assumptions, but which one is it? And is there a way for me to instruct XCode to make that assumption?
EDIT: Okay, played with it a bit and got the warning to go away, but it looks like it's not making the first assumption - it's just setting each subview's width to superview.width - 40, and happily burying one view underneath the other. So the question is how to I stop them from overlapping?
EDIT 2: Okay, my actual screen is a lot more complicated than my simple example. Here's what I got:
So in this setup I have 4 views that are vertically and horizontally staggered.  I want the blue, red, and purple views to all be the same subview.frame.size.width = superview.width - 60. The blue and purple are lined up in the left column, and the red is alone in the right column, and all the gaps (between the two columns and between each column and it's nearest edge) are at a constant (20 pts). These 3 tables have a variable height, which I will be setting programmatically as described in James's answer here. At the bottom is a pink view that stretches the width of the screen (minus gaps), and sits at a constant 20 pts below either the purple or the red view, whichever is lower (which I'm attempting to do by giving it a spacing constraint of >= 20 to each view, and I hope that it will pick exactly 20 for one of them). Since all of the heights are dynamic and may not necessarily fit on the screen at the same time, I made their superview a UIScrollView instead of the normal UIView.
When all is said and done, I'm still getting a warning that all 4 of my views are horizontally ambiguous, and that the pink bar is vertically ambiguous. I think it's having trouble realizing what is supposed to go next to what, which is why it thinks it's horizontally ambiguous. And I think it's not picking to place the pink bar exactly 20 pts below either the purple or red views, which is why it thinks it's vertically ambiguous. Can anyone confirm or deny any of these suspicions? Or suggest a way around it? When I run it in the end, I just get this (I made the background of the scroll view yellow, which you can't tell in the storyboard screenshot):

Vertically Ambiguous
Okay, I think I've solved the vertical ambiguous part. I added two vertical constraints between the pink and purple views and two vertical constraints between the pink and red views. For each pair, the first constraint is that the spacing between them must be > 20 pts, and it has 1000 priority. The second constraint is that the spacing is = 20 pts, but it only has an 800 priority.
For example, if the bottom of the purple view ends up being lower than the bottom of the red view (as it is in my first screenshot), Xcode should try to set the vertical distance between the pink and red views = 20, but it will realize that that conflicts with condition that the space between the purple and pink being >= 20. Since the >= constraint has higher priority, the = constraint will be ignored. Now, when Xcode looks at the constraint that the spacing between the purple and pink views being = 20, it checks that against the constraint that the pink and red must be separated by at least 20. Since the bottom of the red view is higher than the bottom of the purple view, the >= 20 constraint between the red and the pink still passes.
So TL;DR, you can set up a view to have a spacing at a given value (x) from the most extreme of multiple views by giving it a >= x constraint with 1000 priority and giving it a = x constraint with <1000 priority for each view you are considering - and my vertical ambiguity problem has been solved. I do not yet have a solution for the horizontal ambiguity for all 4 of the views.
Horizontally Ambiguous
Okay, I got the horizontally ambiguous part fixed now as well. What it boils down to is that constraints in scroll views (and therefore table views) work differently than they do for any other kind of view. Here's what the step-by-step looks like.
Place the UIScrollView
Place a UIView into the UIScrollView to serve as a "contentView" for that scroll view
Add constraints to pin the contentView to all 4 corners of the scroll view AND pin it's width and height (so 6 constraints between the contentView and it's superview - 2 more than usual). Note that the width and the height can be pinned to something much larger than the normal screen size, which is probably why you are using a scroll view to begin with.
Add all of your other views you want in the UIScrollView (UIButtons, UILabels, etc. - I'm just going to assume UILabel from here on so I don't have to type as much, but any kind of UIView subclass will work) as subviews of the contentView, NOT directly as subviews of the UIScrollView
With this setup, the UILabels that are given constraints to their superview will constrain to the contentView, which has a defined size, so nothing is ambiguous.
Alternatively, if you want to fix the sizes of your UILabels (or dynamically calculate them, depending on the functionality of your app) and let the contentView expand to hold them:
Place the UIScrollView
Place a UIView into the UIScrollView to serve as a "contentView" for that scroll view
Add constraints to pin the contentView to all 4 corners of the scroll view AND pin it's width and height
create an outlet for the width constraints on the contentView (let's say we name it contentViewWidthConstraint)
place the UILabels
fix the sizes of the UILabels
create an outlet for the width constraints on the UILabels
Then in the code for viewWillLayoutSubviews
add up the widths of all of the UILabels and any gaps you want between them (as a CGFloat, which I'll call totalWidth)
set contentViewWidthConstraint.constant = totalWidth
And you're good to go! Note that I assumed you were setting the width in most of this example, but it should be just as applicable to height.

The problem is that many different widths of the two views will satisfy the constraints that you've set up. Here are two examples (I drew the shapes stacked vertically to make it easier to see the overlap example):
You can add a horizontal space constraint with a value of 0.

Related

In a UITableViewCell used with autosizing, what is missing from the vertical constraints to make the height as small as possible?

I'm trying to use dynamic heights in a UITableView with a specific cell layout. Consider the following illustrative representation of that layout:
I have the horizontal constraints working properly (15px from both edges, 15px between them, equal widths) but I'm struggling with the vertical constraints. Here are the vertical requirements for this layout:
The vertical intrinsic content size of both the green and blue rectangles are based on external data which is passed to the cell at the time of creation.
Both rectangles are vertically centered within their superview
There will always be a minimum space of 15px between the top/bottom edges of the rectangles and the respective edges on the superview. In other words, whichever one is taller dictates the height of the superview (i.e. the cell)
To that end, here's what I have constraint-wise so far:
Vertical center constraints for both rectangles
Height constraints of the rectangles equal to or less than the height of the superview minus 30 (i.e. if the rectangle's height is 40, the superview must be a minimum of 70. This theoretically achieves the same effect as setting separate top and bottom '>= 15' constraints while using two less.)
Vertical content Hugging on the superview set to 'required' (i.e. 1000)
The third point is because the second points together only define the minimum height for the superview (yellow), but not a maximum. In theory, if it had a height of 10,000 it would still satisfy those constraints.
My thought is setting its content hugging to 'required' would make the superview as short as possible without violating the other constraints, thus at all times, either the green rectangle or the blue rectangle would be 15 px from the edge depending on whichever was taller. However, the height still seems to be 'stretched out' as seen here...
Note: The views on the inside are properly vertically centered and correctly maintain a minimum distance from the top/bottom edges. The problem I'm trying to solve is restricting the height of the superview to be as small as possible.
It doesn't appear that I'm getting any ambiguous constraint messages (I don't see anything in the logs, but I believe I should be because again <= constraints aren't enough on their own, so I'm not sure exactly how to use the tools to debug this, or to find out which constraint is driving the height.
So, can anyone help?
P.S. To ensure it wasn't something external to the cell, like forgetting to configure auto-heights for the UITableView, I removed the two rectangles and replaced them with a simple multi-line label pinned to all four edges. When I ran it with that, the cell properly shrank in size as expected. I bring that up to hopefully stave off answers suggesting that's potentially the problem.
Reading the requirements you provided I have added constraints shown below:
For demonstration purpose I have used a simple view container instead of a cell. Since inner boxes will have intrinsic content size derived externally, I have taken labels to mimic that.
To reiterate, constraints added are:
Horizontal
Container view(orange) has leading and trailing constraints with the super view.
Inner views has leading, trailing constraints with 15points of space.
Both labels have leading and trailing constraints with 9 points.
Both inner views have equal width constriant.
Vertical
Container view is vertically in center.
Both inner views have vertically center constraint applied.
Both inner views have top and bottom constraints with >= 15 condition.
Both inner labels have top and bottom constraints with their super views.
I set the no. of lines property of both labels to zero so that they can grow at run time.
At runtime I changed the text of one of the label and the box and container grew accordingly.
To refresh your cell height implement heightForRow method and provide the latest height. A typical snippet will look something like this (assuming you have already initialized the cell):
cell.needsUpdateConstraints()
cell.updateConstraintsIfNeeded()
cell.contentView.setNeedsLayout()
cell.contentView.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
return height
Hope this will help.
Ok, so I was going in the right direction with the content hugging, but the correct way to handle this was to specify a height constraint on the yellow view of 0 and with a low priority. I used 100 to be even lower than the default 250. When I did that, the solver tries to get as close to zero as it can while still respecting the other constraints, therefore 'hugging' the content. Still don't know why content hugging on its own didn't work, but that addressed the issue.

Swift - UIScrollView Scrolls Partially

My problem is slightly different from other's 'Swift UIScrollView' problems when using auto layout:
Problem:
Unlike others, when I run my app, it scrolls. My problem is that the scroll cuts off the bottom 20-30% of the content. I can pull to see the buttons did build and are down there, but when I let it go the scroll snaps back to a false bottom which cuts off my content!!! I've been trying, for days, to fix it to scroll the entire height but it continues to cut off!!
Description of app:
I used auto layout to layout 6 buttons and labels. Each button a rectangular image, with a label directly beneath it. (sorry, the site won't let me post pictures!)
I have my views arranged like this:
MainView > ScrollView > ContentView > Buttons & Labels
I have my contentView pinned to my ScrollView and my ScrollView pinned to my MainView. My buttons and labels all have constraints that are building correctly, to create a list that looks like:
Rectangular button
Label beneath it
Spacing
Rectangular button
Label beneath it
Spacing
Etc.
Can anyone tell me why I can't scroll the full length of the view?
Your description of how your items are constrained is vague, so I'm going to list all of the constraints you need to make this work. Then you can compare what you have to what you need and adjust accordingly.
Your ScrollView should be pinned on all 4 sides to the MainView. (This isn't absolutely necessary. You can constrain your ScrollView however you want, but make sure it can grow as the device and/or orientation changes).
Your ContentView should be pinned on all 4 sides to the ScrollView with offsets of 0.
Since you want your ScrollView to scroll vertically only, constrain the width of the ContentView to the width of the ScrollView using an Equal Widths constraint. To do this, in the Document Outline view, Control-drag from your ContentView to your ScrollView and select Equal Widths from the pop up.
The height of ContentView will be set by the sum total height of everything in it. In order for this to work, your topmost button needs to be pinned to the top of the ContentView, all of your buttons and labels should be pinned to their nearest neighbors, and the bottommost label should be pinned to the bottom of the ContentView. In addition, all of your buttons and labels should have constraints for their widths and heights. I would suggest setting an explicit width constraint and explicit height constraint for your buttons and centering them horizontally in the ContentView. For your labels, set an explicit height constraint and pin the left and right edges to the ContentView.
If you have these constraints and no other ones, your ContentView will be properly sized.
Using contentView, like you said, usually fixes the issue. So chances are you need to take a second look at your contraints. Maybe try this solution in a clean/new project to see that it works. (it does work). My guess would be that some of your constraints conflict each other.
Otherwise I think it would be a good idea to setContentSize of your scrollView in your viewDidLoad.
Another hack would be to place 2 UI objects with their alphas set to zero on the right top corner and left bottom corner. This would hint scrollView to set its contentSize.

Can't align 5 items in a row using autolayout

I am struggling to align 5 items in a row using auto layout as shown in the storyboard.
I can align the 3 items highlighted below but the 2 inbetween just don't want to align.
I have tried pinning the items to the buttons next to each of the items and setting the size but they refuse to align properly. Have also tried setting the 2 misplaced buttons to the leading and trialing container as shown below but these still do not align properly (or even closely).
Am I doing something wrong?
The way to do this is with spacer views. You need to add a UIView between each of your views and give them each horizontal spacing constraints to the view on either side. You'll need to edit these constraints so they are between the spacer's leading or trailing edge, and the view's centerX (the constant value should be 0). Give the spacers equal widths (but no fixed width), a fixed height, and a clear background color. You views on the left and right sides should have constraints to their closest edge, but the center view shouldn't have a centerX constraint (it's not needed). This will cause the views to be equally spaced in all screen sizes, and in both orientations.
Your best bet might be to create another superview (UIView) within which your button items are located. Then you can align your buttons more fluidly within that superview that encapsulates only those buttons rather than just wrestling with the constraints within the card superview, which it seems you are doing. Just make the background of that UIView clear, place the buttons on it, and it will appear exactly the same and you'll have a better way to anchor your views. This could also come in handy when you're targeting the functionality of just those buttons so you can grab them from the subviews of the button bar superview.

How to correctly use constraints when both UITableView and UIImageView are presented on the same view controller

Suppose that I have the following view controller and this is how I want to see it on all iPhone:
If I run it on iPhone 6 it has the following look:
Here you can notice that UITableView not fit the whole screen and UIImageView doesn't placed at the bottom of the screen.
How can I achieve the required behavior via constraints in XCode 6? I thought that I need the following constraints:
Leading space and top space to container margin for UITableView
Bottom space and trailing space to container margin for UIImageView
Vertical Spacing between UITableView and UIImageView
But it doesn't work as expected even after auto-resolve constraints issues:
Thanks in advance.
Ok, a few things here:
Each view needs enough constraints to define it's x and y position, and it's width and height unambiguously. To start with, go back to Interface builder and delete all of your constraints and lay out the view as you would like it to look. You want to have control over every constraint added, don't let IB automatically resolve the issues, as in all likely hood it won't do what you want.
Do you have an image that is the size you want it to be on screen, once you've factored in #2x, #3x etc? If so, then your job will be easier, as the width and height of the image view can be defined by the width and height of the image (ie the image view's intrinsic content size).
In order to use Autolayout effectively, you need to think about your view holistically, and think about how you want your views to behave when the screen size changes, be clear in your head about the behaviour.
To achieve the layout you want, I would do the following:
Constrain the tableview's leading, top and trailing edges to the superview, with a constant value of 0. This means it can get wider and thinner with the device, it will stretch horizontally, but always stick to the top. This has defined the tableview's x and y position, as well as it's width (height still to go, but keep reading...)
Constrain the image view to match the horizontal centre of it's superview (x position defined) and constrain it's bottom edge to the superviews bottom edge (y position defined). If've you've got the right sized asset, then that will take care of the width and height too. If not, you could go ahead give it explicit width and height constraints.
Now we can constrain the tableview's bottom edge to the top of the image view, with a constant of 0 (ie touching). Note we haven't give the table view an explicit height constraint, so as the device screen grows vertically, the table view will stretch vertically.
Autolayout is hard at first. I'd recommended lots of reading to get over the initial hump, really get to know what a constraint is doing, it's limitations, and the way in which the system parses constraints to translate them into frames. This book is really good, and really helped me learn:
http://www.amazon.co.uk/Auto-Layout-Demystified-Mobile-Programming/dp/0321967194
Best of luck
First make sure you have selected the correct size class. The 'Compact Width | Regular Height' size class must be selected in the Interface Builder. Now add the Trailing space,Leading Space, Top space and Bottom space constraints to the table view. For the image view set the view mode to Aspect fit and add the constraints : Align Center Y ,Top space,Bottom space, Leading space, Trailing space and Aspect Ratio .

Using AutoLayout and ensuring bounds of parent view to fit subviews when resized

I really wish I could get my head around auto layout. It seems that whenever I read an abstract description of how things are supposed to work it makes sense, but whenever I actually try and put it into practise it always causes massive headaches. So, apologies if there is already an answer out there for this but I couldn't find one.
The problem should be relatively simple. I have a container view, which contains two subviews, shown here in hideous colours for maximum readability :) :
The bottom (black) view, should remain at it's current size and maintain the spacing between it and the red view, and the spacing between itself and the bottom of yellow view.
The red view I want to be able to dynamically change its height, causing the black view to shift up/down accordingly whilst the yellow view resizes to fit both the red+black views.
For the black view, I've added constraints to:
Set the height to 94
Pin the leading and trailing space to superview
Set the top space to the red view at 51.
Set the bottom space to the yellow view at 20.
I am trying to understand what seemingly-mystical set of constraints I need to add in order that, when the red view is resized vertically, the black view stays its current distance from the red view and maintains its size, and the outer container view resizes accordingly so that it contains the red view + black view + vertical spacing between the views.
For the red view, I've added constraints to pin the top, left and right spacing to superview, but have had no luck working out the vertical constraints. Currently I've got a constraint pinning the height =114 with a priority of 999 and a constraint with height >=114 with a priority of 1000 thinking this would ensure the view is always at least 114 in height...
The fun starts when I try and manually set the height of the red view.... I've added a button on the view, and when the button is pressed, I manually set the bounds of the red view. (The red view's default height is 114):
CGRect bounds = self.redView.bounds;
bounds.size.height = 300;
self.redView.bounds = bounds;
When I run this and press the button, the view goes from this:
To this:
To me this makes no sense whatsoever. Why does this result in:
The Y origin of the red view changing? Particularly when there is a "required" constraint telling it to stay 20pts from the top of yellow view.
The spacing between the red and black views breaking down, even though the constraint on the spacing between them is "required"?
The vertical size of yellow view not changing. Again, despite the spacing between red+black, and me having tried just about every combination I can think of in terms of compression resistance and content hugging priority.....
I really want to understand this, so would be really grateful if someone can explain what additional constraints / changes to constants are required, but more importantly WHY they are required, because to me it doesn't seem clear at all how the layout system comes up with its answers....
Any clarification much appreciated.
Thanks in advance
(All code above is running on iOS 7 and built with Xcode 5.0.2).
You don't need any fancy constraints to do what you want here -- no inequalities or messing with the priorities. In addition to the constraints to the sides, the red view should have 20 to top, 51 to black view, and a height constraint of 114. The black view, has a 20 to the bottom and a height of 94. The superview (yellow) should have zero constraints to top, left and right -- no height. You should have an IBOutlet to the red view's height constraint. When you want to change its height, modify its constraint (don't set frames):
- (IBAction)resizeYellowView:(id)sender {
self.heightCon.constant = 300;
}
Everything is linked together from the top of the yellow view to the bottom with fixed values, so the only thing that can change when the height of the red view changes, is the height of the yellow view.

Resources