Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2012-240
$eventId
ID of event: wwdc2012
$eventContentId
ID of session without event part: 240
$eventShortId
Shortened ID of event: wwdc12
$year
Year of session: 2012
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2012] [Session 240] Polishing Y...

WWDC12 • Session 240

Polishing Your Interface Rotations

Essentials • iOS • 56:31

Go beyond the basics of rotations and learn best practices for getting maximum performance and smooth transitions between interface orientations. See when and how to use rasterization and snapshotting to create gliding animations, gain insight as to how the render server model works, and understand how some of the built-in apps on iOS get their visual effects.

Speakers: Andy Matuschak, Josh Shaffer

Unlisted on Apple Developer site

Downloads from Apple

HD Video (568.9 MB)

Transcript

This transcript was generated using Whisper, it may have transcription errors.

All right, good afternoon, everybody. Thanks for coming to check out the "Polishing Your Interface Rotation Animations" talk, even if you didn't necessarily know what it was that that meant when you read the title. So we're going to talk a little bit today about interface rotations, and by that I mean the thing that happens when you take your device and turn it from portrait to landscape or vice versa, and you get a nice, smooth rotation transition as it rotates and animates from one to the other. So we're going to start by talking about, well, what exactly that means and, you know, give you a little idea there, but we've got a little more depth to go into. And why you want to do this. And really the reason here is because this is one of those things, one of those little pieces of polish that just makes your app that much better. A key component of iOS landscape and portrait operation is the fact that the transition between the two is super, super smooth and and a really nice, smooth transition rotation keeps your users in the context of what they're seeing and makes sure that there's no jarring fades or jumps when the device goes from one orientation to the other. So it's really important that this be smooth and that the users have a good experience here.

After we're done talking about that, then we're going to talk a little bit about the iOS view hierarchy, the way that animations work and how they get rendered onto the screen, so that we can talk a little bit about how that impacts the way that you're going to design and update your rotation animations.

And then after we've talked about those things, we're going to get into some more advanced topics about some performance issues that you may run into and ways that you can resolve them. If you just go ahead and start out by doing the really simple thing and basically doing nothing and hoping that your rotations will look smooth, Sometimes you may find that's not the case without some work.

So we'll look at what you might change in order to make them smoother. And then if you find that even just optimizing your performance isn't sufficient to get a really great experience, we'll talk about some visual sleight of hand, various different ways that you can trick your users into thinking that they're seeing you doing something awesome on the screen that you're really not doing at all. So, yeah, basically trick your users. It's a good plan. All right, so first, what is interface rotation? Well, let's take, for example, a really common thing that you'll obviously see in most apps on iOS, but starting with this one, the notes app. So when your user rotates their device from portrait to landscape, they're going to see a nice transition, an animation that's going to happen when this happens. There it is. And then, of course, when they rotate back from landscape back to portrait, you're going to get another nice animation that smoothly brings you back. Now, that all happens pretty fast, so you You may be thinking, well, it was, you know, three-tenths of a second or something. Whatever. But it really is important. It keeps your users with the context of what they were working on. It makes sure that they know exactly what from one orientation is still relevant in the next orientation so that they don't lose track of what they were working on. It's just that nice, you know, part of what makes using an iOS device so nice. So I want to slow this rotation animation down a little bit so that you can see some of the things that are happening here. Because at first it may just look like we're actually redrawing all those intermediate frames and doing exactly the kind of thing that you would expect if you really drew every frame at the way that it should look during this rotation. But when I run it really slow, you're going to see that in some cases that's not actually true. So let's take another look with it running just a little bit slower. So partway through the rotation, if you watch the keyboard, you're going to see that there's actually some ghosting of the keys where one key seems to be sort of overlapping part of itself. The reason that this looks like this is because Because the keyboard actually only knows how to draw itself in two different ways, landscape and portrait. And that's it.

For all the intermediate frames of this rotation, we're actually overlaying and stretching the two images, the snapshots of the landscape and portrait views, and then stretching them out at the same time and cross baiting them. The effect about halfway through is that you do see some of that ghosting, but it happens so fast that to the user at speed it really just looks like it's a really smooth flowing transition. I just sometimes stand here and watch this for like an hour and then fall asleep. But I think it's kind of fun. Now, there is a little bit more obvious effect going on in the title bar there, in the navigation bar. You do sort of notice a little bit more of the ghosting in the title of the navigation bar. But even at speed, users generally don't really even notice that. In fact, I would probably bet that most of of you just watching this now had never even imagined some of the stuff going on here and just really never even noticed.

So anyway, that's the kind of things that we do in order to make sure that we're sacrificing some visual correctness in the middle of these animations in order to get really, really high performance, high frame rate rotations. They happen so fast that there's not a whole lot of frames to begin with. If we drop any of them, it starts to become noticeable. So we want to make sure we have full frame rate rotations. I'm going to take that away so you'll maybe pay attention to me a little bit more. So that's the kind of thing we want to build. Now, when we sat down and you look at how you're going to build a rotation in your own app, it may start out seeming kind of complicated because there was a lot going on there and there were many different parts of the screen. And, you know, when you start thinking about it in terms of rotation, that can be a little bit daunting. But it helps to step back and just think about it as transition between two separate renderings of one view. So really all we actually have is a view of the size that it is in portrait and a view of the size that it would be in landscape. Those are the only two things you ever really have to know how to draw, right? Everything else in between, if you're using core animation or normal UI view animations will be interpolated. Or if you're doing more tricky things like snapshots, you still will only have those two views and then you'll figure out how to transition between them. So the whole problem of rotation you can simplify by completely removing the fact that rotation is involved at all. You just forget about that and think about it as an animated resize.

And I think that alone as a starting point really simplifies the problem and makes it easier to think about what you might do. The fact that there's going to be a rotation happening after, you know, during your resize is kind of incidental. The same thing would happen if you were just resizing without rotation. So the other nice thing that we can simplify here is that if we're looking at figuring out how we'd rotate notes, we can ignore a pretty good part of the screen because UI Kit handles a lot of this for us. So, for example, we don't have to do anything with the navigation bar or the status bar. So we can just ignore those because UI Kit will deal with those. Similarly, we don't have to worry about the keyboard because that's already handled by UIKit. So we can just ignore that as well. So what we're left with is just these areas in the middle. And because all we have to think about is how we'd stretch them, it becomes a pretty easy problem. We start out, for example, going from portrait to landscape. And we just have to think about how we'd stretch this out horizontally and shrink it vertically. And all that really requires for something like this is that we get our background image to be a little bit wider and take those two labels on the sides and move them off. shift them off to the side. So the entire rotation animation for the Notes app is really just this. It's resize horizontally and shift those labels. And then when we go back from landscape back to portrait, it's just the opposite. We shrink it down horizontally and bring the labels back in. So something that started out looking like a pretty complex thing, as far as what you have to do in Notes in order to implement that animation is really pretty easy. And in fact, for something like Notes, there's really no tricks that you have to play at all because the background can just be redrawn a little bit wider and the labels can just be shifted. So it all just happens in an animation block. And there's really no trickery that you have to play there. So some of these things, depending on the complexity of your app, may actually be pretty easy.

So that's the kind of stuff that we want to build. I want to talk a little bit now about how the view system on iOS works, how views get rendered, how animations come on the screen. If you already know this, maybe you'll still learn some new things here because we're going to talk in a little more depth. But if not, hopefully there's some interesting stuff here for you. So first of all, this is the thing that truly everybody knows. When you're putting things on the screen in your applications, you've got a view hierarchy. You create a bunch of UI views and you put them into your window and so they draw on screen. Now this is all happening, of course, in your UIKit application itself. Now what you may not know is that there's actually a parallel tree to your view hierarchy that represents very similar data but may not be identical. And that's the presentation layer, the presentation tree. So the presentation tree is a separate parallel version of your layer tree, which is what backs your view hierarchy. But this represents the state that the user likely sees on screen right now. The model view represents the values you've set on the layers. But if you've set animations, the values in the intermediate, in the middle of the animations that are being interpolated are not reflected in the model. The model always shows the final state. The presentation layer shows the current values taking into account the animations on the views as they would appear to the user at the current time. So presentation layer may show the interpolated values in between what you actually set. And even if you knew that, there may be one other thing that you're not familiar with, which is there's actually a third parallel tree, and that's actually in a whole separate process.

So there's a core animation render server, which is actually responsible for rendering your views onto the iOS device's screen. And this render server has another copy of your tree, and this is the render tree. Now, the render tree also always obviously shows the current values being displayed to your user because it's the tree that the rendering is actually happening out of. So if you have some animations going on, the render tree always shows the current state as it's being displayed to the user. Now, you don't in UIKit code have access to the render tree because it's actually in another process, but you can find out the kinds of things that it likely has, the values that it would be displaying, by looking at the presentation tree. The presentation and render trees should always be pretty much in sync.

So let's take a look at how that impacts values that would be set -- or, sorry, how setting values on your layers and your views impacts these various trees. So let's look first at what happens when you just call set frame on a view with no animation block. So let's assume that we've got some views that start out with just a size. Let's say it started out with 25, 25 is the size of our view.

And we call set frame on that view and try to change its frame size to 50 by 50. Immediately when you call set frame, the model value will be updated. If we query back the value from our view by calling view frame, we will find that it thinks its size is 50 by 50. The render tree and the presentation tree, though, have not been updated yet. Let's assume we've done this in response to some user action like a button press or a tap. Once you finish processing that event, UI kit and core animation will commit that change to the render server, and then it will actually be able to be displayed. So everything you change as a result of one user action will all get committed at one time. So at the end of that event, once it's processed, then the two other trees will be updated to reflect the current state because that will then be displayed to the user. So that's sort of the progression when you're dealing with a non-animated case.

So let's look at what happens in the case of animation. With the exact same thing, we're going to assume that our view starts with a size of 25 by 25, and we're going to call set frame to change it to have a size of 50/50, but this time within an animation block, for example, calling uiview animate with duration. And we'll assume that we're going to do a two-second animation just so that we have something that we can talk through.

So let's call animateWithDuration and then call setFrame in there. Again, the model will update immediately because we're setting the value on our UIView. And when I'm talking about model here, I mean, I know that's a little confusing because we have MVC, the Model View Controller. That's not the kind of model I'm talking about here. Here I mean a model layer. It's your UIView. So, yeah, we've called setFrame on this, and so our size is now 50/50 in the model, but our presentation and render trees are still 25 by 25. Now, at the end, again, we're assuming we're doing this based on some user action, so the user action now finishes. We return to the run loop. At that point, unlike in the previous case, our presentation and render trees are still 25, 25.

We've committed the change, but now this is an animated change, so we're right at the beginning of the animation, and the user is still seeing the old size of 25, 25, and that's reflected in the presentation and render trees. Now, let's say one second later we check again. At that point, our presentation tree will be reflecting what's currently displayed to the user one second in, halfway through, it will be about 37.5 by 37.5.

And the same for the render tree. And then two seconds later, once the animation has finished, since we had a two-second duration, everything will be back in sync and the sizes on all of these different trees will be back to 50/50. So that's sort of a basic understanding, hopefully, of how rendering on iOS works. And the reason that this is interesting is because the first way that you will start building your rotation animations is just by making changes inside of animation blocks and having them animate from the beginning to the end. And so if you're trying to query the state of these animations, you can do that by looking at the presentation tree during your rotations. So now to get an idea of how we might do some of this and some ways that we can improve performance during some of these animated transitions, Andy's going to come up and talk about rasterization.

Thank you, Josh. You have animations happening. And whenever you have complex animations happening, complex graphics, the sad truth of the matter is that you have to worry about performance. So this section of the talk is going to be concerned a little bit more with making your rotation animations performant, assuming that you already have them performing the visual effect that you'd like.

Say that you have a user interface that looks a little bit like this. You got these three blocks, they're nested inside of each other as a view hierarchy. When we go to render this, We're going to copy each of these sequentially from the bottom up onto the screen. Orange, then green, then red.

The tricky thing about this situation is that this orange view is mostly covered by the green view. And so by copying the orange view onto the screen and then immediately covering it mostly with the green view, we're actually wasting a lot of time. Now, it's important that we do this in general, copy each of them individually, because they might be moving with respect to one another, because sometimes the green view might be really small with respect to the orange view. But you, the developer, know a piece of information that the system can't necessarily know. Namely, you might know these are going to stay fixed relative to one another for a while.

And that's why we're talking about this now with respect to rotation. It's that often during rotation animations, you're able to say, "Pieces of my interface, I can keep these static while they rotate," or, "I can keep really tight control over pieces of my interface while it's rotating because I know that the user isn't able to interact with this application. I control the whole appearance."

So there's this method you can call on CAA layers called setShouldRasterize. And that causes Core Animation to make what's called a rasterization. When it goes to render this thing, it takes a snapshot of the entire view hierarchy under the layer where you set should rasterize to yes. It keeps that copy around so that when it goes to render, it can just copy that whole thing back on screen. it doesn't have to do orange, then green, then red. And for the next frame, it can reuse that rasterization, which saves even more time.

Now, if you have these views animating with respect to each other like this, then you get a somewhat different effect. Remember I was telling you that this is useful because you know something, you know that these views are static with respect to each other, and that allowed you to take advantage of this rasterization technique where you can say, "Hey, just treat this whole thing as one block. Take a snapshot of it effectively and use that instead of dealing with the layers individually." Well, so if you have an animation like this, and you try to set should rasterize yes on the orange view, then Core Animation will make that rasterization, copy it on screen, but then for the next frame of the animation, because that red block has moved down a little bit, Core Animation will say, oh, that rasterization that I have, it's not valid anymore. It's not an accurate representation of the layer tree, so it will throw it out.

And then it will make another one. And it will copy that one on screen. And then for the next frame, it will have to throw that one out, too. Every frame, creating one, throwing it out, doing even more work than it was doing before you tried to set should_rasterize onto the orange layer. So be careful where you set it.

I told you that you might want to set it during your rotations when you know that you have some part of the view hierarchy that's fixed and you can use this optimization for. So you might say, okay, in will animate rotation to interface orientation, I'm going to set should rasterize yes on this particular view hierarchy.

I encourage you to be very careful doing this because you will want to disable rasterization after the rotation finishes so that once your user is able to once again interact with your interface, thus presumably changing around the way that it looks, you don't want to inadvertently have that problem I was just showing you where core animation has to repeatedly create these rasterizations and then just throw them out. So turn it on, and then at the end of the rotation animation, turn it off again.

If you have a rotation that looks like this, unfortunately what that means is that rasterization is of no use to you. But hopefully we'll be able to talk about some other things in this presentation that will be. There's one other interesting issue with rasterization that you need to be aware of, and that's, if we're taking a snapshot or something like a snapshot, at what scale, at what resolution should we take it?

It's really kind of usage-dependent. So if we have a label like this one, and say it's 75 points high, well, if you're an LLDB and you ask this label how high it is, it doesn't say, "Oh, I'm 75 points high," it says, "I'm 75 high." It's kind of not that clever. It doesn't know the unit. And so when you ask this layer to create a rasterization, it creates a 75-pixel rasterization. And critically, it does so even on a retina device. What that means is that on a retina device, when you blow it up, it's going to have jaggies. We don't like jaggies.

There's a method you can call called setRasterizationScale that specifies to CoreAnimation at what resolution would you like this rasterization to be made. Now, generally for UIKit applications, it makes sense to use the main screen scale or the ViewsWindowsScreens scale. So if you do that, then it'll actually create 150 pixel tall rasterization rather than a 75 pixel tall rasterization. And then the jaggy situation is substantially improved.

there are some memory implications as well. We're talking about making a cache effectively and as with all caches, they have memory implications. So if you have a user interface that looks a little bit like this where you have an image view and then a sub view that's a label, you certainly need to have space to store that image. It's a UI image of the coliseum. Maybe It's stored in memory. But if you set should rasterize on the parent of the UI image view and the UI label, then you should be aware that you also have the memory cost of that rasterization, which in some cases may be as large as the image itself.

Now that we've talked about using this snapshot-like tool for performance-- Let me talk about using snapshots for snapshots' sake, because they're actually really cool, and they're generally useful in rotation animations, especially for the kind of slate-of-hand tricks we're going to be showing you later. If you have an interface like this, when it rotates, watch the red square.

Do you see how it actually appears on screen in two places at once? In order to do that, we basically need to have one view duplicated. But unfortunately, UIView doesn't conform to NSCopying. And even if it did, that might not be a very effective way to achieve this result because maybe your views point to a particular data source that would be very confused if it got queried twice as often. It's not prepared for that. Or maybe it's just very complex. So I propose that in many cases, it's more convenient to simply take a snapshot of the red view to achieve this result.

And you can do this with a piece of API. If you have a layer like this red view, you can send it render in context to have it draw into a CG context. You can create a CG context into which it might draw using these UI graphics functions. UI graphics begin image context with options.

Note that here the scale is yet again relevant. Here we're using zero for the third argument to begin image context, and that indicates to the system that the image created should be at the scale of the main screen. In this case, on a high-resolution device, that would be 2x, and that's what we want to avoid those jaggies. So now we have an image, and we can manipulate it as such.

There is the question, however, of what exactly are we taking an image of, particularly when we have an animation? So if we take a snapshot while that red box is moving, what happens will depend upon whether we're taking the snapshot of the model tree or of the presentation tree.

As Josh mentioned earlier, the model tree always contains the final values. And the model tree is the one that your layers actually refer to, your UIViews layers. So if you just send rendering context to the orange layer directly, then what you'll get is a snapshot of it at the end of its animation, no matter where in the course of its animation you take the snapshot. That may be what you want, but it may be more expedient for you to send rendering context to the presentation layer, and that way you'll get a snapshot mid-flight, because the presentation tree always contains at least an approximation of what the user is seeing on screen.

There are some performance characteristics that you should be aware of. Although rasterizations and snapshots sound like mostly the same thing, they're both snapshot-y, the way that they work, technically speaking, are somewhat distinct. Critically, rasterizations are performed by the render server. You say, hey, I want this layer to be rasterized. I want you to effectively take a snapshot of it and use that instead of dealing with the layers individually. The render server, when it renders, does that. So that's not happening in your process.

It's not blocking the flow of application events. Sometimes that can be useful, but it could also mean that you're now blocking the render server. So it's an interesting thing to be aware of. The render server, though, knows how to use the GPU. That's valuable. When you do it within your own application, when you take a snapshot, you're going to be using the CPU. So you say layer rendering context, and that thread, the one where you say rendering context, which incidentally, if you're sending that message to a UI views layer, had better be the main thread, is going to be blocked until that rendering is complete, which could be quite a while, especially on a new iPad. On a new iPad, snapshots of the entire screen are 10 megabytes. So consider even the cost of just copying 10 megabytes of data around, let alone the fact that those 10 megabytes of data come from a detailed rendering process. There are costs associated with these snapshots.

But if you're aware of these characteristics, between your knowledge and your usage of instruments, you should be able to figure out the best thing to do. Now that you know how to take snapshots and how to make your rotations a little faster with snapshot-y-like techniques, Josh is going to show you how to make them look even better. Thanks.

Thanks, Andy. So to get into things that you can do with these snapshots and interesting sleight of hand tricks that you can play on your users, we're going to take a look at another app that ships as part of iOS. And that's the Mail app. So we're all familiar with the Mail app, obviously. But you may not necessarily be familiar -- oh, feel free to read my e-mail. That's fine.

Yeah. You may not necessarily be familiar with how the Mail app actually does these rotations. So of course, again, we have a portrait view, and then when we rotate the landscape, we've got a landscape view. Now, the interesting thing about these two views is that they are actually pretty similar in that they both have a list of emails, and the row heights of these emails is roughly the same. In fact, it's identical. So there are a lot of similarities between the two views that we can take advantage of when figuring out how to transition between them. What we've got here is the view in landscape. If we did nothing else and we just rotated back to portrait and did just a normal UIView animation during that rotation, what you would find would happen is that the views would actually redraw themselves right at the beginning. Now, the reason for that is because these views that are displaying this text actually implement drawRect.

So a view that implements drawRect, when you animate it, will, at the beginning of the animation, always redraw itself for its new content size. So when rotating back to portrait, right at the beginning, if we did nothing else, we would see the views snap to their new size like this. And then the rotation would begin.

So you would see a really jarring snap right at the beginning of the animation from the full width text in portrait to the landscape -- sorry, landscape to the portrait size, and that big white area would open up, and then things would sort of slide towards each other and the area would close itself. So it would be a pretty jarring initial experience as soon as you rotated your device. What's worse is that depending on your content mode, you might end up seeing the content stretch horizontally in a way that would be really not very pleasing. You could fix the stretching thing by using a correct content mode, but it's still -- it's not really the best experience you could be providing. So it would be really nice if we could take advantage of the fact that these things look pretty similar to crossfade some snapshots instead that looked a little bit better.

So on the left-hand side here, we've got some area in -- this is the part where I said it would redraw itself. We've got an area that's roughly the same size as it's going to be in portrait that looks pretty similar in both portrait and landscape. You've got the name of the person who sent you the e-mail, you've got the title of the e-mail, and a bunch of text, the message body. And that part looks pretty much the same as it's going to in both landscape and portrait.

So that part on the left, we could probably just overlay during the rotation and leave it pretty much the same, the only thing that's really changing in that area is the bottom row of text in the detail because there the text of your message is wrapped so the bottom row changed a little bit. But everything else is the same. Similarly, on the right-hand side here, those chevrons, they're at the same spot in both landscape and portrait, so they're not going to change at all. In fact, the only part of this content that's really changing dramatically during the rotation is this center part here marked in red. else is pretty much the same. So we could actually take advantage of this by taking two snapshots, the before and after, and stretching only the part in red while we just crossfade the other parts. And that would make a pretty minimal amount of fading with just a small amount of stretching in an area that's kind of off to the side, away from the main focus of the content anyway.

And in fact, that's exactly what the mail application does. So if we take a look again -- I've slowed this down so that you can fall asleep here in the next few minutes. If you watch the right-hand side that we had marked in red, you're going to see that that part where the labels are actually stretch and fade out a little bit during the rotation from landscape to portrait, and the opposite happens. It fades in on the rotation from portrait to landscape. The parts on the left are cross-fading, but the only part on the left that we can even notice that there's a cross-fade at all is the second line of text because everything else remains identical. Nothing's changing there. You do kind of notice a little bit at the bottom where the text crops a little bit right at the beginning.

But, again, this is happening so fast that users mostly just never even notice this. Hopefully this is something new to you. If you've noticed it, then maybe it's not worked as well as I was hoping. Anyway, so we've avoided basically having to redraw a lot of stuff, and we fixed a problem that wouldn't even be possible to fix without snapshotting in any really high performance way, because we can't draw every frame of the text rewrapping. That would be really expensive to rewrap the text at every frame of that animation. So, yeah, we've just -- this entire animation really in the middle is just two snapshots, cross fading and stretching that part that we had highlighted in red. So what we're going to do next is write a a little sample application that does basically exactly that. And it's going to look a little bit like this with slightly less interesting content. So we've got, again, just two views, our landscape and portrait, and it's basically the same layout as the Mail app. We've got a bunch of rows of text, and we've got, you know, navigation bar and toolbar.

And we're going to do that same thing, because we've got the area on the left that will remain the same, the area on the right that will remain the same, and the area that we're going to stretch. We actually, as I mentioned, are going to take two snapshots. So we're going to also take a snapshot of the portrait view, and we're going to stretch that in much the same way. But because that's actually the full width of the thing we're trying to stretch to, we need to find some part of it that we can stretch. And I'm not sure how visible it is there, but there's a red line just to the right of that portrait snapshot, just to the left of the chevrons. which is really -- basically, we can find one pixel there that's pretty much all white except for the separator rows that we could stretch out as we expand this horizontally, and the user would never really notice that because it's all white, so it's just going to fill some empty space. And we'll be crossfading it with the landscape one simultaneously anyway. So let's take a look and just focus on that because, again, as in the other case, we don't have to worry about the toolbar or the tab bar or the navigation bar. So we're really just focusing on this part of the content, the table view. And again, if we did nothing, we would end up with something like what we saw would have happened in Mail, where at the beginning of that animation, it would just snap to its new appearance, and that's definitely not what we want because we'd see that big open white space. And then again, if we hadn't done something, we would see it animate towards the portrait view in somewhat of a way kind of like this. Just doesn't look very good. And then on the way back, it would actually be slightly worse. It would redraw itself and then be clipping off on the right as it sort of comes out from under the chevrons. It's a very strange effect and definitely not the best we could do. So instead, we'll do those two snapshots, and we're going to crossfade and rotate them -- or, sorry, crossfade and stretch them, and it will look instead something like this.

So the right side with the chevrons is just sort of sliding in towards the other side, and the left side is minimally changing. We just got crossfade of the bottom lines of text, and there's a little bit of stretching happening there on the right side. But for the most part, most of the content is remaining pretty much still in place. And when we do this at speed, users pretty much won't have any ability to tell that there's anything happening here other than what they may actually have convinced themselves is really supposed to be the correct rotation animation. So let's build that.

All right, so I've set up a little sample project that is basically just creating our initial state. We've got a text -- sorry, our table view showing those rows, and I've got a toolbar and a navigation bar. Now, I've set this up in a way that's a little bit different than what you'd likely do.

I'm not using a navigation controller at all, so -- and I'm not using a table view controller. I just have a nib that has a navigation bar and a table view. So what happens without any other additions, then, is that when I rotate to landscape, my bars haven't actually resized themselves for landscape. They should be a little bit shorter. Now, Navigation Bar -- or, sorry, Navigation Controller, UI Navigation Controller, handles that for us.

But because we're not using it in this case, we have to do it ourselves. I've actually used constraint-based layout in this program, which makes it really nice and easy. I've set up a constraint to keep my toolbar pinned to the bottom and my navigation bar pinned to the top. So all I have to do is invalidate their intrinsic content size. And I can do that by, in willRotateToInterfaceOrientation, telling the navigation bar and the toolbar that their intrinsic content size is now invalid. And really with that one change, we've fixed the entirety of the problem with the sizing of the bars. So now if we rotate, we're back to the shorter bars and landscape that we expected and everything looks good. Our constraint-based layout has made sure that the content of our table view has increased vertically -- or, sorry, decreased vertically to the right size and everything fits in the correct place. But we have actually introduced another problem that you'll notice more if I slow things down. If you're not familiar with this, there's a toggle slow animations option in the debug menu to slow down your animations. So we can see our rotation -- oops, not that slow. That slow. So our bar at the bottom, you can see our buttons are going a little bit crazy in a way that we definitely don't want. This would, again, normally have been handled if we had been using UI Navigation Controller, but since we're not, we just have to tell the UI View Controller that that bottom bar is special and that we'd like it to snapshot and crossfade it for us. And we can do that with just one line of code. So we'll just add one new method to our rotation view controller that we have here, and that will be implementing a method called rotating footer view. Many people have asked what this method is for. It's maybe not entirely clear, so hopefully this clarifies it. When we implement this and return the toolbar from rotating footer view, that tells UIKit that we would like UIKit to snapshot that toolbar and rotate snapshots for us instead of doing the normal animations. So now when we slow down and see it, we're actually going to be able to tell that there's some snapshot and crossfade going on in that bottom bar. So the fact that those buttons didn't know how to animate their own size changes no longer really matters because we're just rotating some snapshots. So if you've ever wondered why rotating footer view exists, that's the reason. There's also a rotating header view as well that we could use for the navigation bar, but in this case, our nav bar is doing just fine, so we don't really have to do that. All right, so that leaves us with figuring out what to do with this main content in the middle. And as you can see right now, we get that jump right at the beginning as our content redraws.

And on the way back, it redraws, and then we see it sort of slide out from the side over here. So let's implement that snapshotting to fix that. In order to do that, we'll need a few IVARs to track our state during this rotation. So we're going to add a couple of IVARs to track the frames of that table view that we're going to be snapshotting before and after the rotation. And then we're going to create two IVARs to track our snapshots, UI image views, right before and after the rotation.

Now, before we actually get into implementing the rotation itself, let's create a helper method, actually a helper static function that will take a snapshot of an arbitrary view for us using the technique that Andy showed us a minute ago. So I'll call it capture snapshot of view. It's going to take a UI view of which we'd like a snapshot and return a UI image view that represents the content of that view. Now we'll do exactly what Andy showed us in the slides. We're going to call UI graphics begin image context with options and pass in the bound size of the view we want to snapshot. We'll pass yes for the opaque because our table view is fully opaque, so we don't need an alpha layer here. And finally, we're going to pass zero for our scale so we'll get a high-resolution rendering when we're on a retina device. And then we're just going to tell the target layer -- or target view, tell its layer to render in context into the current graphics context that we just started. Just like Andy showed. We'll get an image out of it using UI graphics, get image from current context, and then end that context. Then we'll create our UI image view to represent this. So we're going to allocate a new UI image view with that image we just created. We're going to set its frame to match the frame of the target view that we're trying to snapshot. And then we can return it from this function. All right. So that's going to be used so that we can get our before and after snapshots. It was helpful to factor it out since we're going to call it twice. So now we can actually begin implementing the snapshotting. So in -- it will rotate to interface orientation is where we want to capture our before snapshot. And we want to do this here because in will will rotate to interface orientation were called before any of the changes for the rotation have been applied. So all the views are still in their state for the previous orientation and nothing has been resized yet. So that means that we can capture our table view's original frame, the frame before rotation, by just getting it here. We can capture the initial snapshot by calling our function capture snapshot of view here. That will give us the snapshot and frame before. And then we just need to add that snapshot into our view hierarchy to get ready for this rotation. So we're going to insert it above the table view because we want it to be on top. So snapshot before rotation that we just created, insert into the view hierarchy above the table view. Now that we've got that, we can actually go ahead and start implementing the frames after the rotation. So in "will animate rotation to interface orientation," all of the views in your hierarchy have already been resized for the new orientation. and the status bar orientation has already been updated. So everything is now in its final state as far as the model is concerned.

So we can go ahead and capture our table views frame after the rotation by looking at will animate rotation to interface orientation. So we'll get the table views frame here and save that into our frame after rotation Ivar. Now we're gonna do some setup here that we don't wanna have animated, but will animate rotation to interface orientation is called from within an animation block, the one that UIKit set up to actually resize all our views during rotation. So we just want to temporarily disable animations by setting animations enabled to no. Once we've done that, we can actually take our snapshot, again calling the same capture snapshot a view method on our table view, and that's our after snapshot. Now, we're not going to insert it into the view hierarchy just yet. We'll get to that in a minute. But what we do want to do is resize it horizontally so that it has the same width as the initial view did at the beginning. and I've actually written a helper method for that. So we're going to call setFramePreservingHeight, passing in the frame before rotation. Now, this is basically identical to setFrame, with the only exception being that it doesn't change the height of our view. We don't want to stretch these snapshots vertically, so we want to make sure not to resize them vertically. So this will set the origin and will set the width, but will not set the height.

So we've got our snapshot now. It's our right size. It's the after snapshot, but stretched horizontally down to land on top of the size of the initial snapshot. So they're now layered on top of each other on the left side of our view. So next we need to figure out which parts of our snapshots we want to stretch during this rotation. And if you recall, we wanted to do something like this. We want the right side here to not stretch and the left side in green over here to not stretch. And the same thing in our portrait. The right side over here should not stretch, and the left side there should not stretch. We will allow stretching in this red part here and on the single point width thing here.

So I've actually written a little helper method to calculate those two side things. It's just a little bit of math. And it's not especially interesting here only because the way that you'll decide which parts of your content can be stretched and which ones can't will vary based on what kind of content you're talking about in your own applications. So for the purposes of the demo, I have a quick thing that will calculate edge insets for us. So UI edge insets represents top, left, bottom, and right inset. And so I've got a method here that just calculates those green insets, the left and right ones. Now, the interesting part is that it will basically always be in sum, the image with the smaller width. So we have two images, we have the final and the start, with landscape and portrait. Whichever one is smaller is the one that we're basically going to have be the total sum of our left and right insets. Because the green here basically adds up to the full width of portrait. So anyway, we've now got our UI edge insets representing the areas that we don't want to stretch. So now that we know what we don't want to stretch, we want to modify the UI images that we captured, the snapshots, to indicate which parts should be stretchable.

So we're going to get our images out of those two UI image views by calling image on the UI image views that we have, and we're going to create stretchable images out of those two UI images that we have here. Now, I'm using a new method being introduced in iOS 6 here to do this called resizable image with cap insets resizing mode. The only catch right now is that this is not in seed one that you have. So I've used it here because it's actually quite a bit easier to do stretching using this method. There are other ways that you could do this stretching on your own using other mechanisms.

But for simplicity's sake here, we're going to use this. And you will have it at some point. But in the meantime, yes, we've created a resizable image with cap insets with that unstretched area that we calculated. With the resizing mode being UI resizing mode stretch. Now, that's the new part in this API is the resizing mode. The default if you didn't pass that or if you use the existing resizable image with cap insets is to tile, not to stretch. So we wouldn't end up getting it stretched the way we'd like. And anyway, now that we've got our images created with the correct stretchable regions, we can just set them back on that UI image view. We've got our snapshot before and after. Set the image back on there.

So next we have two things left that we want to do. We've got things set up so that we have the images created and they're set to stretch in the right ways, but now we need to actually set up the animations. We want to crossfade the two views and we want to stretch them horizontally. So for crossfading, we always want to fade the one that is shorter. The reason that we want to fade the shorter one is because if we were to fade the taller one, you'd end up seeing at the bottom an area where it became translucent and you could see through to what's behind it. If we always fade the shorter of the two, you'll only ever see the taller one behind it. So it preserves the illusion that we're actually rotating real stuff.

So in order to do that, we have to pick the shorter image. So we've got our two images, image after and image before. We'll check their sizes and figure out which one's shorter. Assuming that the image after rotation is shorter, we want to put the image after rotation -- this is what we're rotating to -- on top of the image before and fade it in. So we're going to have the image before as what's visible. We're going to have the image after on top, and we'll fade it in on top while we rotate. So to do that, we have to start with it faded out. So we're going to set its alpha to zero. Then we're going to insert it on top of the image before, so insert subview image after, above subview image before, so we've got the after one we're going to on top. And then we want to fade it in, so we'll set its alpha to 1. Now, if the image before rotation, our initial image, were the shorter one, what we'd actually want to do then is fade it out. We want it to start visible and then start disappearing. So for that to work, we have to put the final snapshot, our image after, underneath it.

So we're going to insert it into our view hierarchy, Insert snapshot after, below subview snapshot before. This is why we didn't insert this when we first created the snapshot, because depending on which one's shorter, we want one on top of the other or vice versa. Now, of course, we want to fade out the image before because it's on top right now, so we'll set its alpha to zero, which will fade it out and cause us to see the final version about halfway through our rotation.

So that's it for fading. The last thing we have to do is stretch them horizontally. Right now, they're both positioned right on top of each other on the left side of our view. We want to stretch them horizontally throughout this animation. The animation for this for the snapshot after is really easy because we already have the frame for that. So we'll call the snapshot after frame, set its frame to the frame after rotation. This basically sets the final snapshot's frame to match the final table view's frame. So that will stretch it out horizontally. Now we want to do the same thing for the initial snapshot, the snapshot before. But like we did before, we only want to stretch it horizontally.

We don't want to stretch it vertically. Because if we stretched it vertically, you'd see the text stretched out vertically, and that would look pretty bad. So we're going to use that same convenience method I created before, set frame preserving height, and we'll pass in the same frame after rotation, because we do want it to have the same width, but we'll keep its original height. All right, now there is one last thing we're gonna do. Oops, I have failed miserably in that I've put this above our animations enabled, so they would not actually animate. Wow, I've done this for everything. Let's go ahead and move all this down here.

That would have been surprising when I went to run our demo. Then the last thing we've got here is that we actually have our table view still in the view hierarchy and still visible. Now, our snapshots are on top of it, but they're fully covering it, so it's a little silly to have the table view in the hierarchy at this point, only because, you know, Core Animation will have to figure out that it shouldn't be rendering it. We could tell it that. So we'll tell it that by just setting it into yes, make sure that our performance is as high as possible. The last thing we want to do is clean up after ourselves and did rotate from interface orientation. So we remove our two snapshot views from the view hierarchy and nil them out. I'm using arc here just so you don't think I'm leaking views all over the place. I didn't call release because arc's cleaning up for us when we set our IVARs to nil. And then we're going to unhide our table view so that we actually see it again in the end. And after all of that typing and frantic reorganizing of code, we should now be in a case where when we rotate, We see the whole thing -- oh, right, sorry. I did this right before the demo, so I now know what's wrong. I have commented out my code so that it wouldn't be throwing errors before. That was the method that's returning us the edge insets. I had written it before but left it commented out. Let's uncomment it so it works. Now when we rotate, if we watch over here, we'll see things stretch just in that area that was marked red, and everything else will just crossfade nicely. The chevrons will kind of slide in towards the views on the left, the areas on the left will slide towards the chevrons, and there's kind of a minimal amount now of stretching and cross fading. It's about as close as we can get to what it really should look like without having to really draw every intermediate frame. Now, there is one last thing. So we can scroll around now and rotate and everything will -- oh. So we've got some black areas up there. That doesn't look too good. What happened there is that when we called render in context, it actually was the case that since we're in a scroll view and the thing we're trying to render is a scroll view, we had scrolled our content up, we captured the origin of the content in the table view, 00, but that's scrolled way up off screen now and there's no cells there anymore because it's off screen. So we ended up seeing a big black area where there would have been cells at the top of our table view if it were actually scrolled all the way in. So we need to make sure that when we render this table view, we capture just the part of it that we want. So before we call render in context, we can just shift our graphics context by the amount that we scrolled to make sure that when we capture it, we render the right part at the origin of our image that we're trying to snapshot. So we can do that by translating the context transformation matrix. So we'll call CG context translate CTM, translate the current transform matrix on the current graphics context, and the amount that we want to translate it by is the exact same amount that we scrolled by. And the amount that we scrolled by is just the bounds origin of our target view, which is the table view. So we'll translate by the negative of the bounds origin dot x and the negative of the bounds origin dot y to make sure that we're capturing the right part of the table view and not getting black areas where there's no content. So if we do that and then scroll down a little bit and rotate, now we're actually capturing the right part and we can still get a correct snapshot and everything looks good.

So we say all this stuff not to say that, like, these are the techniques in order to do rotation. We're not trying to say that snapshotting is, like, the way to do it. This is more trying to give you an idea of the kinds of tools that are in the toolbox that you can use when thinking of how you should build your own rotation animations. Some of these techniques may be useful in your apps, but if they're not, then, you know, move on and try something else. Not all of them are, you know, the right answer for every situation. So to get back into some of the more honest rotation animations that are really doing the real things, as opposed to the ways that we're tricking our users here, Andy's going to come back and talk a little more about performance characteristics and tricks. - Josh has lied to you. What I'm about to show you is a series of hideous hacks.

Masks are slow. This is why they're slow. When we go to draw them, say that we have a view like this that's masked in a star shape, we render the entire view, the unmasked version, into an off-screen buffer, and then in each frame, copy the bits of that buffer on screen, evaluating the mask to make sure we only copy the right bits on, and then we throw the buffer away.

We do that every frame. Make the buffer, copy, throw the buffer away. No good. But rotation, like the rasterization thing we were talking about before, is a place where you might be able to control the situation you're trying to mask. So you might be able to use setShouldRasterize, like I showed you before, in order to say, "Hey, this thing that I'm masking, Nothing inside of it is going to change during the course of the animation, so at least don't throw that off-screen buffer away, please.

If you can't guarantee that things inside of the masked hierarchy are not moving, then you might be able to take a screenshot of it, perhaps sans a view, and then move the screenshot around and move one of the views around on top of it. But the aforementioned hideous hack is actually this one. Maybe you can draw what's underneath the masked view hierarchy back on top of it. In this case, that would look like this. So you actually don't mask anything. you create another view that has the background and has a hole in it, and then you draw that on top. You probably can't get away with doing that in general because usually the background is going to change. But during a rotation animation, you might have enough control, you might be able to arrange things so that you can. That would be pretty cool. I have a piece of news for you. This particular kind of masking, which you get by calling setCornerRadius on caLayer, is now much, much faster on Iowa 6.

It's still kind of slow. It has to be. I have more hideous hacks for you. Maybe you don't have to do all of the complex animations that you think you have to do. During a rotation, part of your interface is going off screen. The corner is going to be cut. Maybe the corner doesn't matter. I'd like to show you a demo. The fun part about this demo is that I'm not actually going to write any code. So I'm going to make a new project that's just the default master detail application template.

Build and run. No code changes. I'm going to show you what split views on our system do. It's kind of heinous. So I rotate. Split view looks good, right? I think it looks pretty good. Now watch over here as this navigation bar slides. Yes, that's right, slides into place and out of place. We were able to get away with doing that because this region of the screen mostly goes off-screen during the rotation. So there's really no need to do anything more than take a snapshot of the 768 pixel high master view and move that around, which is extremely cheap, because at that point, Core Animation is just sliding a quad around the screen.

It's not doing anything fancy at all. And you probably never noticed this. I encourage you to use such techniques within your own applications. I encourage you also to bother Jake Behrens with any questions you might have because he is here to help you. Furthermore, you should bother each other on the developer forums.

And I'd like to review a few points that we've talked about today. First one, most importantly, let UIKit do things for you. Much of this is hard, and we've done much of it for you. So there's the rotating header view thing. There's the rotating footer view thing. Status bars rotate for you. The keyboard rotates for you. Let us do that. When you have to do your own rotations, Maybe you can tell the system that a certain part of your screen can be considered as a unit for the course of your rotation. That was the rasterization bit.

And then maybe you can use some of the same techniques to fake things or to make better animations by taking snapshots, which may be slow, especially on new iPads, so make sure you evaluate the performance characteristics. You can get some really cool animations by stretching only part of those snapshots. That's a technique we really like. There's some new API in iOS 6, not in this beta, but soon, that you'll be able to use for that.

And finally, simplify. The theme of this whole talk has been, this is pretty fast. Maybe you can get away with doing something a little less faithful. than you might think you had to. I hope this has been helpful. I hope you'll create even more amazing applications. I hope you'll wow us with your interface orientation transitions. And I hope you have a great time with the rest of your week. Thank you.