Essentials • iOS, OS X • 52:32
Learn advanced techniques for creating sophisticated and dynamic layouts in OS X and iOS apps using the auto layout system. You'll receive tons of useful examples, see "how we did that," and learn valuable best practices and debugging techniques. If you are new to auto layout, you might want to attend the Introduction to Auto Layout session first.
Speaker: Peter Ammon
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning. Welcome to Best Practices for Mastering Auto Layout. I haven't even done anything yet. It's for OS X and iOS. My name is Peter Ammon. I'm an engineer on the AppKit team. will be showing you Auto Layout, which is a way to make your layouts simpler to write, simpler to modify, and easier to understand. The point of this session is to raise you from the level of someone with some Auto Layout familiarity to a true Auto Layout master.
As you know, Auto Layout was a feature we introduced in Mac OS X Lion. We're very happy that we could bring it to you in iOS 6. : This session will cover both, and the reason it can cover both, AppKit and iOS, is because they have identical APIs. Almost identical APIs. The most significant difference, of course, is view. So when I say "view," you should think NSView if you're working on AppKit and UIView if you're working on iOS 6. And any other differences I'll call out when we reach those parts.
So we'll just do a brief review. Hopefully this is stuff you already know. Auto Layout introduces just one new class, and it's layout constraint. A constraint expresses a geometric property of a view. For example, this view's width is 120 points. It could also express relationships between views. For example, foo's width is 120, bar's width is equal to foo's width. Now bar has a width of 120 as well. and relationships have a coefficient and a constant. So we could say that foo's width is twice bar's width minus 20, but we could not, for example, say that foo's width is the square root of bar's width.
Now, constraints can be equalities or inequalities, so we can say foo's width is at least 120. and constraints have priorities. So, foo's width is 120 with a low priority and 75 with a high priority. The 75 priority will dominate. It doesn't try to average between them. It just wins out entirely. And foo will have a width of 75.
So you can create constraints in Interface Builder with the visual format language and with the primitive API. And when you're deciding how to create a constraint, prefer to create them in that order because that's the easiest order. It takes the least work and it's the least error prone.
So that concludes the background review. In this session, we're going to cover thinking and constraints. How do you adjust your thinking for an Auto Layout world? We'll talk about debugging constraint-based layouts. What happens when something goes wrong? And lastly, we'll really start to leverage constraints in Auto Layout and get some mileage out of this new feature.
So thinking and constraints. Now, you can use Auto Layout just like you've been using springs and struts by specifying constraints for the offset and width. But if you are able to shift your thinking to a declarative type of layout, it will be far easier. And we'll see an example of that right now.
Let's say you have like a keyboard type view. You have a bunch of keys that are all the same width and height, and they're in this container view, and there's some padding between the keys, and the container view has an overall width. So how would you implement this layout with springs and struts? Well, you would start by computing some sort of expression for the width. So you'd say that the width of a key is equal to the total width divided by the number of keys minus some fix-up for the padding.
And then for each key, you might compute an offset, which is going to be the key index times the key width plus some sort of fix-up for the padding. And I've written plenty of code like this. You probably have as well. And it works, but it's not easy to tell by looking at the code what it's actually doing. It's hard to revisit this and say, "Oh, this is laying out a keyboard-type view." So how might you address this with Auto Layout?
You can start by establishing width constraints for every key. And we could use the same expression. So you'd say that the key width is the overall container width divided by the number of keys minus some padding. Now, for the offset, instead of calculating the offset, you could just string the views together. So you could say, "Oh, Q's offset is equal to its container's left edge plus padding. W's offset is equal to Q's max X edge plus padding," and et cetera.
And you string all the views together. So this is better because these are relationships. So if the container width changes, you don't have to do this work again. It will do it for you automatically. The width of the key is related to the width of the container. But this is still not great, right? There's still this sort of confusing expression there. It's hard to tell what that's doing. So here's how an Auto Layout master would approach the same problem.
If you listen carefully, I told you at the beginning exactly what constraints we need. Every key has the same width. So let's just establish constraints that say Q's width is equal to W's width, W's width is equal to E's width, etc. And the last step is to add a constraint at the right edge, binding the rightmost key to the right edge of the container.
When you add that last constraint, it's going to unfold all the views like an accordion, and you'll get exactly this layout. And notice that there's no real calculation here. It's just very simple relationships, which I think is a hallmark. When you reach that state, you know you found a good layout.
Also notice there's no rounding. We didn't talk about what happens if the number of keys doesn't evenly divide the amount of space available. But with Auto Layout, it will handle that for you. So I'm going to give you an example, or a demo rather, of a layout very much like this in a real app.
This is an app called Opposite Subtract. It's just a very simple game. The point is you have a bunch of letters that -- by the way, everything you see here is done entirely with Auto Layout, naturally. And the idea of the game is to find the word which is the opposite of the word in the upper left in as little time as possible. And every time -- every few seconds, your score goes up. And when you get the correct word, your score goes down, you know, because opposites. So the opposite of warm is cool.
So I got that right. Explodes. The opposite of big is little. T, T. You can drag these around a little. Little. I got that right and it explodes. So you see that the view at the bottom here -- I'm calling it the rack view -- has a layout very similar to the key view we saw before in the slides.
Notice that when I start adding views, it's going to start shrinking to accommodate the extra space, but eventually it's going to not let it get too small, and it's just going to start clipping off the right side. Like that. You can add them back. You'll also notice that they will try to grow with the view-- let me get rid of some more.
The keys will try to grow with the view and shrink with it, but they also have a minimum size, and they start clipping, and a maximum size. And when they reach the maximum size, they stay centered. So it looks like a very simple view, but there's actually a lot of sort of sophisticated edge cases and aspects of the layout going on here. So allow me to show you how I implemented that.
The pile up here, I'm calling it the pile, and the rack down here are both subclasses of a view called LetterContainer. : Letter container has a method, add letter view. And when you add a letter view to it, the top or the bottom, it's going to add it as a sub view, add an object to its own internal store.
And then it's going to remove all the constraints it's established for all of its letter views, get rid of them, and call set these update constraints. Now, the next turn of the run loop, constraints will be updated. I'll show you the constraints we actually add. So we pinned the first view to the left edge. That's why the P is over on the left.
We pin the last view to the right edge weakly, so we add objects from array with constraints that say the last view is pinned to the right edge of a super view with a padding of at least 20 and a priority of 450, which is a low priority.
We make a constraint that says the first letter view is the same height as the container, but weakly so. So that's why it tries to be the same height as the rack. We do that by saying that the height is equal to the height of the container times 1 plus 20. We do that with a weak priority as well.
Here we center the letter vertically in the container. So we say that center Y is equal to the center Y of the container view. And here's where we establish constraints that make all the views equal width, like we saw in the slides. The other thing I wanted to show you, we didn't say everything about the height of the views, but in the letter view itself, when it instantiates itself, it establishes a constraint that says, "My width is equal to my height," and also establishes the min width and the max width. So this idea that different aspects of the program can be responsible for the layout is something we're going to be revisiting.
So the layout became distributed in this app. Another component of the app responsible for the layout of these views is the component that does the animation. I think of it -- if you can decompose your layouts into components where each component can own an aspect of the layout, that's how you can get a really clean layout.
I think of it kind of like retain, release, or arc, where the lifetime of an object is sort of a global problem. It's kind of a hard problem, but each owner of an object can contribute to that lifetime, and the overall lifetime is established by looking at all the interested parties. So we saw that the Q view said, "I'm square, my width is equal to my height," and its super view said, "You're the same height as me, and also you're vertically centered in me, and that's sufficient to establish the layout."
So let's say you've figured out the layout you want, but now you want to convert from springs and struts to this new layout. So how do you go about converting an existing app? Well, the first step is you want to plan your attack. Do you want to just convert part of a view or one view?
Well, you can use Auto Layout just in the part you need it, which is convenient, but you can also, if you do a full conversion, it will pay off later. The next step is to turn on Auto Layout in your NIBs. That's easy to do. It's in the leftmost tab, the NIB inspector. There's a check box, use Auto Layout. There it is. It's a per nib property, not a per window or per view property.
When you check the checkbox, Interface Builder will create the constraints that reflect your existing layout. But what it will not know how to do is how that layout should change if your screen rotates or if you resize the window. So you might want to inspect the constraints Interface Builder has made and then add to or modify them.
The next step is to find every place where you create a view and turn off auto resizing mask translation. This is something you heard about in the first session. You have to call setTranslatesAutoResizingMask and the constraints "no," and that means I want to use only Auto Layout for this view. And if you forget to do this, you'll figure it out pretty quickly because you are likely to get unsatisfiable constraint errors. We'll see some of those later.
Now you want to identify places where your existing code performs layout. So layout subviews and iOS is a good candidate, or any sort of call to set frame or set frame size or set frame origin. These are all places where your existing code is doing layout, and they all have to go. But what do you put there instead?
Well, what you don't want to do is just take the code and try to replicate what it does. Instead, you want to try to understand the layout that the existing code is an implementation of and recreate that with Auto Layout. And the hope is that by using Auto Layout, this will become simpler. The idea of Auto Layout is to simplify making sophisticated layouts.
So it's great if you can replace it with nothing. If you're working around a limitation of springs and struts, maybe Auto Layout can handle that directly. Or if the existing code is implementing a relationship that Auto Layout can express directly, then you can just use Auto Layout to express that. But if you have to add some constraints, well, then naturally you should do so.
So you want to think about which component should own each constraint. As we saw, the letter view owns its aspect ratio, but its super view owns its positioning and its height. And you want to consider centralizing all this work in update constraints. That will keep your code focused, all your Auto Layout stuff will be in one place. We saw that with the letter rack view before.
And of course, the last step is to test it. Make sure you have the layout you want. If you see log messages, you may have unsatisfiability. If you see views that jump or disappear, you may have ambiguity. When you see those issues, you just need to fix them.
So how do you fix them? That brings me to debugging. So what can go wrong in an Auto Layout app? Well, one problem is that you have constraints that don't provide enough information, and that's called ambiguity. Sort of the opposite problem is that you have constraints that provide conflicting information, and that's unsatisfiability. And a third type of problem is that constraints that are not ambiguous and are satisfiable, but that are satisfied in ways you may not expect. We'll see examples of all of these.
And keep in mind that when you use Interface Builder, it will address these problems for you. It will not let you create unsatisfiable constraints, and it will not let you create an ambiguous layout. So this is just one of many reasons why you should prefer Interface Builder when doing Auto Layout. And keep in mind that you might think, "I can't use Interface Builder because I'm going to need to add or remove a constraint, or I need to adjust the constraints constant." But you still can. You can reference the constraint with an outlet. So, ambiguity.
Ambiguity happens when there's multiple layouts that all satisfy the constraints equally well. So, for example, in our key view in the slides, maybe the layout was this or maybe the layout is that. We didn't say anything about the vertical positioning or the vertical size of any of the views. And a common symptom is that your views will seem to jump between different layouts that all satisfy the constraints, or they'll just disappear entirely, which usually means they've jumped to size zero.
When this happens, it usually means you need to add more constraints. Just like a rect has four properties, X origin, Y origin, width, and height, you need to specify four properties of every view. But they don't have to be those same four properties. You could specify the center X and the width or the min X or max X or max X and width. Any combination will work.
And inequalities are usually not enough. So just because I said that this view's width is at least 20, that doesn't mean it's going to try to be exactly 20. It's just as happy being 20 as it is 40 or a million. So inequalities cannot fully specify the layout in most cases.
Now, sometimes ambiguity can come about because you have priorities that are equal, and it does not know which constraint should satisfy. For example, if we say that Views Width is 24 with a priority of 500, and Views Width is at least 30 with the same priority, well, it can't satisfy both because 24 is not at least 30. And they have equal priorities, so it doesn't know which constraint to prefer. This is a case where you have ambiguity.
So if we were to raise the priority of the inequality to, say, 525, it still can't satisfy both, but now it knows which one to prefer, so it's going to satisfy the inequality first. After that, it's going to try to satisfy the equality as close as possible. So there is no ambiguity here, and the resulting width is going to be 30, because 30 is the number which is at least 30 and closer to 24 than any other number.
So if you want to know if you have ambiguous layout, you can just ask your view, "has ambiguous layout?" And if you want to know what's ambiguous about it, you can call Exercise Ambiguity in Layout, and what that will do, will change the frame to another layout that satisfies the constraints equally well.
You can also call Visualize Constraints on OS X only. This will give you sort of a purple overlay window, which will tell you if you have ambiguity and allow you to exercise it in that way. And by the way, these functions are really useful for debugging, but they're only for debugging. You should never have a need to call them in production code.
So that covers ambiguity. Let's talk about the other problem, unsatisfiability. A layout is unsatisfiable when there's no layout that can satisfy all the required constraints, and the keyword there is "required." Only required constraints can contribute to unsatisfiability. And keep in mind that constraints are required by default, so if you create a constraint with the base API or the visual format language and you don't specify a priority, it's going to be required.
And keep in mind that sizes are required to be at least zero, even if you never establish a constraint. So if it's possible that the layout could be satisfied with a width of negative 10, that's not something that will allow -- that will be an unsatisfiability case. Now, when you get an unsatisfiable layout, you'll get an exception immediately. It reports it in the very same call where you call add constraint to the view.
But ambiguity is not reported. It's just something which happens. So we can use it to our advantage. We can temporarily tolerate ambiguity. You saw in the letter rack view that when we added a new view, we removed the existing constraints. That forced the layout ambiguous, but that's okay because we were about to reestablish those constraints the next turn of the run loop. The reason we removed the constraints is to avoid any risk of even transient unsatisfiability.
Now, what do you do when you don't see anything? This is a common problem with Auto Layout. So it's possible your views are hidden or they don't exist at all, and it's also possible that they do exist, but they have a zero width or zero height or both. So you should never call set frame in Auto Layout, but you can ask a view for its frame. You know, where is it? What's its width and height? What's its offset?
You can also ask what constraints are making the view that size. So this is view constraints affecting layout for orientation on OS X and constraints affecting layout for axis on iOS. And you pass horizontal or vertical. And if you call this in the debugger, keep in mind that horizontal is zero and vertical is one, so you don't have to type out those long constants. And it's also possible that the layout is ambiguous, and you want to check if it has ambiguous layout, and you can exercise it to see.
And keep in mind that some layouts are only satisfiable at zero size. So you might say that foo's width is twice bar's width, and bar's width is three times foo's width. You might be thinking, "Oh, there's no way that can be satisfied." But in fact, it can be satisfied with everything equal to zero. So this is another reason why you might have views that just don't appear. So let's see a demo of all of these cases. So let's say we're working on this app for a while. I'm going to switch to a different branch.
[Transcript missing]
What it's going to do is it's going to take the first view. Here we're using the new Objective-C array syntax to get our first letter view. And we're just going to log out its frame. And then for every view in our pile, We're going to ask if it has ambiguous layout, and if it does, we're going to call it Exercise Ambiguity in Layout. So let's try that. Choose debug. Oh, my views appear. They have stupidly tall frames. And I see that, oh, the initial frame height was zero. So this tells me that the height is ambiguous, and we need to think about who should be responsible for setting the height.
I could have done something very similar by calling visualize constraints, and here we could pass an array of constraints to visualize, but I'm just passing an empty array. The reason to do that is because it will Show me this purple overlay window, which tells me layout is ambiguous here, and I can click this button to exercise ambiguity, which doesn't actually seem to do anything. Sometimes multiple layouts all have the same visual look, so this would be a case like that, which is why I called exercise ambiguity on every view, every one of my letter tiles.
So in this case, who's responsible for setting the height? Well, the height should be equal to the width of every letter. So I'm going to try to remember where I set that. Oh, I set that on the letter view itself in init. That's part of the aspect ratio, and someone had commented that out. So I remove the comments. I run that and I'm back in business. So that's an example of ambiguity.
I'm going to switch to a different branch. So this time, when I run it, I do see my views, but they're all pinned to the top, and they don't have the layout I want. And in addition, there is this enormous amount of text which has been output to the log.
So this is a case of unsatisfiability. You can tell because it says "unable to simultaneously satisfy constraints." And we'll go into these log messages in detail because it's very important that we be able to read and understand these. But for now, we can just look at what they're saying. We see that letter view A, so the A tile, its center is equal to the pile's center plus 56.
We see it's also equal to the pile center plus 172. So clearly we cannot satisfy both of these constraints. So we have unsatisfiability. So I suspect that the-- The positioning here is due to the letter pile setting the offsets of the letter views. So I go to the letter pile.
And I say, "Okay, here's the center X equal to my center X." Here we have the center x again equal to my center x. Oh, but this time we're using the constant y instead of x. So this looks like a copy and paste error. So this is another illustration of why using Interface Builder or the visual format language would have avoided this error entirely.
So I'm going to say the center y is equal to my center y plus the location and pile. : I still have some ambiguity, which I'm not going to try to debug right now, but we can see we certainly have an improvement. Letters are no longer positioned in crazy places.
Okay, in the last case, I'm going to start up the app, and everything seems fine at first, but when I click a view, uh-oh, my window suddenly shrunk to some crazy width. What happened here? And if I add more views, it's going to change along with it. I can resize it, but I can't resize it very much.
So this is a case where we have constraints. It's not unsatisfiable, and it's not ambiguous, but it's layout satisfied in a way we don't want. So when you see something like this, a good thought is that some constraint has a higher priority than it should. And in this case, it has a higher priority than the priority with which the window holds its width, which is why it can shrink the window.
So in this case, because it only happened when I added the view to the letter rack, I can -- is the reason that is probably related to one of the constraints the letter rack adds, and I see that Oh, the constraint pinning the last view to the right edge has a padding of 20, but it doesn't have a priority.
So that means it's required, which is a much higher priority, of course, than what the window holds its width. So I'm going to first make this an inequality, and I'm going to make it a lower priority than 500, which is the window holding priority. And this time when I run that, the views start out and they no longer shrink the window. So those are some examples of how you would debug problems you can encounter with Auto Layout.
We saw with the unsatisfiability case a very long log message, and you'll probably encounter this at some point. and it's intimidating at first, right? It's like a wall of text that comes at you. And when you see this, you don't want to panic and we'll break it down for you right now.
So the first thing to notice is this set the user default, NS constraint based layout visualize mutually exclusive constraints, you set this to yes, the instant constraints become unsatisfiable, it will open up that purple overlay window and it will call visualize constraints and you can actually inspect the constraints visually. So this is a good thing to have set on your application all the time when you're debugging.
Also notice obc exception throw. When an exception is thrown, the second layout becomes unsatisfiable. Now, the exception is immediately caught, so it does not propagate through your code, but this is still a very useful fact to know because it allows you to put a breakpoint there and see the backtrace of, "Oh, here's where I added that first bad constraint."
Now, this is telling you what it's doing to resolve the conflict, which constraint it's allowing to win, and this is usually not that interesting. The real important information is the array of unsatisfiable constraints at the top, unable to simultaneously satisfy constraints. So let's zoom in on one of these. And there's three parts to a constraint message.
At the top, we have the address of the constraint. So if you reference constraints, you create them, you can ask, "Is this my constraint or someone else's?" At the bottom, we have a mapping from identifier to view. So if you set an identifier for your view, it will make your log messages easier to read. You set the identifier in Interface Builder right here under the View Inspector. There it is. So this is a good thing to do for all your views. Instead of seeing a long hex address, you can just see the name of the view.
The most important part, of course, is the meat of the constraint itself, what relationship it expresses. This has the view's identifier, and it has the property of the view, the attribute, the relation, the view it's related to, and then the constant. So it was very useful to translate this from sort of code into English. So we would say, "Oh, the letter views center should be 11 points to the left of the pile center."
Now, for constraints that can be expressed with a visual format syntax, it will show that syntax in the log messages. So for example, in this case, we say that Views Width is 250. And again, it's really useful to translate this into English. Here's another case. We say that the view's left or right edge is at least 50 points from another view's left edge.
And here's a very important log message you'll see. It looks like this. It's a little different. The first thing to notice is that it's not NSLayoutConstraint, it's NSAutoResizingMaskLayoutConstraint. And this tells you right away that Translate's AutoResizingMaskIndicConstraints is on for this view. So if you didn't mean to leave that on, you know right away how to fix this, locate the view, and turn that property off. Next, you'll see the auto resizing mask itself. So one auto resizing mask actually generates multiple constraints. So this tells you which of the many constraints from this mask this particular constraint is. And lastly, we have the familiar visual format language. This view's height is 50.
So here's a longest example and the last example we'll look at. What can we tell from this constraint? Well, we see that it's an auto resizing mask constraint. Here's this auto resizing mask. We see that it's a horizontal constraint, because H means horizontal, V means vertical. And we see that there's 200 points between this view on the right and what? Well, the vertical bar means super view.
So 200 points between the view and the super view. And the super view we see below is an instance of flip view. That's its description. So to put it all into English, This view's left edge is 200 points from that of a super view, which is a flip view.
And by saying it in English, it's often very obvious why the constraints are not simultaneously satisfiable. So that concludes debugging. Hopefully by now you've got a fully debugged layout and you're ready to start really leveraging it and getting some mileage out of the feature. So one very common task in OS X and iOS is animation. How do you animate layout changes?
Well, one approach is to apply the new layout and let Core Animation handle it. Just like you might call Set Frame, you can just apply the layout and it will animate to the new position. And this is very fast, but it has the disadvantage of perhaps transiently violating constraints because it just interpolates from the old position to the new position without regard for constraints. You can also animate the constraints directly. And this is pretty fast, and it produces the correct layout at every frame. So what do I mean by this transient violation or correct layout?
So say you have a view like this with a purple view inside and a blue view on the outside. And the purple view wants to have a width of 100, but weakly so, and it wants to have a padding of at least 20 on its left and right side, and that's required. So as the view shrinks, initially the purple view is going to maintain its width, but as its outer view shrinks, eventually the purple view is going to start shrinking with it as well.
So if you were to animate this change using Auto Layout, that's exactly what you'd see. Every step of the animation would be as if you had resized the window point by point. But with core animation, you'll see something like this, where the purple view will have interpolated from the start position to the end position at the same time as the blue view, and it will not be exactly the same. So when you're considering how to animate, consider whether you care about this sort of transient violation or not, and then choose the API appropriately.
So let's say you're satisfied with core animation, you find it's easy to use, you're familiar with it. How do you do animation with Auto Layout? Well, you want to set up your new constraints, and then within an animation block, you call layout if needed or layout subtree if needed, depending on your OS. So here it is with NSView. You call runAnimationGroup. You'll set the duration. You'll set allowsImplicitAnimation, and you call layoutSubtree if needed. In UIView, it's a little simpler. You animate with duration, and you call layout if needed.
Now, maybe you do care about the transit violation or you want to use Auto Layout for your animation as well. How do you go about doing that? Well, most of NSLayout constraint is immutable. You cannot change it once it's been created. Except for one property, which is called constant, ironically. The constant can be modified after creation.
And when you modify the constant, it is very efficient because it doesn't have to recompute a whole new layout. It can just use the work that's already done and just tweak it a little bit to reflect the constant change. So this is efficient. So you could modify this on an NS timer.
For example, every tick of a timer, you add 10 to the constant of a constraint. Or you could use the animator proxy on OS X only. So you could say, for example, constraint.animator.constant = 10, and it's going to step from its current value to the new value using the animator proxy. : So let's see a demo of these different animation approaches.
So there's two separate animations in this app. Well, there's a couple, but one of them is when you click a letter, it flies off to the right, and then it flies into the rack. And those are separate animations. So let me show you how I implemented those. In the application delegate animation, We're going to move letter from pile to the rack. So the motion out of the pile view is done by animating an NSLayout constraint, not using core animation. So we start by computing a constraint that expresses its existing layout. Here's that constraint. So we say that it's actually flush against the right side.
[Transcript missing]
So then we add the constraint to the letter pile. We run the animation group. We set the duration to something that we like. We set the timing function to something we like. And then we call the animation constraint animator. We set the constant to zero. So initially the constant is something large and negative, so the view is a negative distance from the right side. It's inside the view. And then it's going to be pushed towards the right side of the view.
So once that's done in the completion handler, this is the animation where all the letter flies in. And by the way, you might have other views animating in there as well. You see that if I start adding more views, all the views are going to animate together. So we're going to remove the constraint that pushes outside of the pile, remove the letter view from the pile. : We're going to run an animation where all we do is call layout subtree if needed. And this will apply the new layout and it will animate from its current layout to the new layout. So this is very little work to do an animation.
That covers animation. Let's shift gears to talk about writing a custom control that you want to integrate with Auto Layout. How do you go about doing that? Well, there's a couple concepts you should be familiar with because they're very important. And the first is alignment wrecks. So we've been talking about constraints like they've been operating on the view's frames, but that's actually not quite true. They operate on the alignment wreck. And you can think of the alignment wreck as the content area.
So if the user sees a button, you know, "push me," that's good, but we don't know what the frame is, right? The frame could have a very small padding or no padding or a lot of padding. But in a sense, you don't really care as Auto Layout. You want to position the content, not its logical frame. So again, the frame is that logical rect, and the alignment rect is the rect within that which contains the actual content.
Here's another example of alignment, Rex. Say you have a text and you have an image and you want to align the tops and bottoms of the text and the image. Now let's say you want to apply a badge to the image, like that. That badge isn't really part of the image. It should maybe overhang outside of it, but it should not contribute to the alignment at all. So in that case, the alignment rect is still the gear that we saw before and excludes the badge.
So you can convert between alignment rects and frames. There's a method, alignment rect for frame and frame for alignment rect. And that's all. The other important concept to know is the intrinsic content size. This is something you encountered a little bit in the introductory session. Now, many of you are very happy at any size. If you just make an arbitrary view, it doesn't really care what size it is. But some views have a preferred size, and you may know this as size to fit or size that fits. In an Auto Layout, this is the intrinsic content size.
Now, you may have an intrinsic content size in two dimensions, like this button has a preferred width based on its label, and also a preferred height based on its bezel artwork. This progress indicator doesn't really care what its width is, but it has a preferred height. And this is just a base class view. It has no preferred width or preferred height.
When you have an intrinsic content size in one or both dimensions, Auto Layout will generate constraints for you in the dimensions where it has that intrinsic content size. And there's two constraints per dimension. So here we see the button's width is at least 120. And that's called the compression resistance because it tries to prevent it from getting smaller than 120.
And it also has a width of no more than 120. And this is called the content hugging because it tries to hug its content and prevent itself from getting bigger. Notice that this is sufficient to unambiguously size the view. Before I told you that inequalities are not sufficient to avoid ambiguity, but in this case they are because there's only one number which is at most 120 and at least 120, which is of course 120.
You might be thinking, "Well, why didn't we just set the width at 120? Why do we have two constraints?" And the answer is because they can have different priorities. This is a very powerful tool you can use in Auto Layout. For example, if you have a text label, maybe you're okay if the text label grows as the window grows or if the screen rotates because it looks fine. But what you really want to avoid is the text label clipping because then you're losing content.
You would implement this by setting a low content hugging priority so it allows itself to grow, but a high compression resistance priority so it does not allow itself to shrink. For the button case, you would have a high compression resistance and a high content hugging because it wants to be its preferred width very strongly.
Now, the intrinsic content size is not settable. You can't tell a button what its width should be, except by setting a constraint on it. It's size which is intrinsic to the content. But the priorities are settable. This is a very powerful tool in your arsenal. You can set them directly in Interface Builder.
It's right there under the sizing tab. You see that there's sliders for vertical and horizontal. You can also set them in code. You say set content hugging priority for dimension, and you pass a priority and a dimension, and the compression resistance priority. And in UIKit, dimension is axis. Otherwise, it's very similar.
Let's say we have a text label like this, and it starts out and it's exactly the right size, but the content changes. So now it's clipping because it still has that constraint setting it to the old size. How do we resolve this? Well, any time the content changes such that its intrinsic content size should also change, the view will call invalidate intrinsic content size on itself. And this tells Auto Layout, "Oh, I need to get the intrinsic content size again and establish constraints reflecting it."
And if you're implementing a custom control, this is a great method that you want to -- you don't want to ever override it, but you want to call it on yourself whenever your intrinsic content size might change because some property of your view has changed. For example, the text or image content. So in this case, by calling invalid intrinsic content size, it will establish new sizing constraints and the view will be at its correct size.
Now, intrinsic content size also works with springs and struts, and you can use this to your advantage as a better size to fit. What do I mean by better? Well, size to fit cannot change. It has to stay the same to preserve binary compatibility. You'll recall in OS X, we used to have pop-up buttons with this large blue sort of blob on the right side with the indicator, and that's gone now, but the pop-up button is still sizing itself as if it's there for binary compatibility reasons.
So as the artwork changes, size to fit can sometimes lag behind. But intrinsic content size can change, and in fact, it has changed in Mountain Lion to reflect artwork changes and also just to fix some bugs. So you can use intrinsic content size as a better size to fit.
Doing so is very easy. First, you make a rectangle, which represents the intrinsic content size. Then you want to convert from the alignment rect to the frame, and then you can set that frame on the view. So by doing this, you'll often get a better layout, even in springs and struts.
So when you're running a custom control, rather than establishing width and height constraints on yourself, it's often very nice to just override intrinsic content size. And if you do that, it's a good idea to measure your text or your image or your other content, and you can also just hard code values. So if you have artwork which is exactly 22 points tall, you can just return a height of 22 for your intrinsic content size.
What you don't want to ever do is inspect your current position or size. It's supposed to be intrinsic to your content. It should have nothing to do with where you're currently at. You don't want to call super and just tweak its value. So you don't want to say, "Oh, my intrinsic content size is super class's intrinsic content size plus 3." And you don't want to use it as a substitute for explicit constraints. So if you're just making an empty view which should have a particular width, just set a constraint on that view. Don't override intrinsic content size.
And for indicating your alignment rect, for example, you have a shadow or the image badge, the dos are similar, but you want to consider using the default implementation, which is just -- returns the same thing as the frame. You can also override alignment rect insets, which is just a set of four values which can be used to convert to and from frames and alignment rects. This will save you from having to override frame for alignment rect, for example.
And the don'ts are the same. You don't want to inspect your position size or your constraints because those should be irrelevant. You don't want to call super and tweak its value. You just want to set your own value. And you don't want to use it as a substitute where you should be using explicit constraints.
So the most sophisticated thing you can do as an Auto Layout custom control is to override layout or layout subviews in the UI kit. And this is how you do most of the sort of fancy frame-dependent layouts. So all layout does is set the receiver's frame to the values determined by the constraints, and on UI kit, it sets the receiver's center and bounds to the values determined by the constraints. And afterwards, the constraints and the frames agree. And as long as you ensure that's still true in your override, you can do anything you want.
So here's an example. Say you have a view like this. Imagine like a toolbar view, and it's got some views within it, and it's all strung together with constraints, like we saw in our keyboard view. But let's say if the super view shrinks, you want that rightmost view to just go away.
: How would you do this with Auto Layout? You do this by overriding layout or layout subviews. You call super and inspect the frames of all your views. You say, oh, this rightmost view is actually outside my bounds. I'm going to get rid of it, remove the view, remove its constraints, and then you can call super again. And you can do this over and over again until you've found the layout that you're happy with. So I'll show you an example of that.
So you recall that in my letter rack view at the bottom, as I start adding views, eventually it's going to reach a minimum size and then just start clipping. And let's say I think, "Oh, that clipping looks kind of dumb." Instead of a clipping, I want to just hide the views, get rid of them. So I'll show you how we could do that.
I have this code here. So the first step is to override layout in the super view. We call super layout. For any view that we threw out before, we're going to add it back. will go into our view and call update constraints for subtree if needed. So this reestablishes all the constraints for all these views that we added back.
We'll call layout in an animation block with a zero duration so you don't see this kind of transient work being done. And then afterwards, in a loop, we're going to get our last view, last object, and if it's max X is less than or equal to our max X, well, we're done. It fits.
But if it's not, that means it's overhanging and we want to get rid of it. So we call remove letter view. We add it to our overflow array. We call update constraints for subtree if needed. And then we call layout, again, in an animation block with zero duration. And we're going to keep doing this until the rightmost last view is either nil or it fits within our view.
So I've added this code, and this time when I run it... I'm going to add a bunch of views. And then when I start shrinking it, it's going to shrink until it reaches the minimum size, and then it's going to -- instead of clipping, it's just going to throw the view out. So here we have a layout-dependent view hierarchy.
So let's go to the last topic, which is internationalization. How do you localize an Auto Layout app? Well, Auto Layout makes it a lot easier because controls know what their content should be. You don't have to go into the nib anymore and adjust things by hand for every localization. And in particular, the same constraints all work across different localizations.
So, for example, if we have buttons, cancel, save, and then the edge of the window, under German, the content is going to get a lot longer because German typically has longer words, but the constraints are the same, so you'll get the layout appropriate for the content. It's pretty cool. Hopefully you saw in the developer kickoff that one nib can now service multiple localizations, and the control content comes not from the nib anymore, but from a strings file which is associated for every localization.
And you can still use separate nibs when necessary. And this even works for right to left. Auto Layout is cast not in terms of left and right so much as leading and trailing. And leading and trailing flip under right to left. So this exact same constraints will give you a layout that flips under RTL languages like Arabic or Hebrew. So I'm going to show you what happens when I use this in my app.
So my app is using this new localization mechanism. You can see we have a base localization. Then we have English and German. So it will create strings files for these. So here's the base localization, and here's English and German. And this is all the control content just laid out for me. So let's say that I want to change the title of my initial window to something longer.
So now when I run it, oh, the window's sized to reflect this new string without even having to recompile the nib. And this works, of course, because I had constraints that say that the text field needs to push out the window if it's below its intrinsic content size.
What I can also do is turn on right to left, and you can do that with some user defaults. Apple text direction, yes. Force right to left writing direction, yes. And when I run this, It may look the same. Well, it shouldn't look the same. What's different is that, you know, the buttons that used to be on the right are now on the left, and the buttons that were on the left are now on the right.
When I start the game, the words have flipped sides, and in particular, when I click a letter, it's going to fly off to the left and not the right. So I did absolutely nothing to support right to left, but I still get a pretty awesome right to left experience. See how the views are appearing on the right and not the left. And everything else about it is the same.
So we think this is a way easier and more powerful way to localize applications. So in summary, Auto Layout allows for sophisticated and powerful layout with less code or no code in some cases compared to springs and struts. And you can take maximum advantage of it if you're able to think declaratively about your layout, not imperatively. Be wary of issues like ambiguity or unsatisfiability. The log messages are there to help, so when you get one, you want to take the time to read it and understand it.
By overriding methods like intrinsic content size, you can integrate your custom control with Auto Layout, and it will save you a lot of work. And now you can localize with a single nib file and multiple strings files. So for more information, contact our evangelist, Paul Marcos. See the Cocoa Auto Layout Guide. It's really great. It's got a ton of complete information. You don't have to write down the address. You can just Google "Coco Auto Layout Guide." The developer forums as well, of course.