I’m having bit of a tough time getting my head around auto layout, constraints, priorities, compression and content hugging.
I think I understand it but getting it to play nicely seems to be impossible.
I have this simple view, with 3 buttons and a label.
I want them to be able to adjust to fit the screen when the user orients the screen.
All the buttons are pinned to the leading and trailing superview.
The top label’s top is pinned to the super view. The bottom button’s bottom is pinned to the superview.
There are vertical space constraints between the buttons.
I have tried varying priority levels for the compression of the buttons to get them to squeeze vertically for the horizontal view, but everything disappears off the screen.
Or one of the lower buttons appears above the upper buttons.
I am sure I am doing something quite simple to make these errors.
Would someone be able to offer me their 2 cents on how to fix this?
Many thanks for any help!
Merry xmas!
Adam
UPDATE - after "update all frames"
It seems button 01 disappears when the view is rotated
**UPDATE #02 - almost working but not sure how **
Guys I seem to have managed to get it to work, but i'm not really sure how.
And it's not reliable - if I adjust the constraints between the label and the first button then the oriented view ends up scaling off out of the bounds of the screen.
I would assume that I could adjust that vertical constraint to be able to change that space, but it just makes a mess.
Pin the bottom button to the view controller's bottomLayoutGuide, not the superview. Do this by control-dragging from the bottom button to the Bottom Layout Guide in the scene's Document Outline.
Also, your interface objects are misaligned; that is, the frames don't match the constraints (or the constraints don't match the frames). That's why you see the dashed rectangular outlines. Go to the floating tool bar in the lower-right corner of the storyboard canvas and click on the "Resolve Auto Layout Issues". From there, either select "Update All Frames…" or "Update All Constraints".
I think the default values for compression resistance and content hugging priorities are adequate.
Related
I am trying to learn auto-layout/constraints but for the life of me I cannot figure out a solution to my problem. I have multiple buttons surrounding a 'main' button. I would like the layout to remain the same and the buttons to auto resize depending on the device AND to remain in the center of the superview. I have been reading and trying and have yet to get anywhere. Any help would be greatly appreciated.
At the moment I am not worried about landscape view but if anyone would like to elaborate that would be great.
Any references I could read or videos would be excellent as well.
Thank you kindly
Example Layout:
I can offer some advice.
Firstly, all your views and buttons should have proportional widths and heights relative to the screen size of the device you're using so it will look decent across different devices.
Secondly, apart from the width and height constraints, your middle "main" button will also have a centerX to your UIViewControllers's view as well as a center Y so you can keep it pinned to the middle. This is important because the other 6 buttons will be pinned relative to this main button.
Let's start with the other buttons, specifically the one above the main button and the one below the main button. These two buttons also have width and height constraints. Now, horizontally, you have two options - since these guys are always in the middle of the screen, you can align their centerX's to the main button, or to the UIViewController's view itself - it doesn't matter as long as it's in the middle. Lastly, we have to consider the vertical distance from the main button. These distances should be proportional to the total height of the screen so it will look good across all devices. You'll be adding a constraint from the bottom of the top button to the top of the middle button. For the bottom button, you'll be adding a constraint from the top of the bottom button to the bottom of the middle button.
Now for the other 4 buttons, apart from the height and width constraints that they will have, you should constrain the horizontal constraints to the top and bottom middle buttons, and then constrain vertically to the main button.
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.)
Hey, I'd like to obtain what you see in the pictures: in Compact Height mode (landscape iphone) both the red and the blue view have to take all screen vertically and half the screen horizontally. In Compact Width mode (portrait iphone)they have to take all the screen horizontally and half the screen vertically. Space between views should be same size in both modes.
I used to think I have to use size classes and auto-layout constraints, but everything I tried failed miserably.
Maybe I have to use a UICollectionView and change flow direction based on orientation (if that is even possible)?
A collection view is probably overkill, because you don't want scrolling and that's the whole point of a collection view--by the time you do the sizing to stop it you'll have done all the work necessary to set a non-scrolling layout.
This is possible with Size Classes in IB. First, In general you will probably find it helpful to name the views in the Document Outline on the left in IB. You will also want to use this outline rather than try to grab the tiny constraint H-lines.
Set up all the constraints except 1) constraints linking the
OrangeView and BlueView to each other, 2) the constraints linking
the OrangeView to the top and left(leading), and 3) The constraints
linking the BlueView to the bottom and right (trailing).
Change the size class setting at the bottom to w-Compact and
h-Any in the funky box system. Now we're designing for a compact width, so views on top of each other.
Create a constraints for vertical space for BlueView.bottom to
OrangeView.Top. Also create constraint for OrangeView to
superview.leading (or ledaing,margin) and BlueView to
superview.trailing.margin. If you select any one of these constraints and look at the Size Inspector on the right (the ruler) you should see an "installed" checkbox not selected, and below that a w-Compact h-Any and another installed box, this one selected.
Now, while keeping the constraint selected just to see what happens, change the sizeClass selector at the bottom to w-Regular h-Any. Notice that in the Document Outline to the left, it should get grayed out.
Now we are designing for regular, so side-to-side. Add constraints linking the views for horizontal space, BlueView.trailing to OrangeView.leading. Also link OrangeView.top to the superview.top or top aligned to BlueView.top, and same for bottoms. You can manually edit the frame first; if not, IB will automatically fill in the wrong values, so edit these after you create them, and verify they are w-Regular and h-Any. With the ViewController selected, select "update frames" and the views should snap to their expected shape for the size class.
Let us know if this works for you or if it was unclear. Good luck!
I have the most basic set up possible. See pic 1:
Believe it or not this is my first project using AutoLayout, I have created everything prior programatically. This basic set up is literally a UIWebView with 1 custom UIView positioned at the bottom. Previously I was using a tool bar that handled everything for me and had no issues with constraints whatsoever. However, the tool bar created discrepancies for event handling when adding a UILongPressGesture to the subview of the UIBarButtonItem so I decided to convert the tool bar to a UIView (Even inserting a UIView into a tool bar, it naturally converts to a button item) for easier handling. But run-time, the view gets pushed off screen by half of the UIView size (48px) See Pic 2. Then when I add buttons, it just gets worse:
I have reviewed the documents and the support HERE with no results. I've spent about 24 hours in total researching it and learned a lot, so my efforts aren't in vein. I KNOW by 'Adding Missing Constraints', the constraints are just recommendations based on the current set up, they aren't concrete in all cases, so I did try to create my own with control drag after reviewing the documents but my set up concluded with no different results, and exponentially more sloppy. So I decided to include the populated constraints by Xcode below :
UIWebView Constraints
Custom UIView (toolBar) Constraints
Any solid starting point recommendations? Does Intrinsic Size have anything to do with it?
EDIT : WORKING CONSTRAINTS I didn't realize you could simply omit a constraint. It seems the culprit was adding one to the top layout guide.
Just for answerer #Matt :
Constant 0 result : there are small gaps at edges
-16 for leading space/trailing space results as a true toolbar emulation in my case with no outstanding warnings or issues. Thanks
Let's talk about the view at the bottom of your interface and how you would use auto layout to position and size it the way a toolbar would be positioned and sized.
To use auto layout, you need to supply sufficient info to determine both position and size. This view is a subview of the view controller's main view. The main view will be resized depending on the screen, so we want to use auto layout to resize the subview ("toolbar") as well. This is what auto layout is for!
So constrain subview leading edge to the leading edge of the superview, and constrain subview trailing edge to the trailing edge of the superview, both with a constant of 0. Now the right and left edges match the superview!
That takes care of horizontal position and size.
Now let's talk about vertical position. The position should be the bottom. So constrain subview bottom edge to the bottom layout guide of the view controller, again with a constant of 0. Now the bottom of the view is at the bottom!
The only thing we don't know is the top of the subview. This, in our situation, is the same as knowing its height. So give the subview a height constraint, set its constant to a reasonable value like 40, and you're done.
I've got a scroll view contained directly inside the content view of a view controller, at full size in both dimensions. The top, bottom, leading and trailing space constraints from the scroll view to its superview (horizontal) and the layout guides (vertical) are all set to 0. The VC is eventually meant to be nested as a child view controller in one or two places. I'm using a Storyboard.
I've placed a number of elements inside the scroll view and constrained them to it, but I'm seeing various kinds of strange behavior. Below is a screenshot with all the subviews of the scroll view selected to display their constraints. The scroll view's four constraints to the top level view are not visible in it. The view controller has been set to Freeform size, with its top level view (and, accordingly, the scroll view's content view) 616 pts high, guaranteeing that scrolling will be necessary at runtime.
Before analyzing the screenshot, here's a list of things that I'm trying to achieve.
The vertical spacing between elements is set by the designer and fixed. (BTW, none of the vertical constraints, text styles etc. in this wireframe are final yet; the whole image is for illustrative purposes only.)
All the labels (except the topmost one) should start at their intrinsic size, expand up to the width of the scroll view (minus the standard HIG horizontal space of 20 pts on both sides).
Buttons are unlikely to be much bigger than this, but in case of localization surprises, we want them to behave just like the labels. (There's an extra vertical ≥ constraint on "Another Button"; it's irrelevant to this question.)
The web view has a fixed height, and its width should be determined by the width of the scroll view; standard 20 pt horizontal space on both sides.
The text views have a minimum height (67 pts here), but they should expand vertically if the contained text is too big to fit. None of them are editable or scrollable. Like the web view, they're horizontally spaced the standard 20 pts apart from the leading and trailing edges.
As you can see, none of the elements have explicit width constraints. The whole thing relies on the leading and trailing space constraints between the elements and the scroll view. The layout, in my mind, would somewhat gracefully work on hypothetical wider-than-320 pt iPhones of the future without changes to the constraints. It would also work after rotating to landscape orientation (it might look a bit silly, but it would work).
I'll go through the points step by step, referring to the screenshot where necessary.
1: This works, nothing out of the ordinary here.
2: The leading constraints of the labels are all simple Equal 20 pt standard spaces. The trailing constraints are Greater Than or Equal 20 pt standard, ostensibly to allow them to grow to be scrollView.frame.size.width-40 wide, but no wider.
3: Same as 2.
4 and 5: Here's where it gets interesting. The web view and the text views are all listed as Misplaced Views, with IB saying their frames will be different at runtime. The orange dashed borders denoting the correct frames only reach horizontally as far as the longest element with a Greater Than Or Equal trailing constraint; here, it's "A Button With A Long Title", whose right edge is where the dashed border edges end.
Constructing this set of views and their constraints, I expected to have some trouble. I knew it would be tricky to have UITextViews that grow vertically taller than the ≥ 67 height defined here, perhaps only possible through code. Getting the labels and buttons to work as specified above through IB alone seemed a bit iffy, too.
What I didn't expect was the web and text views' reported correct frame being only as wide as the widest label or button. It seems that with this setup, the scroll view won't actually be 320 pts wide, but rather only as wide as necessary to fit the longest element and its spacing, and the web and text views are expected to comply. Given that the scroll view is firmly constrained on all sides to the top level view, which is set to be 320 pts wide, I have no idea why this is. SOMETHING must obviously define the initial width of the scroll view, but why aren't the constraints I've made from the scroll view to the top level view doing that?
Given the specifications above for this set of views, what do I need to change to make it happen?
This case demonstrates the fact that I truly do not properly understand Auto Layout yet, and I hope that the answers will enlighten me about many of its crucial aspects.
With respect to Xcode's warnings about misplaced views, select the view controller in storyboard, tap the "Resolve Auto Layout Issues" button in the lower-right-hand corner of the canvas (it looks like a Tie Fighter from Star Wars), then select "Update All Frames in View Controller". This forces all of your views to reflect their constraints.
Using Auto Layout with UIScrollView is a different animal; so much so that Apple felt it necessary to release a Technical Note on the issue: https://developer.apple.com/library/ios/technotes/tn2154/_index.html
When you connect all those constraints between subviews and the inside walls of a scroll view, the result is probably not what you expect. When you pin a subview to the sides of a scroll view, you are not in fact determining the subview's position relative to the scroll view. Instead, you are determining the scroll view's contentSize. This is weird.
You are using Apple's so-called pure Auto Layout approach from the Technical Note. With this approach, the subviews' constraints must dictate the scroll view's contentSize.
Let's take just one of your subviews and ignore all the rest, say one of the text views. And let's only think about that text view along the horizontal axis. If you wanted the text view to be constrained by the width of the scroll view without any horizontal scrolling, you would need to install a fixed-width constraint on the text view that was exactly the width of the scroll view's bounds minus the spacers. After doing this, the content size width would be the sum of the left spacer, the width of the text view, and the right spacer.
Unfortunately, you cannot install a constraint that establishes a relationship between the width of the text view and the width of the scroll view's bounds. And that's really too bad.
I don't actually recommend installing a fixed-width constraint on the text view. Instead, I would start over and use Apple's "mixed approach" from the Technical Note.
With the mixed approach, the subviews' constraints don't determine the scroll view's contentSize. Instead, you must explicitly set the scroll view's contentSize and the frame of a container view (i.e., a UIView content view).
Let's go back to that UITextView and the horizontal axis. Using the mixed approach, you could leave the constraints for the text view as they are (i.e., no fixed-width constraint). You could explicitly set the width of the scroll view's contentSize and the width of content view's frame as early as viewDidLoad. You could explicitly set these values to self.view.bounds.size.width because your scroll view hugs the sides of the main view.
To implement the mixed approach, you will have to instantiate the content view (UIView) in code and not set its translatesAutoresizingMaskIntoConstraints property to NO. By extension, you'll probably need to create your constraints for all those subviews in code as well (I don't know of any way around this). The visual formatting strings are tedious and repetitive, but they're actually easier to work with than constraints created in IB when configuring complex layouts (your layout is sufficiently complex).
I used the mixed approach to solve a SO challenge here: https://stackoverflow.com/a/21770179/1239263. Unfortunately, the solution hasn't been vetted yet. It's always possible that I'm nuts and I don't know what I'm talking about.