Essentials • iOS, OS X • 56:22
Auto layout is a powerful constraint-based layout engine that can handle an amazing variety of user interfaces. This session pulls together API and technique to demonstrate how to build a wide range of interfaces. Learn how to construct common layout scenarios. From simple "button and text field" layouts all the way up to dynamic splitviews with interface elements that are dependent on each other, you will learn crucial skills to implement any type of user interface.
Speaker: Kevin Cathey
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.
Welcome to Auto Layout by Example. My name is Kevin Cathey. I'm one of the interface builder engineers. And here's what I want to do today. We've had two Auto Layout sessions so far, right? We've had an introductory session and we've had a mastering best practices session. And we've thrown a lot of content at you.
And what I want to do in this session is I want to take a lot of that content, bring it all together, and answer the question, "How do I do that?" So we're going to be looking at a lot of different demos, a lot of scenarios, and we're just going to power through this. I have a lot of content to cover. I'm just going to warn you guys up front. We're going to be going fast. For those of you watching the videos, no problem. Just hit pause. For those of you in the audience, buckle up.
Here's what we're going to talk about. We're going to talk about transitioning to Auto Layout. We're going to be looking at converting part of an application to use Auto Layout using some of the different techniques that we talked about in the mastering Auto Layout session. Then we're going to move on to talking about explicit widths in interface builder. How many of you guys had one of those and wanted to get rid of it? We're going to talk about how to do that.
Then we're going to look at localization as our third topic and talk a little bit more about the new localization workflow in iOS 6 and OS X Mountain Lion. Then we're going to move on to talk about some new API in Mac OS X Mountain Lion, particularly with splitview and how this new API is going to make your lives a lot easier.
And then finally, we're going to wrap it up and we're going to do a really quick breeze through of some of the animation. But let's dive in. Let's talk about transitioning to Auto Layout. Let's have some fun while we do that. All right. How about that? All right. So transitioning to Auto Layout.
A couple different steps here. First, you want to plan your attack and choose do I want a partial conversion, a full conversion? Let me encourage you, if you guys can do a full conversion, you'll save your guys a lot of headaches. But it might not be possible to convert your entire app all at once. That can be a huge cost if you have a big app. So we're going to actually be doing a partial conversion in the demo app that I'm going to be showing you today.
Another technique you can do is actually convert -- or rather to adopt Auto Layout as you add new -- UI. For example, maybe you have a new window, a new popover, something like that. You can adopt it just in a new UI. That's a great way to get started as well. So I'm going to go into a demo and we're going to look at turning on Auto Layout and your interface builder documents. We're going to look at the code, we're going to test it, and we're going to see this all at work. So let's get started.
All right, so this app might look a little familiar. So we've been showing you the SiteSeer application throughout the conference so far. The developer tools kick off, the platforms kick off. We were showing you the iOS version. Now I also happen to have an OS X version, and so we're going to be going back and forth between the two versions today to look at some of this stuff in practice. So if I look at my application, really simple. I have some landmarks. I can add those to an itinerary. So very simple application. And this little piece right here, this detail area here, is not yet using Auto Layout. And you can tell.
Because as I resize things around, you can see it's not really doing exactly what I want. I can get this guy really small. There's some clipping up at the top. And, yeah, I could write some code to fix this, but we're going to turn on Auto Layout and just let Auto Layout do it for us. So let's just dive in and do that.
All right, so here's my interface in Xcode. And, again, step one is to go into all your interface documents, your storyboards, your ZIP files, and to check the box to use Auto Layout. So I'm going to open the utilities area, go to the file inspector. And here in the interface builder document slice, I'm just going to check the box to use Auto Layout. And that's going to pick a set of default constraints right out of the gate for us. And we can preview this by just resizing the window.
And you can see, okay, it's doing something reasonable here. It's, you know, growing on exactly how we want. And look at those labels in the bottom left. Eh, that's not exactly what we want either. So let's go ahead and modify this to do what we want. The first thing I want to do is add some expression to my UI. I want all these four views to kind of grow proportionally both vertically and horizontally.
Now, this was really hard with springs and struts. I had to write a lot of manual layout code to do this. With Auto Layout, it's a breeze. So I'm just going to select these two views. And using the add constraint menu down here, I'm going to pin these widths equally.
And I'm going to do a similar thing with the heights. So I can just use this add constraint menu down here in the bottom to pin these guys proportionally. And now when I resize my window, hmm, still not doing quite what we want, right? This separator image here, we kind of want to stick to the top, and we don't want the image view in these views to stay fixed. If you look here, I have an explicit height constraint. I'm going to be talking more about how to get rid of these later. But what I'm going to do in this case, I'm going to undo that change first of all.
I'm going to select this separator. I'm going to add a new one. I'm going to add a top spacing constraint. When you're adding constraints with this menu, most of the time, with the exception of these two constraints in the bottom, it's going to take an existing relationship in the canvas and freeze it into a constraint.
Now on top of that, what it's going to do is it's not going to remove any other constraints in your document. So if we look back at this image view, this explicit height is still here, but now it's a user constraint, something that I can interact with and I can delete. And now when I resize my window,
[Transcript missing]
Now you might be asking, or you might not be asking, maybe you should, is why would I be wanting to add all these vertical spacing constraints or constraints between views? So this again is one of the powerful features of Auto Layout is that you can have constraints between siblings.
What's great about this is that as the content of these siblings changes, if they're bound to each other through constraints, then that way, as the content of these views changes, things won't overlap or clip and that can be really great and exactly what you want. Okay, I'm going to do one thing special with this bottom constraint here.
I'm going to show you another really cool expressive feature of Auto Layout, which I'm sure you guys have already seen, and that's inequalities. So instead of saying I'm going to make this constraint be 21 points, for example, I want to make this an inequality and say make it be no more than -- sorry, no less than 21 points or some amount of space.
So if I select the constraint and go into the attributes inspector, I can change the relation here from equal to greater than or equal, and I'm also going to check the standard check box. The system, both UI kit and app kit, have divinely defined a set of spacings between different combinations of views, so I want the system to determine what that spacing should be, so I'm going to check that check box.
And now when we run our application, we're You can see this is looking a lot more reasonable. Things are proportionally growing, and things are looking really great. So, so far we've gone in the interface builder, we've checked the box, we've adjusted some constraints, but we're not quite done, right? When I click my add to itinerary button, it's not doing the right thing anymore. And this is because we've got to go into code and change it.
So here's what we're going to do. We have this box in my document that's going to be our add to itinerary box for what day I want to add this landmark to my itinerary. If we look at the constraints for this, we just got a bunch of spacings to the top, and what I want to do is I'm going to add a bottom spacing. Here's why.
What I want is I want this view to be able to fully specify its height. Without this bottom constraint, I could grab the bottom of this box and resize it up over that content. But by putting a bottom spacing constraint here, this means that there's at least some size information that we have.
That way I don't have to put an explicit width, or rather explicit height on the view to be able to define this. So what we're going to do is we're going to go and do a little bit of code. And what we're going to do is we're going to do a little bit of code in code, which would mean that if the box changed over time, it wouldn't grow. So that's why I'm going to define this all right here in the box.
Okay, so what we're going to do in code is we're going to take this box here, and we're going to insert it in between this label and this top separator. Again, just a really simple sentence. We're going to insert this in between two views. So let's go look at the code of how we did that.
Jump bar is a really, really great way to navigate around in Xcode. I found it to be very quick. I'm going to show you something else of the jump bar, too. In this talk, I'm going to be inner sprinkling both Auto Layout and then also little tidbits and power tips for you Xcode users.
If I have a jump bar open right here, I can just start typing, and the jump bar will automatically start filtering. So I know I have a method in here somewhere that's something like open box of some kind. And so by just typing with the menu open, it'll automatically filter for me. and I can jump right to the method that I'm looking for.
All right. So looking at this code, what do we got here? Well, it's really hard to look at this code and see what's going on. We're just basically getting a frame, we're adjusting a bunch of other frames, we're adding some deltas for some views and doing a bunch of resizing. This code is really hard to read, hard to understand, and hard to maintain. And what's great about Auto Layout is I can just delete it.
And replace it with some more code. So let's talk about that. So the first thing I'm going to do is I'm just going to add just define some variables that we can quickly reference as we go on. Okay. The first obvious thing, we're going to add our box to our container view, right? So it's actually in there.
And then the second thing, once we have our container view actually in there, we want to add some positioning constraints, right, so that it goes in the right place. Now look at how the visual format has helped us. So in the horizontal direction, what I'm saying is be the, you know, Aqua space, be the box, Aqua space, edge of the super view.
But remember, I specifically repeated the statement about the vertical dimension. I said we want to insert our box in between the title and the separator. And that's exactly what the code looks like. There's no math. There's no -- it's just exactly what I said in English. So it's really, really easy to see exactly what's going on here.
All right. And now when we close our add to itinerary box. Again, we're going to add a little bit of space. Again, I had to undo all the work that I was doing in the other method. And this is just -- let's just get rid of this. And I can replace it with one line of code. Remove. And that will automatically remove all the constraints that I had and get that box out of there and restore the layout. So now when we run our application, we're going I'm going to click this and, ooh, still doesn't do quite the right thing.
What's going on here? So if you look in the console, you can see that we have one of those log messages that we've been talking about in the different sessions. So let's put on our debugging hat and let's apply some of the things that we were talking about in the mastering on a layout session. So this is a log message about unable to simultaneously satisfy constraints. So we have a bunch of constraints in the window that can't all be satisfied. Which ones? The list right here.
So the very first one that I see here is an auto resizing mask layout constraint. So let's rewind to some of the previous sessions. And as a part of compatibility, what Auto Layout will do by default is that it will add constraints that mimic the auto resizing mask.
Now, one of the things we need to do is turn off that feature so that if we're using our own constraints for positioning, they don't conflict with these auto resizing mask constraints. So it looks like I've forgotten to set no for the translates auto resizing mask into constraints property on one of my views, in particular on my box.
So looking in the code, I can do it in one of two places. I can set it right here in the code. I can also do it in Interface Builder. So if I go back to the zip file here and I select my box, open the Utilities and go to the Attributes Inspector, there's a checkbox right here called Translates Mask into Constraints. And this is, again, that value of Translates Autoprocessing Mask into Constraints. It's set by default to yes for all top-level views. But in this case, I want it to be no, so I can just uncheck it here.
When you would set it in code would be if you wanted to program defensively. For example, you had some API that took a view from somewhere you didn't know where it came from. And so if you know you want it to be set to no, then just set it to no. And that's what we're going to do. We're going to do both just to show you both here.
Okay, so now when I run, we should be set, right? I'm not forgetting anything. Of course, this is a demo. I would never forget anything. Oh, but it's still not doing the right thing. So what's going on this time? Let's go back and look at the log message here. Once again, we have an unable to simultaneously satisfy constraints. What's going on this time? Let's take a look at this list of constraints and see if we can get some clues of what might be going on here.
Okay, so we said be some spacing between the box and the separator. Yep, I remember adding that constraint with that visual format. Same with this one. I said make the box be flush with that label. Yep, that looks good. And, huh, I don't ever remember creating this constraint.
So have that separator image view be 58 from the top of the view? I don't remember. Maybe I have a really short-term memory, but I don't remember adding that. So how might we find when that constraint was created and added to the window? There's a really useful performance tool that you guys might have used called Instruments. Might have used it to do for CPU or for memory. It also includes a template for debugging Auto Layout.
And you can see exactly when different Auto Layout things happen inside of your window. So we're going to use that to help us out here. I'm going to go to profile here. What this is going to do is launch instruments. I can choose the Cocoa layout template. We're going to go ahead and profile. Now it's going to launch our application again.
And we can just go ahead and reproduce the problem. So, yup, there's our problem. Let's go back to instruments. And if you look at instruments, we can see a trace of all the Auto Layout activities happening inside of the application, this spike in particular was where we reproduce our problem. So I have all the data I need, let's stop and take a look at what's going on here. So with this view, I can see all the constraints that were ever added to our windows.
And I can see their constants, and I can see what it was that was created, added, it was removed from the window. I can see who created it, very, very useful data. So let's use this to figure out where that constraint came from. So I'm going to switch to the console, and that will show me exactly the output from Xcode.
And we can see there's our log message again, unable to simultaneously satisfy constraints. And if I look through this list, yup, I remember where that one came from, I remember where that one came from, but this is the constraint that we're not quite sure where it came from. So I'm going to copy the memory address here just because the constraint is unique.
And I'm going to go back to the memory address. And I'm going to go back to the memory address. And I'm going to go back to the event list. And I'm going to filter the event list based upon this memory address for this constraint. And you can see that I get two hits, two events. One was created and one it was added to the window. So now we can see exactly where this constraint came from.
To do that, I'm going to open the extended detail and make it a little bigger here. And we can see a stack trace of exactly who created this constraint and when. So if we look through this stack trace, you can see there's a call to NSNib instantiate the view controller. Okay, there's a pretty big clue.
And load view. So it looks like this constraint is coming from loading the interface with the document for one of our view controllers. Which view controller? Well, I can just double click on here and get the code right here in instruments and see the call that loaded this view controller.
Then I can click here and jump right back to Xcode and jump to the implementation file that we were just looking at. So this is the detailed view controller that we were just working with, and now I can see that the Nib document for this view controller has that constraint that was added. So let's go figure out where that is in our document. So let's go ahead and open it up here. And I don't remember which view is the view that is the top separator anymore. So I'm going to use the outline view and filter for it.
Top separator. And sure enough, there's our image view. And if I select it, it opens up in the canvas. Now I'm going to look for -- I'm going to go hunting for that constraint that was the constraint that we don't know where it came from. And so I could click on every one of these constraints or I could use a new feature with Xcode 4.4. If I have a view selected, I can go in the size inspector and see all the constraints that involve that view. So now I can see -- I can scroll over these.
You can see they highlight in the UI. And there's the constraint that was causing me difficulties, the 58. And so what do we want to do? Now that we've identified what the constraint is, and I remember, yes, I added that because I wanted it to be pinned at the top. And generally speaking, I do.
But when we add this box, I want the image view to come down, right? And I want that box to slide in between the title and the image view. So what I'm going to do is I'm going to add an outlet to this constraint. And we're going to remove it in code temporarily while that box is there.
And then we're going to add it back after the box goes away. So to do that, I'm going to open up the assistant here. So this is the code for my view controller. And I'm going to switch to the implementation. Scroll to the top. And you can see in a class extension in my implementation I have all my outlets. This is to make sure that I'm not vending these outlets to as public API for my view controller. And now I can just control drag right from the constraint, write in the code.
I'm going to give it a name, like Separator to Label Constraint, and make my outlet. Great. So now we can refer to this constraint in our code. Let's go back to the method that we were looking at before. And the first thing we want to do is when we are opening our itinerary box, we want to remove it so that it doesn't conflict with the constraints that we're adding.
So to do that, I'm just going to simply call container view, remove that constraint that we made an outlet to. Then when we close the box, we want to restore that because we want to restore the UI back to its original state. So I can just do this down in the close.
is the founder of the "button and text field" method here that's going to close the box on an add or a cancel. And we're just going to add that constraint right back exactly how it was before. And now, when I run my application, Boom. There it is. Using Auto Layout, no more frames, exactly as I laid it out in code, and it goes away perfect. So now our detail view controller and our entire app is using Auto Layout. We're good to go. Okay. So let's recap what we just saw.
So again, we planned our attack. We chose a partial approach to convert that last view controller to Auto Layout. We went in, we enabled it in our interface with a document, we went through the constraints in the document and adjusted them to what we wanted, added a few more.
Then we went into code, we removed all of the set frame calls and then we sanitized all the add sub view remove super view calls to make sure that they were consistent with Auto Layout and they worked with our interface. And then we tested it, we ran it a few times and now our app is looking pretty good.
A few best practices that we used when we were doing this is first of all, we want to avoid explicit widths. I'm going to talk about this more, so we're just going to skip over it. Secondly, we added those spacing constraints. If you remember with those labels, we added those spacing constraints so that as the views would change size, they would automatically would grow relative to each other. The other thing we did is we added some more expressive constraints, some inequalities and some things that weren't possible with springs and struts or at least really hard and required manual layout code.
All right, so that's just a brief look at transitioning to Auto Layout. Let's talk now about explicit widths. What do I mean by explicit widths? Well, if I have an interface, like the one on the screen, That's what I mean by an explicit width. It's a constraint that says this view must be this size.
But why would this be a problem? Generally speaking, you want to avoid explicit widths on your controls because with an explicit width, it doesn't allow the control to change size as the content changes. We had talked about how with Auto Layout, things will automatically grow and shrink based upon content, and when you add an explicit width, it can't do that. This can cause issues with localization or dynamic content where things can start clipping unexpectedly, and that's definitely not the experience that you guys want to give your users.
There are a few exceptions, of course, I want to point out. These are applied to both iOS and AppKit exactly the same. First, border text fields. If you have like a password field and your user is typing in their password, you probably don't want the view to be growing as the user is typing in their password.
Same with image views. Image views will actually want to be the size of the image that you give it. This can be very useful in cases, but oftentimes you're going to throw on some explicit width or height so that the image view stays some size and the image within it is just changing. So those are just a few exceptions.
So how do I get rid of those explicit wits when I don't want them? I'm not going to answer that yet and I'm going to talk a little bit first about how Interface Builder is generating constraints so we can better answer that question. What Interface Builder is doing when you're working in IBE is it's adding constraints for you which can be really nice because it can save you a lot of time but as you saw we had to customize it a few times and we wanted particular behaviors. The set of constraints that Interface Builder is generating is the minimum best set.
There's an infinite number of possibilities of constraints that you could add to your interface. And Interface Builder has a scoring system for determining what we think are most likely the ones that you're going to want for a given situation. So that's the first thing, minimum and best set.
Secondly, we avoid two illegal cases which is really important. First, we avoid unsatisfiable constraints due to like mutual exclusion. For example, you can't have a button that's both 15 and 20 points wide so we'll remove one of those constraints for you so you don't end up with a crash at runtime.
What we'll also do is we're going to avoid ambiguity. If there's not enough data to fully satisfy a particular layout, we're going to keep adding constraints so that you don't run your application and get undefined behavior. This is where those explicit widths come into. And there's a couple cases that might not be immediately obvious of why those explicit widths come up. So to show that, we're going to go back to a demo. All right. So we're back in our app here.
And, you know, everything is looking pretty good. You know, this is a little clipping going on here, but that's just because some views are overlapping. But overall, it looks like all the content in our interface builder document is not really clipping. That's at least what it appears at at first look. Let's take a little deeper look.
I'm going to show you some really useful debugging defaults that you can use to help test your applications and react to problems ahead of time. So I'm going to go ahead and edit the scheme that I have here. I can either click this menu item here or I can actually hold the option key and click the run button and that will also allow me to edit the scheme.
This is a default I'm going to turn on called NS double localized strings. What this is going to do, it's going to take every string in my interface and multiply it by two. So, for example, this would mimic, for example, if you were to run your application maybe under like a German localization or some other really long localization where words are longer. And so I can perceive these problems before they occur.
So let's see our app now running with that turned on. So if I run my app, ooh, we're getting some clipping here. These buttons look okay, but if I open, we're getting some clipping here as well. So with this default, I can see how my interface might react to some changes.
All right, so let's go back into Interface Builder and I'm going to show you three ways that you can get rid of explicit widths. Number one, the first way to get rid of explicit widths is to size the views in an Interface Builder document to their intrinsic content size.
I have two ways to do this. One, I can just grab the resize handle and resize it down. You can see when I get down here, it snaps into place and I get these blue guides right here. And that indicates that my view has now reached its intrinsic content size. And you can see the explicit width is gone.
And now I can go ahead and recenter that in my interface. The other way I could have sized this to fit is by going under the editor menu and selecting size to fit content. So that's way number one. Now I'm going to take the opportunity here to just add a few more constraints.
Now that my label is going to be resizing, I want to make sure it doesn't occlude this button. I know we've showed this demo lots, but I think it's really cool and it shows some of the power of Auto Layout. So we're going to do -- we're going to set the relation to greater than or equal to. And we're going to set the standard space. So make sure that that space is always greater than or equal to some standard space.
And then also I have an explicit width on the share button. And it looks like it's about the same size as the add to itinerary button. So I'm just going to make that so by adding an equal width. And you can see that explicit width also goes away. Okay. So way number one, again, is sizing to fit your content. Now there might be cases where you can't size to fit your content. For example, a visual designer has specified that in English, for example, this button must be 85 points, for example.
So again, your visual designers might have a different design in mind. Let me undo that change. In this case we're going to employ technique number 2. What I'm going to do here is I'm going to take the relation of this width constraint and I'm going to change it to be greater than or equal.
What this is saying is that generally speaking this button should be greater than or equal to the current size, which means if the content is smaller than that it'll be that greater than or equal to size, but we're also affording the button to grow in the case that you go to a localization that has really long strings. It will allow it to grow, but it won't get smaller than the current size that we've just specified. So that is way number 2 that you can get rid of explicit widths. So now we run our application.
You can see our tile looks great. Push the window out to make it bigger, more room. And you can see our buttons also look good as well. So now we're going to take this button and we're going to take the width constraint and we're going to make it look good as well. So those are the first two ways in which you can get rid of explicit widths.
For the third way, I'm going to go back to our iOS version and show you that there. A lot of the topics that we're talking about today are completely synonymous between iOS and AppKit because of the very, very similar APIs that they share. Okay, so we're going to look down here in this label section for the facts of this particular landmark that might be selected. And you can see I have two labels here, and this one has an explicit width. Now I'm just going to size this thing to fit.
And you can see it still has the explicit width. So why? Why is it that these two labels are both being driven by their intrinsic content size, yet one of them has an explicit width? This goes back to what we talked about in the mastering Auto Layout session that has to do with content hugging priority.
So by default, views have a constraint inside of them that is saying try to be this size. You can kind of think about it this way. If you have a label that's, like, got some text in it and you grow it really big, you can think of the content hugging priority as someone inside of your view kind of pulling on the edges of the view saying, oh, come back and be the size of my content. So that's what's going on here with our constraints.
We have these two views. And as this container grows bigger, it's ambiguous which label should grow. Should they grow proportionally? Should the one on the left or the right grow? And so what we want to do is we want to indicate that one of these labels should have a higher hugging priority.
It should pull harder to be its content size. So what I can do is I can just select this bank of labels here. And I want the kind of labels to these facts to always, always be the ones that stay put. So if I switch to the size inspector here and go to the content hugging priority, what's important here is the relative priority to the other thing. What I have to do is make it higher to indicate to the system I want these labels to hug more closely. They're very friendly labels.
All right, and now you can see, if I zoom back into that label, the explicit width is gone. This is a tricky case and it's definitely going to come up as you guys are designing interfaces. So the third way that we get rid of explicit widths is by adjusting the content-hugging priority of a view. So I'm going to run this application and we're going to take a look at how this looks running in the simulator.
And you can see, huh, still not quite right. If I rotate to landscape, you can see it looks good. But in portrait, things don't look quite right. Why? Let's go back to interface builder and see why. So there's content hugging priority and there's also content compression resistance priority. And this is the exact opposite of content hugging priority.
If you have that label that we were talking about earlier and you make it smaller than the content, you can think of the content compression resistance priority as someone inside of you pushing on the wall saying, "No, go back out to be the size so you don't clip." By default, we have this set pretty high.
But also by default, the content compression resistance priority of these two views is the same. The actual value at this point is not important. So they both have the same content compression resistance priority which means if this container view that these two labels are in gets small enough where we have to clip content in both labels, well, which one do we clip first? That's ambiguous.
So what we want to do is we want to select this bank of labels again. And again, relative priorities is what's important here. I'm just going to increase it by one which says I want to resist compression more than those other labels. So now when I run -- You can see I get exactly what I would expect right here in my interface. So those are the three ways in which you can get rid of explicit widths using Interface Builder. Okay. So let's just reiterate what we just saw.
So, again, there are three ways we can get rid of explicit widths in Interface Builder. Again, we can size the content to fit. If you can't do that, you can change the relation of that constraint to be greater than or equal. And then also if those two don't work, then you can also sometimes have cases where you have to adjust the content hugging or compression resistance priorities of the different views. So those are the three ways you can get rid of explicit widths in Interface Builder. All right. Let's talk about localization.
So let's talk about localization before AutoLayout. So you as a developer, you have your interface with a document, and it's great because all you have to worry about is your development region. Let's just use English as an example here. But then your marketing team comes and says, well, we really want to localize the application into French, Spanish, German, and a bunch of other languages.
So you go, OK, well, I'm just going to make a bunch of copies of my interface with the documents. And I'm going to go change the strings in those documents for each one of those languages. And right now we've created this barrier, this divide between your original engineering document and all the localized documents. Now this is fine if nothing changes.
But as we know, that's not the case. Things always change. So now you have to go and you have to make a change to your development time document. So you're going to make your change. And now you have a synchronization problem. You have to get all the changes that you've made to your new development document into all those localized documents as well.
This is where the three-way merge process with Interface Builder would come into play. We would merge all the diffs from the different ones into a new document. And then we would have to do the same thing with the other documents. Now as many of you might have experienced, this can be error prone and can cause issues. And it can be a lot of work to maintain all of the different changes between all the files. We can do better with Auto Layout.
So again we're going to start with this development time document and instead of creating new documents when you want to localize your application, new interface with the documents, you're going to create a bunch of strings files. For those of you who are not familiar with strings file, a strings file is just a list of key value pairs.
The key being some view and interface builder and some property and then the value being the actual string value and whatever language you have. So now when you need to make a change to your development time document, you go ahead and make the change. And let's say all you change is the font color of something.
Well, previously you'd have to regenerate all of the localized documents. But because we're using strings files, we don't have to touch anything. Everything just works. Now if you were to add a new view, let's say a new button that had a new string and you need to localize that, it's very, very easy just to use textual diff and to apply and get some new strings files that contain those new strings.
We think this is going to be pretty great. And so let's talk about how this actually works a little bit more under the hood. So when you build your project, we take a zip file and we turn it into a compiled document that we actually ship with your application.
So now you have your compiled document and then you also have a bunch of strings files for each one of the different languages. Now, what's actually on disk is you have a folder in your resources for each language, and this contains the localized resources for each language. In this case, we're going to stick the strings file for your interface into that language. So how does this work and interact at actual runtime? Let's look at an example.
We've introduced a new localization called Base. And this, you can kind of think of it as your development time localization. It doesn't even need to be English. It could just be what your engineers use to talk. I talk in a very different English than the people that are writing the strings for our interface. So you could have an actual English localization that also does this as well. So Base is just where your interface documents are going to go, and then the strings files are going to go into each of the different languages that you localize.
So what happens at runtime is we have, let's say, a Mac running in English, and so we're going to look for that English interface, and we don't see anything, so we're going to fall back and use Base. We load the Base interface with the document. It's got our English strings. We're good to go.
But what about having, let's say, an iPad that's running in German? Well, in this case, we're going to go look in the German file, and we're going to find those strings files, but there's no layout information in a strings file. So where does the interface come from? In this case, we're going to fall back to base, and we're going to grab the interface out of base.
So now what happens? At runtime, when you actually run your application, it's going to take the strings, and it's just going to substitute them into your base interface builder document. Well, isn't that going to cause all kinds of clipping and issues? Well, if you're using Auto Layout, everything just works because everything will reflow, and your layout will look beautiful in all the languages that you've localized in. And let's look at a demo of this.
All right. So I have my storyboard here. And this is the storyboard for iOS variant of the application. And I'm going to show you that I only have one storyboard in this entire project. And then I have a bunch of strings files. I have one for German and I have one for Arabic as well.
Let me open the assistant and show you a new category that we've added to the assistant to help you working with localizations. If you pop this open and go to localizations, you can see all the strings files for the interface builder document that you have in the primary editor.
So you can see, for example, in German, our share button is going to turn into that. So let's run the application. and see this at work. So now the simulator is going to load our base storyboard document and it's going to take the German string files and it's going to substitute them right in.
And there we go. Beautiful. One story board, bunch of strings files. It gets better. All right. So let's go -- and I'm going to -- I'm just really passionate about sharing. So we're just going to duplicate this string a bunch of times because I really like sharing. And maybe your designers are having a day and they say, well, we want you to go in and we want you to change the color of all these labels to be, I don't know, bright blue. And then maybe you want to add a progress indicator to this image view to show the progress of the image as it grows. So I'm just going to grab a progress view and just drop it right here into the document.
Now at this point, all I'm going to do is hit command R. I'm not going to generate any new files. I'm not going to generate any new localizations at all. And you can see the strings changes come right in. My interface changes come in all without having to do any synchronization across my strings files or my interface builder documents. And we think that's really great.
But it gets better. All right. So we're going to go ahead and show you a little bit of the process. And we're going to go ahead and show you a little bit of the process. And we're going to go ahead and show you a little bit of the process. And we're going to go ahead and show you a little bit of the process. All right. So what I'm going to do now is I'm going to switch my system language into Arabic.
Arabic is a language that is read right to left versus left to right. So we want everything in the UI to layout right to left. With Auto Layout, this couldn't be easier. If I run my application, again, I only have one interface with a document for this view. You can see every single thing in my interface automatically flips. Not only do I get the Arabic localizations and the text flipping.
So not a single line of code for a very, very powerful layout. Now as you can see, it's not doing quite what I expect, right? This gradient view over here that indicates the separation between my splitview is also flipping, even though the splitview, which due to what we say for HI says not to flip, the gradient is also flipping. But we don't really want to do that. So why is it flipping? Let's go into interface builder and find out. Let's open the main menu. That has our splitview in it.
All right. So now I can select this gradient view and go over to the size inspector and I can see there's our trailing space that we have. And using the gear here I can just go ahead and select and edit it. Now by default all the constraints in interface builder and in the visual format language are going to be leading to trailing. Leading to trailing you can kind of think about the natural text direction.
Based upon what language you're running in the constraints will flip orientations. So for left to right it will flip versus from left to right. But what I want to do is I actually want to say that this constraint should never flip. So I can explicitly tell the direction to be explicitly left or left and right versus leading and trailing.
And so if I were to run my application in, for example, Arabic, now my gradient does not flip and it stays right there. So this is really, really powerful for you guys as you're working on right to left localizations. So that's what I wanted to show you with localization.
Let's go back to slides. So again, with the localization, the new base localization, it's where your interface-based documents are going to go to. We've also provided an override, so you can create an interface-based document for a language if you have, like, special UI you want to have for your French users, for example, or maybe for another region. You can actually have an interface-based document for just that language that will be an override, but then you'll need to use the three-way merge process to make sure that those changes get back into those documents that are synchronized across.
Okay, and then we have string files, one per localization. At runtime, we're going to take the strings from that localization, we're going to stick them into the base localization interface with a document, and Auto Layout takes care of the rest. For availability, this is available for iOS 6 and OS X Mountain Lion going forward.
All right, so that's localization. And looking back over the three things that we've just talked about, explicit widths, spacing constraints, localization, we're talking about the main takeaway for this session, which is thinking ahead. With Auto Layout, as things can react to changes, it's important that you guys are thinking ahead of how your interface could change over time.
For example, in our app we had this fax area in the bottom and the labels were changing and it had different content. So we want to think about as the strings changed, what would the interface do? Or if you have device rotation, if you're writing an iOS application. Or as we just saw with localization, strings can drastically change.
So also as we showed you, we've added a few debugging defaults to help you anticipate some of these changes. I'm not going to explain all of them here, but we saw the doubling of the strings, and you can also see, as we saw in the second session, the one from this morning, you can also mimic running in a right-to-left language without actually switching your system into that language.
All right, so that's what I want to cover with localization. We've got two topics left, and then we'll get you guys off to lunch. All right, so let's talk about some new API. And really what I'm going to focus on here is the new splitview API with OS X Mountain Lion. This is really, really going to help you guys as you're developing applications that use splitviews.
So before I dive into that, I want to talk just a really refresher on priorities to make sure that we've squared away with those. So again, priorities are for expressing either required or optional behaviors. If you lower the priority of something, you're saying that this is optional. For example, we're looking at the content hugging and compression resistance priorities, and those are constraints that are internal to the view that are implementing that using a priority system.
Okay, well, let's talk about that a little bit more. By default, constraints are going to have a required priority. So 1,000. This means it must be fulfilled. It must be satisfied. If you lower the priority anything less than 1,000, what the system is going to do is it's going to try as hard as it can and get as close as it can to fulfilling that constraint.
I like to think of the example of Auto Layout as kind of like gravity pulling rain down. And you might have, like, you know, buckets of constraints that might catch the rainwater. But generally, the rain is falling down as Auto Layout is trying to get as close to that solution as it possibly can. And the system also has some predefined constraints, and so we're going to be talking a little bit about those. And again, to show this, the best place is with a demo.
All right. So we got our application here. And one of the things that we can do is we can grab these splits and we can make them zero. That's not exactly what we want to do. Right? Also, as you can see, as I resize this window, you can see the splits are kind of growing kind of proportionally. And it's doing a little funny behavior.
So if I were writing this with manual layout code, I would go in and I would override all the delegate methods for NS split view and write a bunch of code to set minimum sizes and react to these different changes. I don't have to write any code in this demo. We're going to do it all in Interface Builder. So let's go take a look.
So back here in my splitview, the first thing I want to do is add some minimum sizes to those splits. This is as easy as adding a single constraint. So I'm going to select the split over here and I'm going to add a width constraint for the split.
Now, I could drill down in here and try to select it, but Interface Builder pro tip: If I hold control and shift or shift right click, I can get a menu of every single object under the mouse at this point. This makes it really easy to select objects that might be occluded.
So I can go down here and I'm going to select very easily, I can see that I want to select this source list split. So I'm going to select this and I'm going to go to my add constraint menu here and I'm going to add a width constraint. So I can add this directly to the split of the splitview.
And now I'm going to change the relation to greater than or equal and I'm going to set this to 200. And now this split will not get any smaller than 200. Let's do the same thing in our other splitview for the detail area. and that's in an interface with a document called split view controller.
And again, I'm going to select that split. I'm going to add the width constraint, and in the attributes inspector change the relation to greater than or equal to 200. All right, so now these guys have got minimum sizes. Let's talk about now the proportional sizing. We really don't want the split view to grow proportionally.
What I really want is I want the source list to stay fixed most of the time as I'm resizing the window. Then the next thing that's important to me would be the list of landmarks. And then finally that detail area I'd want to fluidly resize as I resize the window.
There's new API on NS split view called holding priority where you can set the priority at which a split view maintains its current width. So if I select the split view and open the size inspector, you'll notice some new UI up here in the corner. By default, the splits are going to have all the same holding priorities, which means the split view will resize proportionally as I resize it. But that's not what I want.
What I want to do here is I want to say that one split should hold its width stronger than another split. All I have to do is increase this by one, and now the split on the left is going to hold that priority stronger than the other split. So I'm going to go back and do the same thing in my main menu.
Let's select that splitview. Go to our size inspector. Now what I'm going to do here is I want to interact with that other splitview. What's great about Auto Layout is I can interact with all the views in my window. So before we set those splits to 250 and 251, what's important here is setting relative priorities. I want to make sure that these priorities are higher than those so that these hold stronger than those other ones. So I'm just going to be safe to go up to 255. And I'm going to set the other split to be 254.
And now the splitview, the source list here, is going to hold stronger than any of the other splits. So now when I run my application, first of all, I can grab these and they stop resizing when I hit a minimum width. And as I resize my window, you can see it's doing exactly what I would expect as far as the splitviews.
Now let me point something else out here. The window stops resizing at a certain point. Now I didn't set any minimum size for that split, so how did that happen? And this is where Auto Layout is kicking in and it's saying, well, we don't want to make any of the constraints that you've specified unsatisfiable.
All the constraints, a lot of the constraints we specified are required and it's going to hold those and it's even going to stop resizing the window and resize the window if it needs to in order to make that happen without writing any code at all. And that's really cool. So we're going to dive into this and make it even better.
So I kind of want to be able to drag this a little bit smaller, right? I want this title to go over, and I want this text here to be able to be clipped. Right now it's not allowing it to be clipped. So let's go into Interface Builder and make these adjustments. So I'm going to go to my detail view controller.
And I'm going to go ahead and change the text here to something a little bit larger. And you can see the behavior as I resize and interface whether it's doing what we would expect, it's stopping at that point. So the first thing I want to do is adjust the priority of this centering constraint. So let's go over to the attributes inspector and set the priority.
But what should I set it to? Up to this point we've worried about relative priorities but at this point we have to pick something else. This is where the system predefined priorities come into play. There's a couple that are coming into play here and I want to show them to you. I'm going to open up the header that describes these just so that I can point them out to you.
Now what I'm going to do, though, is I want to open this side by side with my interface. So I'm going to show you Xcode Pro Tip. If I hold down Option and Shift, again, Option and Shift, when I hit the return key, it's going to show me a little HUD.
And this HUD allows me to target where I want the file that I'm opening to go. For example, I can say open in a new window, open in the current split, open in a new tab, or open in a new split. If I had other splits, I could target an existing split, or I could target a split in another tab. So it's a really, really easy way to quickly open the files that you want to look at.
So there's a bunch of different predefined priorities here, and I want to point out just a few. One, the required one. Again, this is the priority that means this constraint should not be broken. The next one I want to point out is 500, which is the size at which the window is going to stay put. If I have a constraint greater than that, it will resize the window to accommodate that constraint. If it's less than 500, it's going to allow the window to get smaller, and it will break that constraint.
This number also looks familiar, and this is that value that we were using for our holding priorities for our splitview. So what I want to do is I want to pick a priority between the window resizing priority and this priority so that the splitview will still hold, but the window will get smaller and it will break that centering constraint. So I'm going to set it to 400.
So let me reset the Assistant and close it. And let's set this to 400. The other thing I want to do is I want to select these labels, like I pointed out as well, and I want the content in these labels to be able to get clipped. So let's go back to the size inspector and set this to 250, which means don't prevent the window from resizing in order to show our content. And now when we run our application, You can see it's doing exactly what we would expect outside of this label up here, but you can see we're allowed to go off center.
So let's go back and let's just do two more constraints to show you really some more power of Auto Layout. I'm still wasting some space, and I'm going to get rid of that space. So first we're going to add this leading constraint to our view. Again, we want to make this be greater than or equal to the standard space, a common constraint that you'll be adding as you want views to not occlude each other. And then what I want to do is I want these buttons, if the space is really tight, to actually stop being an equal width.
But at what point? So I want it to break after the centering constraint is already broken. So we set the other constraint to 400. We're going to set this one to 401. Again, a lot of setting constraints is setting things relative to each other. So in this case, I'm going to set it to 401. And now when I run my application, check this out. When I resize my window smaller, it's going to stop and look at that equal sizing constraint. gets taken down.
Just look at this interface. Imagine trying to write this using manual code with the existing splitview. That would be really hard. But with Auto Layout, I didn't write a single line of code. All right, let's go back to slides. So the API that I just showed you is the new holding priority API for NS splitview, which allows you to do incredibly complex layouts using Auto Layout and the new splitview in OS X Mountain Lion. There's also some new text wrapping APIs you might want to check out, look it up in the documentation. Those are also really great and will help you a lot as you're developing your applications. All right, so I want to talk about one more thing, and that's animation.
And the best place to do this is to go back and do a demo. Now, instead of writing all the code this time, I actually have the code already all written, and I'm just going to run the application and show you how it works. There's a few just -- a couple key concepts to keep in mind as we're working with animation, and I just want to point those out.
So here's our view. And before, our add to our itinerary button, that box that came down, was not animated. But now you can see it beautifully animates in. So how does this work? In this particular case, the entire application is using the layer backing support that's added enhanced in OS X Mountain Lion, and it's using that layer backing support and core animation to do this beautiful animation. Let's see how it works.
So I'm going to go back to the implementation for our OS X detail view controller, which is this guy right here. And once again, I'm going to use the jump bar to navigate to our open method. So just a couple key concepts to take into effect here. When you're animating, using core animation on OS X, and the same with iOS, the concepts are the same.
The first thing you're going to want to do is set up the constraints for the layout that you want, and then critical piece here, you're going to call layout immediately. So you're going to set up all the constraints, it's going to layout immediately, and that's going to be your starting position. Then what you're going to do is you're going to run an animation group.
Inside of the animation group, you want to turn this property on. And this says any frame changes that are made automatically animate them. So now we're going to make our changes to our constraints. In this case, what I'm doing is I'm putting the box inside of a container so that it will resize down from zero. At the end of this animation, what I want it to be is I want the box inside of it to be pinned to the bottom so that it fills that full height.
So go ahead and add those constraints. And then critical piece of information again, we're going to call layout one more time. So this will force layout to happen immediately. And because we're inside of this animation block, all those frame changes are going to get caught in the animation and seamlessly animate.
With iOS, it's the exact same thing. So let's jump over to iOS real quick and let's take a look. So if we go back to our other view controller... You can see we're doing the exact same thing. We're setting up some constraints. We're calling layout if needed. We're going into our UIView animate with duration. And in this case, it's already turning on the implicit animation for us. I'm specifying some new constants for my constraints. And then I'm laying out immediately. And what this is going to do is this is going to animate my view for me just by changing the constraints.
So if we launch our application in the simulator, we can see this happening. So if I click on this full screen button here, you can see the map view animates beautifully up to full size. And if I dismiss it, it comes back down. And that's the demo for animation.
So let's read it right here. Just a couple really core pieces to keep in mind here. If you're targeting OS X Lion, you can use the animation proxy for NSLayoutConstraint to animate without using layer backing. And that's a great way to do that. Again, that's available on OS X Lion and OS X Mountain Lion.
If you're using layer backing in your applications, either on OS X or you're using UIViews on your iOS application, you're going to want to use one of the run animation groups or animate with duration blocks. Before you go into that block, you're going to call layout if needed to get those initial positions.
Then you're going to make your constraint changes. Maybe you add constraints, change some constants, whatnot. And then inside of the animation block, you call layout if needed. That's the critical piece of information you need to know as you're doing animation. Layout, enter the animation, do your changes, layout again.
All right. So that was quite a whirlwind tour of some different features. And the take way that I want to go back to is thinking ahead. Again this is really important as you're developing your interfaces. You can have dynamic content at runtime. You can have device rotation or window resize or localization. These are all things that can change your content. So as you're using Interface Builder, as you're using Auto Layout, as you're writing it in code, be thinking about these things. You can use those helpful debugging defaults to help you perceive those changes ahead of time.
If you have more information, you can contact one of our evangelists. They'll be able to help you with all your questions about the frameworks or about the tools. Check out the documentation for OS X and for iOS for the human interface guidelines or for Auto Layout. Again, it's great because the API is pretty much exactly the same. If you're watching the videos or you're going to go watch the videos in a couple weeks from now, check out these two sessions, the introduction to Auto Layout session and the best practices for mastering Auto Layout.