Really struggling with Autolayout - 4 buttons arranged 2x2 - ios

I have a fairly simple scene in my Storyboard, but I can't manage to arrange the four orange buttons with Autolayout.
I've been trying to get this right for a week. I've searched online and I must not have the right keywords, because I haven't found anything that applies.
Everything but the orange buttons are behaving correctly for all devices.
The orange buttons should be arranged in a 2x2 grid.
I want the distance between the "Question" label and the first row of buttons to be the same distance between the second row of buttons and the "Home" button.
I want the orange buttons to retain the same aspect ratio, and I want them to grow/shrink as much as possible, but they should remain centered horizontally and vertically between "Question" and "Home" and the left/right edges.

Use container views to divide and conquer the layout.
Start with top, middle, and bottom views:
The middle view constraints are:
Center X in container
Center Y in container
Aspect ratio 1:1
Leading/Trailing/Top/Bottom space = 10, 250 priority
Leading/Trailing/Top/Bottom space >= 10, 1000 priority
This will give you support for both landscape and portrait orientations:
Now you can add the 4 grid views inside of the middle view:
The grid views will have equal widths and heights to each other, and set the spacing between the views to taste.

You can only do so much using the storyboard. You will either have to insert an outlet and manually manage the frame sizes, or you can programmatically use autolayout to mimic your intended behavior. I can help you with the programmatic part if you are open for it.

Related

Constraints to resize buttons to fit any screen - Xcode swift

I don't really understand constraints and have tried many different suggestions found online. All they seem to do is bunch everything up on top of one another or do nothing at all.
I have the following IPad application but I want it to work on any size device, mainly a IPod touch.
The page is simply two buttons that I want to remain the same no matter what screen they're on.
Any help on this appreciated.
It helps to think about points of reference that won't change with different screen sizes. Sometimes you want things on, say the top left corner so you just do constraints to the top and the left.
I'll give you two suggestions
Suggestion One
For your case, it seems like you might want to do constraints off centerY since you want them to be in the middle despite the screen size.
So I would make a constraint to "Center Vertically in Container" and then tap on the constraint and adjust it's value to negative or positive, so that way it's always X pixels above or below the centerY.
Now that's not going to be enough. it knows it's Y position but it doesn't know its height, width, or X position. So you need to add enough constraints to satisfy those.
A few examples:
X/Width: Two constraints to leading and trailing on each button OR Center horizontally and fixed width constraint. (again be careful with fixed width constraints since screen sizes can change, sometimes it's what you want though)
Height: Yeah just give it a height constraint in this case.
Note that this means no matter the screen size they'll always have the same gap between them (and maybe different gaps to the other edges).
Suggestion Two
Use a container view, either a stack view (fill, equal spacing, vertical alignment, a spacing value for gap between) or normal view.
You can make the view a fixed height based off the height and spacing between the buttons you want. Then simply center that container view horizontally and vertically on the super view.
Nonsuggestion
There are certainly other ways (like using buffer views with equal heights constraints. So you'd have an invisible view on top, a view in between and a view on bottom. and you'd give those equal heights constraints and align the buttons to the edges of the invisible views surrounding them. As long as you gave the buttons a fixed height this would work for vertical constraints) but I think these two would probably be the best.

AutoLayout has buttons on top of each other

I have constraints on my UIButtons in iPad app so that the top row of buttons stays a certain distance from the UIImageView, and so the bottom row of buttons stays pinned fairly close to the bottom. However, when I rotate it goes all wrong. Here is the image of when in Portrait and when in Landscape. How can this be fixed, given the less amount of vertical real estate in landscape mode?
You will either need to scale the size of your subviews upon rotation, or use a scroll-view.
Because you are positioning your buttons a certain distance from the bottom of the screen instead of keeping consistent spacing, they overlap when not enough space is available. (I'm assuming from your screenshots that it is breaking the constraint between the 2 rows of buttons, assuming there is one)
You can relate the available space for each row of buttons to the total vertical size of the main view. For instance, let the image be 50% of the total vertical space, and 25% to each row of buttons (I would set up a container view for each row and then add constraints to the containers). Make sure that your images/buttons properly scale and maintain their aspect ratio.
Another option could be to recognize when the view rotates, and modify all of the constraints so that your buttons all layout in a single row, but this could involve a decent amount of coding to swap/change most of your constraints.

Evenly spread buttons horizontally across screen using autolayout. Not equal spacing between them

I need to layout a series of buttons horizontally across the screen so that they're equally spaced across the screen, not equally spaced between themselves. An example is having 3 buttons there they are evenly spaced so that the first button is centered at 25% of the screen width and others are 50%, 75% of the screen width.
This is relatively straightforward manually placing them in x,y coordinates but I'm trying to avoid mixing approaches.
The prevailing recommendation is to use spacers (UIView) between the buttons and put a constraint to make the spacers equal. This does not work if the buttons being spaced are of potentially different sizes. Say there are 3 buttons labeled, "A", "B", "ReallyLong". I still want them centered, with "B" in the middle of the screen. Equal spacers leaves equal spacing between them but not evenly distributed buttons. ReallyLong takes up too much room and B is not centered
You can create a constraint to align the centerX of a button to its container. Then, edit that constraint so that the centerX of the button is equal to the container's trailing attribute, with a multiplier of 0.25, 0.5, or 0.75 (and a 0 constant). To do this most naturally, you may need to tell Xcode to swap the first and second items, so that Button.CenterX equals Superview.Trailing (with multiplier) rather than the other way around.
Apple has explained it really well.
Creating Equal Spacing Between Views
To lay out several views that are proportionally spaced based on the orientation of a device, create spacer views between the visible views. Set the constraints of these spacer views correctly to ensure that the visible views are able to stay spaced apart based on the orientation of the device.
The following example uses the steps in the above task to show how to position two views proportionally spaced. The spacer views are annotated for the example, but are normally left empty with no background. First, create the two views and place them in the storyboard.
Add the three spacer views—one to the left of the leftmost view, one between the two views, and one to the right of the rightmost view. The spacer views don’t have to be the same size at this time because their size will be set through constraints.
Create the following constraints for the spacer views:
Constrain the width of spacer view 2 and spacer view 3 to be equal to
the width of spacer view 1.
Constrain the width of spacer view 1 to be greater than or equal to
the minimum desired width.
Create a Leading Space to Container constraint from spacer view 1 to
the container.
Create a Horizontal Spacing constraint from spacer view 1 to view 1.
Set this constraint to be a less-than-or-equal-to constraint with a
priority of 1000.
Create Horizontal Spacing constraints from spacer view 2 to view 1
and view 2. Set these constraints to be a less-than-or-equal-to
constraint with a priority of 999.
Create a Horizontal Spacing constraint from spacer view 3 to view 2.
Set this constraint to be a less-than-or-equal-to constraint with a
priority of 1000.
Create a Trailing Space to Container constraint from spacer view 3 to
the container.
These constraints create two visible views and three invisible views (spacer views). These spacer views automatically resize as the orientation of the device changes, keeping the visible views proportionally spaced, as shown in the following two figures:
I think I solved it. I have earlier been able to get even spaces BETWEEN buttons by inserting spacers between each button and setting the spacer widths to be equal. And addition is to also specify that the button widths are equal. This seems to be working well. All the text is centered in the appropriate place.
For 3 buttons, here's the one visual constraint that seems to do it.
Constraint = "H:|[spacer0(>=0)][button0][spacer1(==spacer0)][button1(==button0)][spacer2(==spacer0)][button2(==button0)][spacer3(==spacer0)]|"
With the above text it looks like this, which is what I was looking for. The middle button is centered, the right button is centered on the right third of the screen:

Vertically spread/spaced screen elements using Autolayout and Interface Builder

I have a relatively simple portrait-only UI, laid out in a Storyboard, with items which I want to vertically spread to fill both 3.5 inch and 4 inch screens.
In other words, I want the spacing between the controls to be adjusted so that the UI nicely fills the screen, irrespective of the screen form factor.
This doesn't seem like an unusual thing to want to do, however I just can't get Interface Builder (within Xcode 5) to add the right constraints - I only seem to be able to get it to add fixed vertical space constraints, which do not adjust for different screen sizes.
Does anyone know how to do this without resorting to programmatic UI construction? I've invested a lot of effort in getting the Storyboard-based UI just right.
The solution needs to work on both iOS 6 and 7. Thanks!
How to do this depends on exactly what kind of adjustment you want when the screen size changes. One way to do it to give the top and bottom most views vertical spacing constraints to the top and bottom of the superview, respectively. Add a view, I usually use a UILabel with no text, in between all the views you have stacked vertically, and give them equal heights to one another. Give one of those "spacer" views a fixed height, but edit it so its priority is less than 1000 (which means it's not mandatory that it be satisfied). Then add spacing constraints between each nearest neighbor above and below each "real" view and the "spacers", so that you have all the views from top to bottom connected together by vertical spacing constraints. When the screen size changes, the only thing that can change will be the height of the "spacers", since the priority is less than 1000, and all other constraints are mandatory. My constraints look like this:
The labels each have the standard (8 point) spacing to the "real" views above and below them. The top and bottom views should have whatever spacing you want to the screen edges.
Apple have now posted a document which describes the officially-endorsed approach to solving this problem:
https://developer.apple.com/library/ios/documentation/userexperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html#//apple_ref/doc/uid/TP40010853-CH5-SW8
Summary of the approach: insert spacer views between your controls, which have equal width/height (as applicable) constraints.

Views are Horizontally and Vertically Ambiguous with complex layout

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.

Resources