Best Practices for Cocoa Animation

Session 213 WWDC 2013

Smooth animations and great performance are key to developing a state of the art app. Learn best practices when using animations with Auto Layout, see unique animations in OS X and understand how to create similar effects in your apps, and gain insight into common pitfalls.

[ Silence ]

Chris Dreessen: Good afternoon.

[ Applause ]

Welcome to session 213.

This is best practices for Cocoa Animation.

I'm Chris Dreessen.

I'm an AppKit Engineer so I work on this stuff.

We have a lot of great things we'd like to show you here.

So I'm going to give you a basic overview of this.

So I said a lot of great things, I meant a lot of great things.

I'm going to start with a brief overview of how Animation in Cocoa works, how to use it.

We're going to continue to describe how to make your own view subclasses animatable for your own properties and describe how to augment that with context sensitive animations.

I'm going to share with you a technique I like to call chaining animations where we use the completion of one animation to start off another animation.

Some of you are familiar with the implicit animation functionality of NSAnimation Contest we added in 10.8 and I'm going to cover that.

And then I'm going to cover a bit of Core Animation, not how you can use Core Animation but rather how NSView uses Core Animation to do animations for layer back use.

At that point I'm going to invite my colleague Peter Amman onstage and he's going to talk about this great new class called NSStackView.

He's going to describe how you can use auto layout to animate view positions, how you can animate those constraints directly and additionally best practices for animating window size changes.

So throughout the course of this presentation I'm going to be talking a lot about the NSAnimatableProperty ContainerProtocol and that lives in the NSAnimimation.h header.

And it's a simple protocol, it's just five methods but I'm going to be coming back here a lot and we'll touch each of these one by one.

So basic animations, most of you are familiar with this but this is how you animate basic things for NSView.

So back to NSAnimatableProperty Container, I said we'd be coming back here a lot.

I didn't lie.

The method I want to introduce to you here is the animator method which returns to you the thing we call the animator proxy.

And this is an object that implements all the same methods that your object does.

The difference is when you message it it will animate them instead of just popping to the final location.

So suppose you want to fade the Alpha of a view.

You want this to disappear?

Well that's not too bad, that's just one line of code.

We set the Alpha value to zero through the animator proxy, view.animator.alphavalue=0 and that's all it takes.

And here's something more complicated.

This is, we're sliding, that's got to be way more code, right?

Same thing, view.animator.frameorigin, just one line of code again.

Okay here's one more, we resized it.

That's two dimensions.

That's got to be four times the work and it is four times the work.

It's also one line of code.

[laughter] Alright so you're all familiar with that.

I want to talk about how to make your own views animatable now.

So suppose you want to do an animation like this.

We have a framed view which is a single red line on the outside and we change the line thickness, we grow it.

So your view might look like this.

You're going to declare a line thickness property, it's a float.

And you're going to go in and drawRect and this is a simple drawRect.

We just set the red color and then we frame our own bounds with a rectangle of the appropriate line thickness.

And additionally in our accessor for setLineThickness we call setNeedsDisplay yes, after we've updated our I-bar.

So I told you, you know, you can just use the animator proxy, that works fantastically.

And if we try this we see this animation.

And technically that was an animation.

Things changed on screen.

But I don't think any of us are really satisfied with that animation.

So what was missing?

Well back to the NS Animatable Property Container Protocol.

There is this method called defaultAnimationForKey.

It's a class method.

And that is our missing piece here.

And to make this animatable we're going to go ahead and implement the defaultAnimationForKey method.

So our implementation we start by checking what key we're actually talking about here.

In this case it's our line thickness property which is great.

And we return the default CABasicAnimationObject and CABasicAnimation is one of the animation types we use most frequently.

It just has very simple from and to values along with some properties that control how those values are interpolated.

And of course if it's not the line thickness property we want to fall through as a super so the other view properties can continue being animatable.

And just by adding that if we do view.animator.linethickness again we get this animation which is just what we wanted.

So that's still pretty simple so let's complicate things a little bit.

Sometimes you need to know more information that you can provide in a class method.

You need to know what specifically is being animated.

And you can do that too with MS Animatable Property Container.

There is a method called setAnimations which takes a dictionary, the keys in the dictionary are the properties that are being changed and the values in the dictionary are CAAnimationObjects describing how you want us to change them.

So this is our method here and suppose we wanted to snap to multiples of 10 for our line thickness as we did our animation.

We can't really do this here.

There's no way of specifying.

We don't know what the from and to values are inside of the class defaultAnimationForKeyMethod.

So the way we fix that is we're going to use this thing called CAKeyframeAnimation and the key frame animation is another animation type we use a lot.

And in it you can, well basically you specify values to the key frames and you can interpolate between those or just have it use those discreet values at different time stamps.

And in this case we're going to populate it with steps from 0 to 40 moving by 10 each time.

So once we create our animation we turn it into a dictionary and we set it on the view.animationsproperty and that's our new objective C dictionary syntax there.

We specify the key as line fitness.

The value is our key frame animation.

And then we just talk to the animator like we always did.

View.animator.linefitness=40 and we wind up with an animation that looks like this instead, which is exactly what we wanted.

So that's kind of cool.

But there's a few other things you can do with key frame animations that are neat.

And I mentioned you could, you know you have your key points in there that have specific values.

You don't need to interpolate between those values.

And in fact with some types of values you can interpolate.

So in this example I'm setting these values to an array of images of various Apple products and the same as before, I set the animations dictionary there using our new animation for the image property, in this case this could be the an NSImageView which implements an image property.

And I just tell the animator hey, change the image to this final Apple TV image.

And something I want to point out, my key frame animation specified five different types of Apple products but it didn't specify Apple TV.

And the reason for this is that the value you tell your animator proxy is the value that's going to wind up in the object ultimately.

The stuff in the animation is just used along the way.

So if we see what this looks like, recycling nicely through the Apple products in our image well, and we end at Apple TV.

And you can do this with other things.

You don't need to do it with just images.

You can do it with string values, for example.

In this case we have a text field.

Our values are various city names.

And again, if we set the animations dictionary on our text field using string value this time as the key and our key frame animation again as the value, and finally we tell the animator, hey your string value is San Francisco because we want to wind up in San Francisco and all of you here have already completed the step, good job.

So we see what that looks like.

It animates our text field just like we expected.

But you can do a few more things.

You don't need to restrict yourself to key frame animations.

So if you're familiar with NSCell and NSControl, especially NSTextField, you're familiar with these things we call formatters.

And formatters are objects which take an arbitrary object type and return a human readable string value.

And in this case I'm going to implement my own formatter class, in this case the cleverly named my formatter but we're going to delegate most of our actual formatting work to NSDateFormatter.

So our string for object value method looks like this, we take in an NSNumber and we just ask for its double value.

And one of the ways we represent dates on our system is as the number of seconds since January 1, 2001, that's the reference dater, in this case the time interval since the reference date.

And we can construct a new date from that double value representing the number of seconds.

And then we just plug this into our NSFormatter and it gives us a nice human readable string for the date.

And we couple that with this code.

We go ahead, and I want this to be a long, slow animation for us to savor so it could take 20 seconds.

And here I'm replacing the animations dictionary on the text field again, this time for the double value property.

Double value is a property most of our controls themselves implement, and again it's a double so it's easily interpolatable using CABasicAnimation.

I set the text field's double value to the initial value of 0 and then I ramp it up to 1,000,000.

And when you do this you get this animation.

We are actually formatting the interpolated values for every second between January 1, 2001 and wherever this ends.

So you can also use this for other things, for example you can implement your own double property that we can interpolate because it's a double and say ramp it between 0 and 1 and then use that as the input to your own calculation to do a more complicated interpolation.

And yes, I love you so much I dug up the old Chicago font for that animation.

[applause] So we're going to get into something I call chaining animations now which is simple but can look complicated.

Don't be daunted by this next slide, that one.

You can be daunted by this slide, that's okay.

So in this case we just have a few animation groups.

So we have our first animation group and we're setting properties for our first view.

And then the completion handler of that animation group contains another animation group and we set properties for our second view, likewise the completion handler for that group contains another animation group and we're just setting properties for view three here so you can see here's the 3rd animation group encapsulated within the second animation group, encapsulated within the 1st animation group.

So I'm going to go ahead and show you a demo of how you can use this in your own code.

So let me show you what this program is here.

This is just a simple list of fonts on the left and a rotated shadowed preview of that font on the right and we can actually select multiple ones of these and we get this sort of page fanning effect.

And if you've used mail and you select multiple messages you notice they do a very similar effect.

One of the differences though is that mail actually slides these messages in from left to right and then right to left as they come in and out.

And additionally it implements this really cool effect where they slide in one at a time.

So I'm going to show you how we can take this program here and implement all of that.

So the important methods to be aware of are these three here, update visible views, our controller object here has two NSIndexSet I-bars and an index set is basically just an array of specific indexes but you can perform set operations like union, interception, subtraction on them.

And one of these index sets stores the rows or fonts that we want to be visible and another stores the ones that actually are visible.

So to calculate the ones we want to remove we take the ones that are actually visible and we subtract the ones we want to be visible and the remainder there is what we need to take out of the view hierarchy.

Similarly when we want to bring stuff in we take what we want to be visible and we subtract what's actually visible and that tells us what we need to add.

So then we just enumerate over these index sets.

And we hide the rows that shouldn't be there and we add the rows that are there.

And if we go ahead and look at the implementations of these methods, here's the hide method, we just have an early out in case it's already visible.

We update our I-bar.

We grab a cache view for the appropriate font and we take it out of our view hierarchy.

The show method is a little more tricky but it's not that bad, same thing, early out, update our I-bar, grab the view and here we position it within our right side container and we just center it.

So what we want to do for animation though is complicate this up a bit.

We want it to slide in left to right.

So just setting the final bounds isn't enough.

We actually need two new variables.

We need one to specify the origin of view of the view when it's on screen and that's actually the same as the value we've already computed so we can take what we set the view frame origin and we just assign it to this sort of temporary in origin value.

And then we need another one which says what the origin of the view should be when it's off screen or our out origin in this case.

And when it's off screen we want the right edge of our view to match the left edge of the container view so we're going to take the MidX of the container view instead and instead of subtracting 1/2 of our width we're going to subtract our whole width and that aligns our edges like we'd expect.

We don't need this bit anymore.

So one of the things we can do now though is we're going to, this will look familiar if you're paying attention to the slides, we're going to make a CABasicAnimation and we're going to set its from and to values.

We're going to start at the out origin and we're going to animate to the in origin.

And it's important to remember our steps of setting the animations dictionary on the view so view.animations=frameorigin, with our animation there.

And then we tell the animator again, hey we want to animate in.

And we actually want to add our subview before we do that.

So similarly for removing this we're going to do a very similar thing, we're also going to specify an animation for how to remove it.

So we actually copy and paste a whole lot of this.

So we copy and paste our container bounds, our view frame and our from and to values.

But in this case we need our from and to values to be switched so that's an easy fix.

And additionally our use of the animator is also very similar so we can take that again as well.

Importantly we're replacing animating to the in origin with animating to the out origin but we have one remaining problem here.

You'll notice before we remove from the super view if we did this immediately we would tell our view to animate and before it ever had a chance to draw we would remove it.

So we're going to go ahead and open animation group using NSAnimationContext run animation group.

We're going to take our changes that kick off the animation and move them into the changes block.

We're going to take the completion handler and we're going to take our removeFromSuperview from this.

And this prevents our view from being prematurely removed before animations had a chance to run.

So if we go ahead and run this and we cross our fingers that it compiles, yay, we get this instead.

And you can see these things nicely exchanging locations come in from the left and exiting to the left.

And that works, we bring multiple things, or bring them out, so that's great.

But one of the differences here is these all fly in at the same time.

We want them to come in one at a time so how can we do that?

Well if we go back to our update visible rows method I'm going to go ahead and declare some block scope storage here and just keep an integer that keeps track of the number of animations we've added in this pass.

So I'm going to begin an animation group again.

And I'm going to copy and paste our code that enumerates our in and out index sets.

And one of the differences though is I only want to remove or add a single view.

So I'm going to go ahead and enter increment my counter inside of my enumeration and the enumeration method has this handy stop argument that I can set to yes to exit early and I'm going to set that to yes if our count has been something other than 0, which it will always be in this case.

I'm going to make the same change for adding a view but instead of always enumerating the views to add I'm only going to do it if we haven't removed anything so we're only going to get one removal or one addition for pass through this.

Now if we didn't add anything this time, excuse me, if we did add something or remove something after our animation has completed, we want to go ahead and just run this another time so we call self-update visible views and of course we add a semicolon here to appease the gods.

So we still have our nice animation.

But you'll notice as I bring these things in they come in one at a time just like they do in mail and they exit one at a time just like they do in mail and that's exactly what we're looking for.

It wasn't as complicated as having 10 animation groups stuck inside each other.

It was just the act of invoking our single method from the completion handler that we had already kicked off.

So that's how you can use animation chaining to implement some fairly entertaining animations.

That concludes our demo for chaining animations.

So on to the next thing, implicit animation.

This is something we added in Mac OS 10.8.

And some of you have probably already used it and enjoyed it and or have been terrified have it.

So before we added this you would use view.animator.frame and you would just set it to a new rectangle and with implicit animation you could use this instead.

You tell the current animation context that it's okay to allow implicit animation and then you set the frame on the view directly.

So you're actually animating as a side effect of setting the properties on your view.

So consider this method here called swapSubviewFrames.

In this we just take our first two subviews, we grab their frame rectangles and we set subview 0's frame to subviews 1 frame and vice versa.

And if we just execute this we get this, they just swap positions, which is correct but it's not necessarily what we want.

You can do this with allowances in animation really easily, we just setanimation context.

currentcontext.

allows implicitaniimaton=guest and we execute our same method as before.

And in this case it looks like this.

So when would you use this?

You would use this in places where the animator proxy doesn't have visibility into what's being animated.

You'll notice swap subview frames was an action.

It didn't take an argument.

The animator can't actually interpolate anything in there so you can use the animation context AllowsImplicitAnimationProperty to animate those frames as a side effect of your action method.

There are some caveats.

First it doesn't work for everything.

It will work reliably for frame, frame size and frame origin, pass that you're beginning to pray.

But it does tend to work for more things when you're layer backed and we're talk a little bit more about layer backing in a bit.

In fact we'll talk a bit more about layer backing right now.

So I'm going to give a brief overview of core animation, a very brief overview of core animation here.

And you're probably already familiar with CALayer.

And this is an object that contains lots of properties very similar to MSView, it has like bounds and position and opacity.

And these are all properties that Core Animation knows how to animate.

And if you want to use explicit animation you want to tell core animation to animate this, you don't modify the property, you tell the layer to add an animation that will look kind of like this.

Here we construct another CABasicAnimation.

We set its from value.

We set its to value.

And we add it to the animations on that layer.

Well what does that do?

Well first it doesn't do anything destructive.

It's not actually setting the property on the layer.

It just temporarily overrides that property for rendering purposes.

So what are the consequences of this?

If you take a look at this snippet here we have that basic animation again and we're interpolating from .75 to .25 and it really is only applying that for rendering purposes.

If you actually ask the layer what it is while it's doing its interpolation between .75 and .25, it's going to tell you this.

It's going to tell you it's opacity is a default value of 1 because nothing has actually changed that property inside the layer.

So again we have to have implicit animation for core animation as well, why have one of everything when we can have more than one?

So in this case animations are avid in response to property changes.

If you call this code you're setting your property on your layer, you actually get all of this for free, which is kind of handy.

It makes the basic animation for you.

It figures out that the two values, what you're setting it from and what the from value is and what's already on screen and then it adds it as an animation to your layer.

If you're curious about that there's more information on this in the CAAction Protocol which lives inside of CALayer.h, you can do some pretty clever things but that's beyond the scope of what we're talking about here.

Tying this back into views though, how does this effect layer backed views?

Well first of all views can optionally delegate a lot of the drawing of animating responsibility to CALayer.

And you would generally get this by studying the wantsLayer property of your view to yes.

And what that does is it causes your view to manage its own CALayer and it also causes all of its descendants to manage their own CALayers.

And there's a few reasons you might do this besides animation, performance and memory are the big ones, and they trade off, sometimes non very predictable ways but we trust you to balance it for your own applications needs.

The take away from this though is when you're using a layer back view, it's going to behave a little differently than a non-layer back view, surprise.

If you want to know more about these differences I encourage you to stick around for our next session, 215, Optimizing Drawing and Scrolling on OS X.

I realize this slide says this was 11 hours ago and we did in fact run the session 11 hours ago but we're going to run it again in 40 minutes or so just for you.

[laughter and applause]

So how does AppKit run the animation for non-layer or even layer backed view over not delegating our responsibility over core animation?

Well first of all we keep this animation around.

It doesn't immediately change the property in your view.

And we periodically wakeup the main thread to do our animation.

Every time we wakeup we're going to evaluate that animation for the current time and figure out what the current value is and we're going to apply it to the object that's being animated, in this case the view.

So we're actually replacing the property inside the view, that means you'll see all of your key value accessors get called, all the change notifications get called, all the NS notifications get posted.

And the drawing of this property actually just happens as a side effect of the regular NSView drawing cycle.

And when we change a property that effects the visual appearance of NSView, the view gets marked as needing display and we draw that on the next from loop pass.

So here's an example, if you do an animation you're going to see this happening, the main thread wakes up and every time it wakes up it changes the size of a view.

Core animation works a little differently so it also stores the animation in addition to property on the layer so that's similar.

And this is somewhat nebulously phrased, it is waking up periodically on a background thread or maybe another process but it does different things from this point on.

It evaluates the animation but only evaluates it as part of rendering so the results you see on screen aren't going to be back propagated into your layer.

The property of the layer is left unchanged and unmodified.

So suppose you're running the same animation with a layered back view and we delegate this to core animation.

You're going to notice hey the size on screen is changing but we're never doing anything on the main thread.

None of those key value accessors or notifications are being posted so that's an important difference.

In general we try to let core animation drive things because doing stuff in the background is usually more performant and lets us be more generous with what the main thread can do.

The frame, frame origin and frame properties of NSView are important exceptions to this where we don't necessarily let Core Animation drive them.

And our decision is governed largely by the value of the layerContentsRedrawPolicy of your NSView, and again that will be covered in greater detail in session 215 in this room right after this session.

So I want to call out this code snippet here.

This is very simple.

We have a window and we want it to animate in a view so we want it to look like that.

We set the views initial frame and then we told the animator what its final frame was.

So if we go back to here the only difference in the snippet is that we've told it wants a layer.

If we run that animation we see this.

Again the screen changed, it's technically in animation but it's not a great animation.

It's definitely not what we wanted so why did that happen?

Well first, Core Animation groups all of its changes into transactions.

And if you're using implicit animation, Core Animation is going to try to grab the on screen value as your from value but the view was never on screen so there wasn't any on screen value.

That initial value we set on the view is actually replaced in that same transaction by the value we set on the animator so there's at least two easy ways to fix this and I'm going to show them to you.

So in this case we're going back to manually specifying the from and to values of our animation.

Again we do that by setting our animations dictionary on the view and then telling the animator to go to the to value as necessary.

Technically we will automatically fill in the from and to values for you, in this case the from value is the important part to specify.

The to value is inferred from what we're setting the frame on the animator to so it's optional but leaving it in there is often good for clarity.

The other way of doing this is with animation groups again.

And in this we actually use our changes block in the animation group to set the initial value on the view and then we use our completion handler to tell the animator to set it to the to value.

And in both cases we're going to get an animation looking like this, which is exactly what we want.

So at this point I'd like to welcome my colleague Peter Ammon on stage.

He's going to get you started with Auto Layout.

Welcome Peter.

[ Applause ]

Peter Ammon: Thanks, Chris.

I'm Peter Ammon.

I'm a Cocoa Frameworks Engineer.

I apologize my voice is a little rough.

I'm fighting off a cold.

We'll be talking about a new class in AppKit called NSStackView which is really cool and then I'll show you three different techniques for doing animations in an auto layout based app.

So NSStackView, let's say you just have a collection of views, a button, a text field, a label, you want to put them all together into a list or a stack, well that's what NSStackView does for you.

Pretty simple, right?

Where it gets its flexibility and its power is from its use of auto layout, everything is strung together with constraints.

That means it knows how things should be sized according to their intrinsic content size or any other constraints you apply.

It knows how things should be aligned so you can specify a top alignment, left alignment, here we have a baseline alignment.

And it interacts well with window resizing so you can make a stack view that prevents you from resizing the window too big or too small so the things wouldn't clip, etcetera.

And this is a really common type of layout to have and StackView makes it very easy to create so you can go back to [inaudible] or Mountain Lion and create these yourself but with StackView it's a lot easier.

We figure why stop there?

We can make a stack view that's horizontal or vertical.

You can have one that has flexible spacing or equal spacing.

Equal spacing in particular is difficult to create with auto layout today.

You can make a StackView nestable so you can put a stack inside another one, why not?

And you can have a stack view that will automatically have views that get thrown out when the StackView gets too small or put back in when the StackView has enough space.

For example, how NSToolbar does it.

And the API for StackView could not be simpler.

You can create a StackView with just passing an array of views to NSStackView, StackView with Views and you're done.

So we're going to use StackView to create an inspector type window.

You may have seen this in FindersGetInfo or in for example a graphics editor.

For each of the individual views in our inspector window, for example filters, we're going to string it all together with auto layout.

So we have constraints that specify the positions.

And you'll notice the bottom constraint is dashed.

That means it's breakable.

It has a priority less than required and that means it could be overridden by a constraint with a higher priority.

So when we want to disclose it, when we want to collapse this view, we're going to add another constraint which just says center the label in the view, that's going to force the bottom up until it's centered and that's how we collapse it.

And this way we're going to put these in a StackView and that's how we'll get the inspector window.

So I'm going to show you a demo of that.

So here's the views that we're going to put in our StackView.

We have the header view.

We've got three views that are all kind of similar and I'll zoom in here and you'll see that all the labels here are positioned using auto layout.

I use the new X Code 5 Auto Layout Work Code which is a lot nicer, for those of you who attended the session earlier here today you saw that too.

And you'll notice the bottom constraint has a priority which is still pretty high, 725 but it's not required, which means that a higher priority constraint can break it.

And by the way, one of these views is itself a StackView.

You'll notice that we have, this is a StackView.

We have some basic support for configuring a StackView on the interface builder, in the seed build.

But in a future release of X Code hopefully GM we will have full support for configuring the Stack View with all the views inside it.

So now I'm going to switch to the window controller.

So window did load.

We're going to start by making a list of all our views, the header, the filter, the shapes view.

And we're going to make a StackView.

We're going to say that it's vertical.

Alignment is leading, that means that it's left aligned.

They all have the same width so it doesn't matter but if we wanted everything to be left aligned, that's what we would do.

And there's no spacing between them.

We're going to see oh it keeps its width and its height with a high priority and we're going to set it as a document view of a certain scroll view, of the scroll view that's in the window.

Next we're going to tell it how do we position ourselves in the scroll view?

Oh using the Visual Format Language we're going to say if penned to the left and the right so it just fills that width.

Vertically its penned to the top, that's what that means, but it's not penned to the bottom.

It can float freely.

And this has to be in a flipped clipped view, by the way.

So in our disclosure view this is the view that goes into the StackView.

We'll start by creating a constraint.

This is the centering constraint that I showed you.

We're seeing the label fields center Y is equal to our center Y times 1 plus 0 so it's exactly centered.

Now when it comes time to collapse it we're going to remove that constraint, I'm sorry, we want to uncollapse it, we remove the constraint, set the title of our button and say that we're no longer collapsed.

And when we want to collapse it we add the constraint, set the title and say that we are collapsed.

So that's not a lot of code but here's the affects you get.

Here's our StackView and you'll see that I can disclose things pretty nice and I can resize the window, for example, I can scroll.

So we got this pretty sophisticated control without a lot of code.

But this is a talk on animation so how would we make this disclosure view animate?

Well the desire is that we want to just switch from our old layout to the new layout.

We want to just add that constraint or remove it.

But rather than the views just jumping we want them to animate from their old position to the new position.

So Chris showed you some techniques for doing this in a non-auto layout based app where you set the frame through the animator proxy or you set the frame after setting allowsImplicitAnimation to yes.

But with constraints you're never supposed to set frames directly so how would we do this with constraints?

Well here's some things people have tried that don't work.

What they have done is they've tried opening the animation block and saying add constraint and you'll see that doesn't animate.

They've tried adding a constraint and maybe setting the constant to something, that doesn't animate.

And here's something people have tried, they're actually going to call layout on the view after modifying the constraints.

And this might seem to work but this is really bad, you can see it's off the page bad.

Why does it seem to work?

The underlying step frame calls must be in an animation block with allows implicit animation set to yes.

And that's actually where those calls occur in the layout method.

But the layout method is for overriding.

It's not a method you're ever supposed to invoke yourself, unless you're invoking super.

So how do we trigger it being called by AppKit?

Well we're going to start by adding the constraint.

We can do that inside or outside the animation block, it doesn't matter but here we do it outside just to make it clear.

Or you can set the constant on a constraint.

If you're not familiar with a constant on a width constraint it's just the width, or it's like the base value.

For a spacing constraint, it's the space between the two views, etcetera.

And then in the animation block we're going to say allows implicit animation yes.

And we'll trigger layout by calling view layoutSubtreeIfNeeded.

If you just want to animate that view and all of its descendants or you can call window layoutIfNeeded to animate the entire window.

So this is the right way to do or one way to do animation correctly in an auto layout based app.

So let's make our disclosure view animated in this way.

So I'm already in the right spot, toggle collapsed, and I've already added or removed the constraint so now all that's left to do is open the animation block here.

Say we allow implicit animation yes.

And then trigger layout if needed on the entire window.

So now when I run this you'll see that instead of just jumping the views actually animate open and close like that.

So just a few lines of code we went from this very static UI to a nicely animated control.

[ Applause ]

It's nice when things take a little code.

So that's one technique where you just set your constraints and you trigger layout in an animation block.

But another technique is to animate constraints directly.

As I said, constraints have only one mutable property, once the constraint has been installed on the view there's only one thing you can change about that constraint and we chose to call that constant.

You can animate it with the animator proxy so you can say constraint.animator.constant=17 and this uses the same sort of animations that Chris was telling you about.

I know the constraints do not respect allowsImplicitAnimation so the constants will always, you either have to use the animator proxy if you want animation.

This is because if they did respect that allowsImplicitAnimation you might get layouts which are not allowed which will cause conflicts with other constraints.

So let's, I'm going to give you a demo of how to use this technique to make our StackView animated again.

So it was kind of nice that it animated but the window didn't change its height, right, so you have to scroll down or you have to resize the window.

It would be a lot nicer if after you disclose it the window just resized and matched the height of the StackView.

So we're going to do that by instead of just penning to the top we're also going to pen to the top and the bottom.

And because our layout priority is high for the vertical orientation, it's higher than the strength with which the window holds its size so this will actually push the window taller or force it to shrink.

Here's the other implementation of where we're going to animate the constraint.

Instead of that centering constraint we're going to have a constraint which just says what is the height of my view, of me?

So it's equal to nil, not an attribute because we're just setting the height to a constant and that's going to be either the collapsed height, which is just the height of our top part, or our uncollapsed height which is our fitting size, the height we want to be.

And then we're going to add that constraint and then we set the title to another button.

So then the toggle collapsed.

Instead of triggering layout with the animation block, we're going to go through the animator proxy and set the constant to either the uncollapsed height or the collapsed height.

So now when I run this you'll see that not only does it animate but it also resizes the window bigger or smaller.

So you may be wondering why did we use this separate technique?

Why couldn't we have just made the window resized using the old technique for animation?

Well I'll show you what happens if I do that.

So I if that out, now we're back to doing the implicit animation and triggering layout.

When I run this you'll see that it, it has this weird kind of jumpy behavior here.

So what's going on with that?

So what we saw there was a core animation driven animation.

It's using layers concurrent with the window resize animation, which always happens on the main thread.

So when you have these two animations going at the same time they may get out of sync or they may fight.

We say don't cross the streams and if you do something terrible will happen or not quite that bad but you'll get drifting or you'll get jitter.

You'll see a view maybe vibrate a bit and this is a clue that there's two animations going at once on different threads.

So the solution to this, one solution, is to go through the constraints animator that will do an AppKit driven animation on the main thread so it will not be concurrent with the window resize.

Or you can do the other technique where you can tell the window to just resize itself in an animated way.

And that's the third way of doing animations with auto layout, which I'm going to talk about now.

Animating window size changes, so we're going to switch to a totally different kind of application.

This is a shopping list app where we have a master and a detail window.

So here we have a shopping list and there's a segmented control at the bottom right.

And when I click that what I want to have happen is a side bar will slide out and the user can select different shopping lists.

So you may have seen this type of UI in reminders or notes app.

So when the user clicks that segmented control, we want the window to grow but it grows in a different way than if the user had just resized the window.

When the user resizes it we want the pane on the right to pick up this extra space, all the slack.

But with this kind of animation what we want is instead all the slack should be taken up by the pane on the left.

So how are we going to accomplish this animation using auto layout?

Well the trick is we're going to set temporary constraints that control how the panes resize so we're going to grow the window and remove those constraints.

But we don't have to add these constraints ourselves.

We can leverage NSSplitView, which is a great client and auto layout starting in Mountain Lion, to create these constraints for us.

We do that by adjusting the holding priority of the split view panes.

The holding priority is the strength of which the split view pane prefers to hold its size so as the window gets bigger the pane with the lowest holding priority will take up all the slack.

So we start by getting the window framed.

And we're just going to adjust the origin left by 120 and adjust the width by 120 so the right edge stays fixed and the window gets bigger.

We're going to lower the holding priority of the first subview to 1, which is very low.

We'll then set the frame of the window and we're going to pass yes for animate.

Instead of just jumping it's going to cause it to slide out.

And then we'll restore the holding priority of the split view pane.

So this gets us almost all the way there but there's one thing we can't quite do, one remaining wrinkle, what's that?

We want not just the pane to disappear but also the split view divider.

And a split view calls this collapsing the pane so this is something it has support for, which we want to take advantage of.

But collapsing is a deliberate user action.

There's a special way the user collapses a pane, it's by dragging more than halfway across it so it's not something constraints can just do on their own.

However, oh, excuse me, so here's what constraints can get you, they can get almost all the way down to the last pixel but to get it the rest of the way we need to invoke NSSplitView.

So the solution is we're going to shrink the pane as small as we can get using auto layout.

Then we're going to collapse it by calling setPosition:0 ofDividerAtIndex:0.

That says take the divider and push it all the way over to the left, which will cause that pane on the left to collapse.

And to uncollapse it is almost exactly the same except for backwards.

We start by setting the position to 1 and then we grow the pane using auto layout.

And don't forget we have to enable collapsing.

We do that by implementing the delegate method, SplitView canCollapseSubview and say oh you can collapse it if the subview is the first subview in a split view so let's give a demo of that.

So here's our master detail view and we have the segmented control down here.

When I click that you'll see that the split view slides nicely in and out and I can you know resize it how I like and then I can collapse it and collapse it again.

How do we accomplish this in our split view demo?

So here's the toggle collapse.

This is the action method of the segmented control.

We start by getting the first subview of the split view and that's what we want to collapse.

We say that do we want to collapse it or uncollapse it?

And if it's already collapsed the way we want it, we just return.

So we're going to get the holding priorities of the first subview and the second subview and we're going to lower the first one and raise the second one and we're going to restore them at the end.

Now we're going to call that set frame display animate method.

So we start by, if we're collapsing, if we already collapsed, excuse me, we're going to set the position to just 1, 1 of the divider index so that's going to uncollapse it by one pixel.

Then we have to resize the window by that amount so that the right edge stays fixed.

Then we're going to do it the rest of the way just using a setFrame display, yes, animate, yes.

And likewise if the window is already big we want to make it small, we want to collapse it.

We start by, excuse me, we get the frame, we say yes, animate yes and then we, excuse me, this is using auto layout here, then to collapse it we set the position 0 of the divider index zero and resize the window in a non-animated way for just that last pixel.

And then we restore the holding priorities to what they were before so that when the user resizes it they get the resizing behavior we want.

And here's where we implement the canCollapseSubview method to indicate that we can collapse that first subview.

So that's how we get that effect.

So that's what we have.

For more information you can contact our Frameworks Evangelist Jake Behrens.

There's also really good documentation on Core Animation and Auto Layout.

You can always reach us at the Apple Development Forums.

We've got some really good labs too, oh later sessions, related sessions.

Hopefully you saw the really nice auto layout session for interface builder that was earlier today.

And there's going to be a session immediately following this about Optimizing Drawing and Scrolling using some of that responsive scrolling stuff, immediately following in this room.

So what did we see?

We saw NSStackView, how it can layout views in a list, horizontally or vertically.

It's a really powerful new class in Mac OS X.

We can animate view positions by adjusting constraints and triggering layout within an animation block.

By using layers you can produce very smooth animations.

We saw how you can animate constraints directly by using the animator proxy NSLayout constraint and this allows you to resize windows.

We saw a third technique which is you set up your constraints the way you want and then you tell the window to resize then pass yes for animate and then the animation will cause the content to reflow in the way you specify.

But be careful to not use core animation, driven animations alongside window resizing, we call that crossing the streams.

You'll get that jitter or you'll get drifting.

Thank you very much.

Enjoy the rest of your week.

[ Applause ]

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US