Autolayout & Alignment Constraints - ios

I want to make similar ui for all iPhones & i am currently using auto layout for that, but i do not know how to create this `UI` using `NSLayoutConstraint`?
This UI works fine in small screen but i want same ui for bigger iPhone as well(keep certain amount of space between buttons). how can i add constraint in this to get same ui as iPhone 4s.
i took UIButton & below titles are UILabel.
Some one help me out here.
Screenshots.

Personally I find that trying to make sense of Auto Layout constraints in code (and using NSLayoutConstraint) to be quite confusing and difficult to understand. VFL (Visual Format Language) is powerful but I would recommend if you are just starting out with Auto Layout then working in Storyboard or a XIB file may be easier (depends on your individual preference).
Since this is quite an involved topic I've put together a sample project for you to reference on GitHub. The benefit of this approach is that there is literally no code - all of the configuration is done in the Storyboard (or can also be done in a XIB file). Highlights of the constraints I created are below however please reference the sample project for specific details:
Background View (Purple) - Constraints pin the top, bottom, left and right sides to the edge of the parent view.
Icon Container View (White) - Constraints pin the bottom, left and right sides; a height is also set which accommodates all of the icons.
All image views have a width constraint and aspect ratio constraint (maintains equal width and height) and all labels are constrained to their appropriate image with the appropriate vertical constraint (top).
The outside corner icons are all constrained only to their corner (top-left, top-right, bottom-left, bottom-right). The center corners are constrained to be centered horizontally inside the view.
If you are just getting started with Auto Layout you may find the following presentation I made helpful in learning the basics.
Here are three screenshots from different simulators which show how the layout adjusts automatically depending on the screen size: iPhone 5s, iPhone 6, and iPhone 6 Plus:
Technically, landscape orientation is also supported by these constraints however the result may or may not fit your requirements. (I am unsure what orientations you are planning to support.)

I don't really want to write the code for your whole view as its not the best way for you to learn and plus I don't have the time right now. Instead, I will tell you the approach I usually take:
Create the view
UIView *myView = [UIView new];
Set translatesAutoresizingMaskIntoConstraints property
myView.translatesAutoresizingMaskIntoConstraints = NO;
Add view as subview
[self.view addSubview:myView];
Add the constraints
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:myView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
[self.view addConstraint:constraint];
You need to add multiple constraints in order to layout the view exactly as you want in relation to the other views around it. Top, left (leading), right (trailing) and bottom.
I suggest either creating your own category in order to make writing the constraints quicker and easier to understand when reading it back. Here's one for example: PureLayout
You can also use constraints with a visual format but I often find this can be harder to get your head around.
More reading on the whole subject can be done here

It depends on your requirement and their may be different ways to achieve. But if you ask in this particular case with three items as fixed then following may help:
The buttons at the corners can have constraints with borders.
All the labels can have constraints with their respective buttons to be same width and should be aligned horizontally centred with buttons.
The buttons in centre and buttons at the borders should not have any dependencies with each other.
The buttons in centre can have constraints with top and bottom of the container view.
Buttons in centre should have constraints for horizontally centred with the container view.
You might need to add some more constraints, but based on the above suggestions it will work for any screen size.

Related

Auto layout space between elements relative to screen height [duplicate]

I understand the old Struts and Springs method of aligning, sizing and distributing views in Interface Builder. However, I cannot seem to figure out how to evenly distribute views using auto layout with Xcode 5. There was a way to do it using Xcode 4, but that option is gone.
I have 7 buttons arranged in a vertical stack. On a 3.5" layout, it looks great. When I preview the screen in the 4" layout, all of the buttons remain tightly packed and there is a large amount of space below the last button.
I want them to stay the same height, but I want the space between them to be able flex so they can spread out across the screen.
I've been able to get the height of the buttons to flex and fill the space, but that is not my desired behavior. I would like to learn how to use Auto Layout to replace my old Springs behavior, but I can't seem to find any way to do it through Interface Builder.
I'm ok with the top button either being a fixed space from the top edge or a proportional space from the top edge, likewise for the bottom button and the bottom edge. Those are less important to me, I'm good with either.
But I really need to figure out how to evenly distribute the extra space between each of the items in the view.
EDIT Note that in iOS 9 this technique will become unnecessary, because a UIStackView will perform distribution automatically. I'll add another answer explaining how that works.
How to Perform Even Distribution Using Autolayout
The simplest way to do this in Interface Builder alone (rather than constructing constraints in code) is to use "spacer" views:
Position the top and bottom buttons absolutely.
Place spacer views between all the buttons. Use constraints to position them horizontally (centering them horizontally is simplest) and to set their widths.
Make constraints between each button and the spacer view above and below it, with a Constant of 0.
Now select all the spacer views and set their heights to be equal.
The first screen shot shows me setting this up in IB:
I have deliberately not corrected for the "misplaced views" because I want you to see what it looks like while I'm designing the constraints. Here's the result on both a 4 inch and a 3.5 inch screen:
I have left the spacer views black, just to show you how this technique works, but of course in real life you would make them transparent and hence invisible! So the user sees just your buttons, evenly distributed on either height of screen.
The reason for the use of this technique is that although the notion of equality performs the distribution of values you are asking for, constraints can apply equality only between aspects of views; thus we need the extra views (the spacer views) so that we have things we can make equal to other things (here, the heights of the spacer views).
Other Approaches
Obviously, a more flexible approach is to assign the constraints in code. This may sound daunting, but there's a lot of third-party code out there to help you, such as this sort of thing.
For example, if we have a (possibly invisible) superview whose height acts as a boundary to dictate maximum vertical distribution of our four buttons, we can pin their tops to the vertical center of that superview with a constant of 0 but a multiplier of 0.000001, 0.666667, 1.33333, and 2.0 respectively (if we have four buttons); now the buttons will stay vertically distributed even as the superview changes size in response to screen height or whatever. [In Xcode 5.1, it will be possible to set that up in Interface Builder, but in earlier versions of Xcode it is not possible.]
In iOS 9 / Xcode 7 this problem will be trivially solved in IB. Simply select the buttons (or whatever it is you want to distribute vertically) and choose Editor > Embed In > Stack View. Then you simply configure the stack view:
Provide constraints that position and size the stack view itself. For example, pin the four edges of the stack view to the four edges of its superview.
Set the stack view's attributes. In this case we want Vertical axis, Fill alignment, Equal Spacing distribution.
That's all! However, you may be curious about how this works, because it is still possible to do the same thing manually in code. A stack view performs distribution, not by inserting spacer views, but by inserting spacer guides. A guide (a UILayoutGuide) is a lightweight object that behaves like a view for purposes of layout constraints, but is not a view and therefore doesn't have to be made invisible and doesn't carry any of the overhead of a view.
To illustrate, I'll do in code what the stack view is doing. Presume we have four views to distribute vertically. We assign them constraints for everything but their distribution:
They all have absolute height constraints
Their left is pinned to the superview's left, and their right is pinned to the superview's right
The top view's top is pinned to the superview's top, and the bottom view's bottom is pinned to the superview's bottom
Now, presume we have references to the four views as views, an array. Then:
let guides = [UILayoutGuide(), UILayoutGuide(), UILayoutGuide()]
for guide in guides {
self.view.addLayoutGuide(guide)
}
NSLayoutConstraint.activateConstraints([
// guide heights are equal
guides[1].heightAnchor.constraintEqualToAnchor(guides[0].heightAnchor),
guides[2].heightAnchor.constraintEqualToAnchor(guides[0].heightAnchor),
// guide widths are arbitrary, let's say 10
guides[0].widthAnchor.constraintEqualToConstant(10),
guides[1].widthAnchor.constraintEqualToConstant(10),
guides[2].widthAnchor.constraintEqualToConstant(10),
// guide left is arbitrary, let's say superview margin
guides[0].leftAnchor.constraintEqualToAnchor(self.view.leftAnchor),
guides[1].leftAnchor.constraintEqualToAnchor(self.view.leftAnchor),
guides[2].leftAnchor.constraintEqualToAnchor(self.view.leftAnchor),
// bottom of each view is top of following guide
views[0].bottomAnchor.constraintEqualToAnchor(guides[0].topAnchor),
views[1].bottomAnchor.constraintEqualToAnchor(guides[1].topAnchor),
views[2].bottomAnchor.constraintEqualToAnchor(guides[2].topAnchor),
// top of each view is bottom of preceding guide
views[1].topAnchor.constraintEqualToAnchor(guides[0].bottomAnchor),
views[2].topAnchor.constraintEqualToAnchor(guides[1].bottomAnchor),
views[3].topAnchor.constraintEqualToAnchor(guides[2].bottomAnchor)
])
(Obviously I could make that code cuter and shorter using loops, but I have deliberately unrolled the loops for clarity, so that you can see the pattern and the technique.)

How to add views in an existing view made in Interface Builder with Auto Layout?

I have a created the following auto layout in Interface Builder:
As you can see I didn't give any fix size to the buttons. I would like to add two button programmatically to get to this result:
Adding the constraints programmatically I know how to do that, at least I know the syntax.
My problem is when to create those buttons?
I create the width constraint based on the width of the button 4. If I do it in viewDidLoad (if I'm not wrong), the auto layout hasn't been set yet so the width (and height) will be wrong.
I thought to do it in viewDidLayoutSubviews but as it's called multiple times when loading the viewController, I get multiple buttons stacked on each other and when I go to landscape more buttons are added..
When should I create those button to have the right sizes?
Auto layout is about rules that hold at all times, not (primarily) about frame sizes at any one moment.
You should not care about getting the frame of button 4 when you set up the constraints for buttons 5 and 6. The constraint that you add for buttons 5 and 6 should refer to button 4's width attribute, not its current width in points. That is, you could create a constraint like this:
NSLayoutConstraint* constraint = [NSLayoutConstraint constraintWithItem:button5 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:button4 attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
constraint.active = YES; // OR: [button5.superview addConstraint:constraint]
That's a constraint that will keep button 5's width the same as button 4's width, even as button 4's width changes. You would do the same for height, and for button 6. Etc.
Put another way, the constraints you create at runtime should be similar to those you would create in IB if you were doing this at design time. It doesn't look to me like you've created explicit, fixed height and width constraints on button 4. You've created relative constraints relating its height and width to other views.
One thing you will have to do: since buttons 2 and 4 have trailing space constraints to the container (or its margins), you will need to remove those constraints when you add buttons 5 and 6. Buttons 2 and 4 would have to have trailing constraints to buttons 5 and 6, respectively, and buttons 5 and 6 would have to have trailing constraints to the container. Actually, you should simplify by getting rid of button 4's trailing constraint to the container and replacing it with a trailing alignment constraint to button 2. Likewise, button 6's trailing edge should be aligned with button 5's, not spaced from the superview's. That way, you only have to remove one constraint (button 2's trailing to superview) and add one (button 5's trailing to superview).
You can create the constraints programmatically in viewDidLoad. If you made an IBOutlet for the buttons, then you can access them and get the size like so:
self.myButton.frame.size.height;
You can use the Autolayout Constraints tool, to make this process easier.

Setting constraints in a UITableView cell, keeping UIButtons appropriately spaced

this is something I have a problem with. I have done a bunch of tutorials on constraints in the interface builder and I understand pinning. My app below uses a UITableView and in the UITableView cells there are 4 UIButtons and 4 UILabels. I want to keep the spacing even like below for larger screen sizes. I guess what I mean is I want the spacing to dynamically increase with the screen size but the size of the images remains the same. If I try pinning the left and right UIButtons to their respective edges of the container this distance will not dynamically increase and there will be a big gap in the centre. How can I set it up so the layout is the same that for the smaller screen size?
You can add some transparent views to the superview, and use them as spacers. I thought this was weird at first, but I noticed that Apple recommends it (it even turns up as a suggestion in one of their amazingly informative NSLayout debug messages)
[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:
#"H:|[spacer1][view1]\
[spacer2(==spacer1)][view2]\
[spacer3(==spacer1)][view3]\
[spacer4(==spacer1)][view4]\
[spacer5(==spacer1)]|"
options:NSLayoutFormatAlignAllTop
metrics:0
views:viewsDictionary]];
The trick, as you see, is to set each spacer's width as equal to the first spacer. Then you will get even distribution of your visible views. In you case, you would add each of your button/image combos to a container view (view1 ... view4).
In the storyboard...
The blue views represent the (transparent) spacer views. This is the setting to get them to resizeable equal widths. You should also set the visible button/imageview combos to fixed widths.
Although this example says 'add two constraints' it actually adds four, all marked as 'equal width to view' - but it seems to do the right thing. You will also want to set the space between each view ('spacing to nearest neighbour') to zero.

Auto layout - 5 buttons in center

I am trying to get into the Auto Layout business, but i find it kinda hard.
I am trying to get 5 image views to display next to each other in the center of the view. They need to resize themselves to expand their height / width as well.
This is how it looks in IB (and kinda the way it needs to look when running the app):
So i have the following constraints:
Added aspect ratio of 1:1 so that they will always be squared
First button is "hugging" the left side of the view, so it will be displayed in the side.
The following 4 buttons have a horizontal spacing to the button next to them
Each button has a constraint to the top and bottom of the screen, so they will get bigger if you resize the screen.
However, when i run it, it looks like this:
And i am just kinda stumped here. What am i doing wrong?
Thanks in advance,
Best Regards - /JBJ
** EDIT **
I added a trailing constraint to the last button. This makes sure they are all within the screen, but is kinda problematic when thinking about the size of it, so that didn't solve it either.
* EDIT EDIT *
Tried removing the top and bottom constraint since they are the ones forcing the size up. Added a vertical center constraint to them all. This won't work either. Displaying them very small (lined up nicely, but too small) and also comes with warnings:
OK, here goes...
Add 5 buttons to the view... No constraints.
Add horizontal space constraints between them all. Also add constraints from the first and last view to the superview. I've also changed the last constraint to 0 (notice the +306 telling me it's currently out of place).
Select all the buttons and (using the add Constraint button) add "Equal Widths" to them all. Notice the orange dotted outline telling me they now all will have equal widths.
Now align them in the vertical centre of the view with this button...
The last thing to do is to go to each one and add the 1:1 aspect ratio. You'll need to add the constraint and then edit it to a 1 ratio.
Make sure you update the frames once you're done to reposition the buttons to their new constraints...
The preview screen shows this working at all different sizes...
For placing them at the centre of the screen vertically use
NSLayoutConstraint *constraintHorizontal = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.superview
attribute:attribute
multiplier:1.0f
constant:0.0f];
For placing them horizontally
Button width = (width of the screen)-(button spacing dimension)- (left distance)- (right distance)/5;
Same for height.
Initial left constraint for the first image view will be left distance.
What I always do in such a situation is just simply think.
How much constraints do I need to 100% define the design? What should I write to tell someone on the phone what it looks like?
In your case this are the following constraints (hope I wont forget one)
They are all squares (equal width height, not a value)
They have equal size, just set it to equal, not to a value
Horizontal spacing between elements and edge, set it to fixed size
Vertically centered
Place 5 buttons vertically and horizontally center in UIView
Select all of them and embed them in a stack view
Change distribution to fill equally.
Adjust spacing in attribute inspector to make space between buttons.
Add leading and trailing space and height constraint to stack view. Also vertically align it.

Autolayout causes small gaps on rotation

My application uses Auto-Layout to create an interface comprising one 2:1 container (fitting the screen size) with two square boxes inside. An example can be seen below (only the left box is visible):
When the device is rotated the code updates the constraints to either position the boxes from left to right (landscape) or top to bottom (portrait). The code works reasonably well, but the interface sometimes ends up like this after rotation:
As you can see, the background of the container is seen in the left and bottom corner; it varies in severity (sometimes it's even more visible).
I've set up a small project that exhibits the issue; it comprises a small view hierarchy inside a storyboard where all constraints are removed at build time.
The actual constraints are created / updated inside ViewController.m and DualVideoView.m.
The constraints seem pretty accurate to me, so I'm not sure why these layout issues happen in the first place.
Update
Removing the view finder (orange frame in the above screenshots) resolves the layout issue; it uses proportional width & height (e.g. width := superview.width * 0.9) to draw an inset square frame. I'm not sure why that should be an issue, though.
Although your vertical constraints are not the issue, I would like to point out that they are missing height constraints for video1/video2:
[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:_video1 attribute:NSLayoutAttributeHeight multiplier:1 constant:0]
[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:_video2 attribute:NSLayoutAttributeHeight multiplier:1 constant:0]
Your horizontal constraints look good, though, so I took your DualVideoView source code and created a minimal application around it (source code at codepad.org) in order to reproduce the problem. Unfortunately the minimal application does not show those gaps that you see, so unless you can provide further details I am reduced to guessing. As usual, the most helpful thing you can do is to show a minimal but complete code example that exhibits the problem - maybe you can start by expanding on the sample app that I linked to above.
The main thing that I can think of is that somewhere along the way a rounding error is creeping in because the video1/video2 views should take up half of the width of their superview.
For instance, what happens if the DualVideoView instance gets an odd width of, say, 1023? Assuming that half points are floored, half the width of 1023 is 511 (511.5 floored). The total width of video1/video2 becomes 1022, therefore leaving a 1 point gap. There are two problems with this assumption, though:
It does not explain the vertical gap since your vertical constraints do not include anything that might lead to a rounding error.
The assumption is wrong, Auto Layout is not flooring (nor ceiling), it is rounding. So half the width of 1023 is 512 (511.5 rounded up), which means that if anything video1/video2 should overlap and there should be no gaps.
Some things that you might check:
Can you still see the gaps if you rotate twice by 180° (i.e. if you rotate back to the original position)? If the gaps disappear, then the problem might not be a rounding error but a faulty positioning calculation that depends on the device/interface orientation.
Can you still see the gaps if you test with a Retina display simulator or device? There might be a difference because on Retina displays Auto Layout is rounding differently (it allows .5 values and rounds on .25 steps). If the gaps are still there and you are testing on the simulator, it might be interesting to measure with the Pixie app to see if the gaps are 1 or 2 pixels wide.
Does it make a difference if you use constraints that align video1/video2 to the edges instead of to the center of their superview (e.g. align the top/left edge of video1 to the top/left edge of its superview)?
UPDATE
In the GitHub sample project I tracked down the problem to the constraints that exist in the storyboard for the small white subview of the video1 view. Specifically, the problem is somehow related to the 0.9 multiplier - if you reset the multplier to its default 1.0 and instead use a constant, for instance -20, then all resizing problems magically go away. I say "magically", because I am utterly baffled as to why a multiplier should make such a difference. If I find more time I will take another jab at this, but right now this would be my recommendation: Use a constant value instead of a multiplier.
One other thing that I noticed is that most (but not all) of your constraints express dependencies in reverse. For instance:
[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.videoContainerView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0],
This constraint expresses that center.x of the VC main view (superview) depends on center.x of the video container view (the subview). I find this a strange way to think about the relationship between superview and subview, I usually think about this just the other way round. Admittedly, the Auto Layout equation solver seems to be able to cope with this, but I would still recommend to write constraints in their natural dependency order. If nothing else, it will help other people to better understand your code.
UPDATE 2
A little bit of additional research:
The layout issue occurs only in the iPhone simulator, but not in the iPad simulator
The layout issue disappears if the proportionally sized view is aligned not with the center but with the top and left edges of its superview (video1)
Examining 1) the main view, 2) the video container view, and 3) the video1 view (which contains the white proportionally sized view) with the debugging aid constraintsAffectingLayoutForAxis never reveals any constraint that involves the proportionally sized view - although removing the view, or aligning it differently, has a demonstrable effect on the layout
Especially the last point leads me to believe that this combination of constraints exposes a bug in the Auto Layout engine. I suggest you file a bug report with Apple using something like the minimal example you posted on GitHub.
If you set up the constraints in Interface Builder you'll get warnings on missing/conflicting constraints.
Also you can simulate rotations, different screen sizes and get ride of that ugly code ;)
For this particular case where your ratio and sizes are pretty much fixed I would rather implement layoutSubviews though.
If the subviews are kept square then they don't need any special adjustment or constrain. Also you can mix manual (layoutSubviews) with auto layout child subviews if needed.

Resources