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 has known transcription errors. We are working on an improved version.

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 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, gave 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 like a really nice smooth transition rotation keeps your users in the context of the rotation. So we're going to start by talking about what exactly that means, and, you know, gave 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. your users in the context of what they're seeing and make sure that there's no jarring, you know, 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 may be thinking, well, it was, you know, three-tenths of a second or something. Whatever. But it really is important. You know, 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 I'm going to be talking about. So let's 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.

And the reason that this looks like this is 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 rotation. It's a flowing transition. And 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 you just watching this now had never even imagined some of the stuff going on here. I really never even noticed.

[Transcript missing]

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. Now, immediately when you call set frame, the model value will be updated. So 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. Now, let's assume that 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 be displayed.

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 animate with duration and then call set frame in there. Again, the model will update immediately because we're setting the value on our UI view. 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 UI view.

So, yeah, we've called set frame 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 being rendered. And if we display 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 setShouldRasterize 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'll throw it out.

And then it'll make another one. And it'll copy that one on screen. And then for the next frame, it'll 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. Generally for UIKit applications, it makes sense to use the main screen scale or the views windows screens 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 you loaded it from disk, maybe you downloaded it.

However, 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 snapshotty, 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 snapshotty-like techniques, Josh is going to show you how to make them look even better. Thanks.

[Transcript missing]

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 on 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, but... Anyway, so we've avoided basically having to redraw a lot of stuff, and we've 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, 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 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 tool bar.

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 cross-fading 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.

[Transcript missing]

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 captureSnapshotOfView. It's going to take a UIView of which we'd like a snapshot and return a UIImageView that represents the content of that view. Now, we'll do exactly what Andy showed us in the slides.

And we're going to call UIGraphicsBeginImageContextWithOptions 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 that we'll get a high-resolution rendering when we're on a retina device. And then we're just going to tell the 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 UIGraphicsGetImageFromCurrentContext and then end that context. Then we'll create our UIImageView to represent this. So we're going to allocate a new UIImageView 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 willRotateToInterfaceOrientation is where we want to capture our before snapshot. And we want to do this here because in willRotateToInterfaceOrientation, we're 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 captureSnapshotOfView 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 willAnimateRotationToInterfaceOrientation , 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 view's frame after the rotation by looking at willAnimateRotationToInterfaceOrientation . So we'll get the table view's frame here and save that into our frame after rotation Ivar.

Now we're going to do some setup here that we don't want to have animated. But willAnimateRotationToInterfaceOrient ation 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, you know, the green here basically adds up to the full image. So we're going to have 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 resizing. So we're going to create a resizable image with cap insets resizing mode.

The only catch right now is that this is not in seed 1 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 used 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 one.

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'll 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 going to 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 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 -- 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 crossfading. 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 looks -- Uh-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 we -- So 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, you know, 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 iOS 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 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. 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.