Chapter 17. Core Animation

One of the iconic things about iOS is its cinematic user experience. GUI elements slide on and off screen, fade in, fade out, flip, curl, and bounce, the result of which is an interface that feels very smooth, dynamic, and organic.

Powering this cinematic experience is one of iOS' foundation technologies, Core Animation. The Core Animation API provides you with a number of UIView-based animation features that allow you to easily animate changes in your interface so that your applications can provide the same cinematic experience that users have come to expect from iOS applications. View-based animation includes two types of animations: transition animations and same view animations. Transition animations are animations that occur when you're changing between views. For instance, you might want to load another view and use a flip transition so that when it appears, the whole screen appears to flip with the new view on the side that rotates in. Same view animations are animations that occur on a single view, such as moving subviews around.

In addition to the view-based animation features, the core animation API exposes lower-level animation functions that operate on CALayer objects, rather than UIView objects, and provide a powerful 2D animation framework that you can use to create more complex animations, and even power games.

For most applications the view-based animation framework is the only part of Core Animation you'll ever use. They provide a powerful way to give your interface that cinematic feel without much work. In fact, if you've been reading this book front-to-back, you've already been exposed to it. In this chapter, we'll explore it a little deeper and take a look at how to work with it in more detail. Then, we're going to dig into the lower-level core animation API to see how advanced animation can be accomplished in the iOS.

As with Chapter 16, this chapter covers most of everything you'll ever need to use with animation; however, the Core Animation API is massive. If you're doing advanced animation, you should check out the Core Animation Programming Guide in the Xcode developer documentation after you've read this chapter.

You can find all the examples used in this chapter in the Example_CoreAnimation sample code and application.

View-Based Animation Framework

There are two different approaches when working with the view-based animation framework: the new way and the old way. The new way (available since the iOS v4.0 release) is the way recommended by Apple, but both ways have their advantages. Let's take a quick look at examples of both methods so that you have a clear understanding of them.

View Animations via the Animation Blocks

First, the old way is to call BeginAnimations, optionally configure the animation options, make your view changes, and then call CommitAnimations. See Listing 17-1.

Example 17.1. Animating changes on a UIView using the BeginAnimations and CommitAnimations methods

UIView.BeginAnimations("ImageMove");
//code to make changes to the view (move controls, swap views, etc.)
UIView.CommitAnimations();

Using this method, iOS will automatically animate any changes that occur between the BeginAnimations and CommitAnimations calls.

Technically, Apple refers to this method of animation as Animation Blocks, but as we're about to see, that's really confusing, given that the new way is called Block-Based Animation. I'll just refer to it as "the old way" to avoid confusion.

View Animations via Block-Based Animation

The new way is to make a call to the Animate method, specify the duration of the animation, optionally specify any of the animation options, and then pass a method (or code as a lambda) that makes the changes that should be animated. See Listing 17-2.

Example 17.2. Animating changes on a UIView using an animation block method

UIView.Animate(0.2, () => { /* code to animate */ });

In this case, to simplify things, I've used the C# lamdba syntax, but you could also pass an anonymous delegate, as shown in Listing 17-3.

Example 17.3. Using the delegate syntax instead of a Lambda expression

UIView.Animate(0.2, delegate() { /* code to animate */ });

This approach is known as block-based animation. It's called such because in objective-c, the analog to code that's passed in to execute is called a block. If you take a look at the prototype for the Animate methods (there are several overrides), they all take NSAction objects for their blocks. MonoTouch automatically does the magic of converting an anonymous delegate into an NSAction for you, so it's a very clean and seamless integration with the underlying Objective-C runtime.

Comparison of the Two Approaches

While Apple recommends the new block-based animation approach, it's not exactly forthcoming in terms of why it's recommended. It's likely because it more closely ties into Apple's new paradigm of parallel tasking (see the Grand Central Dispatch article on Wikipedia.org for more information, at http://en.wikipedia.org/wiki/Grand_Central_Dispatch).

Regardless of why Apple recommends the new approach, it's arguably cleaner. Since the code that performs the view modifications is actually passed to the animation function, there's no method wrapping magic that has to happen.

However, block-based animation is missing the following two key features that are possible with the old way:

  • Fixed repetition count: You cannot specify how many times the animation should repeat; you can only set it to not repeat, or to repeat indefinitely.

  • Automatic duration: With the old way, the default duration (the time it takes for you animation to happen) of your animation is calculated by the iOS. With block-based animation, however, you must specify the animation duration.

Having to specify the duration isn't a huge deal, but not being able to specify a fixed repetition count is very difficult to work around, if you need that particular feature.

Hopefully, at some point, Apple will realize the oversight and add these features back in. Until then, if you need to specify a fixed repetition count, you should use the old approach.

In this chapter, we're going to examine both approaches to view-based animation.

What Is Animatable?

View-based animation is based on a view's property changes. There are a number of properties that, when changed within the context of a view-based animation (either a block based animation or animation blocks), will get automatically animated. Those properties are as follows:

  • Frame

  • Bounds

  • Center

  • Transform

  • Alpha

  • BackgroundColor

  • ContentStretch

The upside to this is that, if your animations are based on changes to any of these properties, view-based animation is very easy. The downside is that if you want to animate other properties, you need to use Explicit Layer Animations (as we'll explore later in this chapter). Fortunately, however, these properties cover most animation needs.

Configuring Animation Behavior

There are a number of options that you can configure that affect the behavior of an animation, namely the following:

  • Duration: The amount of time the animation takes to complete. You have to specify this when you use block-based animations, but it's an optional configuration when using the older style.

  • Delay: The amount of time to wait before the animation should start.

  • Curve: The acceleration and speed of the animation.

  • Repetition: Whether the animation repeats.

  • Auto reverse: Whether the animation automatically reverses.

Nested Animations

It's possible to nest animations by calling methods to generate an animation from within an already executing animation block. Nested animations run the same way non-nested animations do, but, by default, these nested animations inherit their duration and curve from the parent animation.

Specifying Behavior via Animate Method Overloads

If you're using the block-based animation approach, these options are specified via parameters in the various Animate method overloads. See Listing 17-4.

Example 17.4. Various Animate method overloads

Animate(double duration, MonoTouch.Foundation.NSAction animation)
Animate(double duration, MonoTouch.Foundation.NSAction, MonoTouch.Foundation.NSAction
        animation)
Animate(double duration, double delay, UIViewAnimationOptions options,
        MonoTouch.Foundation.NSAction animation, MonoTouch.Foundation.NSAction
completion)

Let's take a look at the various parameters in these overloads.

  • Duration: Just as it seems, the duration parameter specifies the amount of time, in seconds, that an animation should take to occur.

  • Animation: The animation parameter takes an anonymous delegate of the code that actually gets animated. Any view changes that occur during this block of code will get animated.

  • Delay: Again, self-explanatory the delay parameter specifies the time, in seconds before an animation should start.

  • Options: We'll look at the options available in the very next section. The options parameter takes a set of flags from the UIViewAnimationOptions enumeration which contains a number of animation options you can specify.

  • Completion: The completion parameter takes an anonymous delegate of code that gets called when the animation completes.

UIViewAnimationOptions Enumeration

Duration and delay get their own parameters, but all other options are specified via a UIViewAnimationOptions enumeration. The UIViewAnimationOptions is a bitmask enumeration, so you can set multiple options via the pipe ("|") operator, as shown in Listing 17-5.

Example 17.5. Configuring animation behaviors using block-based animation

double duration = 1;
double delay = 2;
UIViewAnimationOptions animationOptions =
        UIViewAnimationOptions.CurveEaseIn | UIViewAnimationOptions.Repeat;
UIView.Animate(duration, delay, animationOptions, () => { /* animation code */ }, null);

The UIViewAnimationOptions have the following general animation enumeration values:

  • AllowUserInteraction: By default, while an animation is running, user interaction is not enabled, that is, buttons can't be touched, and so on. If you specify this value, then user interation will be enabled during animation.

  • AutoReverse: If this flag is set, the animation will automatically reverse and go backward. However, once the animation is done, the elements that have changed will "jump" back to their final location, so this flag is meant to be used in conjunction with the Repeat flag to create looping animations.

  • BeginFromCurrentState: By default, if an animation is already in progress (also known as "in-flight"), other (non-nested) animations will be queued and run sequentially after the previous animation finishes. By specifying the BeginFromCurrentState flag, an animation will execute, even if other animations are running, allowing concurrent animations.

  • Curve*: There are several curve settings such as CurveEaseIn, CurveEaseOut, and so on. They specify the acceleration speed of the animation. We'll explore them more in the upcoming "Animation Curves" section.

  • LayoutSubviews: If LayoutSubviews is specifed, LayoutSubviews will be called on the view when the animation starts, and any changes will also be animated. This is a very powerful feature because it allows you to easily animate layout changes.

  • OverrideInheritedDuration: Nested animations automatically inherit their duration from the parent animation, rather than using the duration specified when the nested animation was generated. Sometimes this isn't desirable, so you can use the OverrideInheritedDuration flag to make sure the nested animation uses it's specified duration, rather than the inherited duration.

  • OverrideInheritedCurve: OverrideInheritedCurve is similar to OverrideInheritedDuration, except that it makes sure the nested animation uses it's specified curve rather than the inherited curve.

  • Repeat: If specified, the animation will repeat indefinitely.

Animation Curves

Table 17-2 lists the animation curves available in CoreAnimation:

Table 17.1. Animation Curves in CoreAnimation

Type

Description

Graph

CurveEaseInEaseOut

Accelerates at the beginning of the animation and decelerates at the end of the animation. Provides the most natural feeling animation for most animation types and is the default animation curve.

Animation Curves in CoreAnimation

CurveEaseIn

Accelerates at the beginning of the animation. Good for animations that send things off-screen.

Animation Curves in CoreAnimation

CurveEaseOut

Decelerates at the end of the animation. Good for animations that bring things onscreen.

Animation Curves in CoreAnimation

CurveLinear

No acceleration curve at all, therefore the animation speed is constant from start to finish. Linear curves are useful for animations that don't require a natural acceleration/deceleration.

Animation Curves in CoreAnimation

View Transitions

In addition to using view animations to move things around on a view, you can also animate adding and removing views from a controller. This allows you to provide a cinematic way to transition between screens. For example, you could apply a flip transition that "spins" a new view onto the screen, or a "curl" animation that lifts the current view off the screen and displays another.

The UIViewAnimationOptions enumeration contains a number of values that apply only to transition animations:

  • ShowHideTransitionViews: If you specify this value, then the view that you're tranisitioning to will be shown and the view your transitioning away from will be hidden. This is different than the default behavior, which actually adds the new view and removes the old view. If you do specify this option, the view that you're adding must already be in the parent view's list of subviews.

  • Transition*: There are several transition types, including FlipFromleft, FlipFromRight, CurlUp, and CurlDown. To specify which one you'd like your transition to use, simply specify the transition type flag, such as TransitionFlipFromLeft, to get a flip transition that starts from the left side of the screen.

  • AllowAnimatedContent: AllowAnimatedContent specifies whether the iOS renders the animation and caches the frames. If set to true, caching will occur; if set to false, no caching will occur. This is the equivalent of the Cache parameter when using the older method style animations. If set to true, performance may be better (setting it to false may noticeably affect frame rate, especially on older devices); however, you must not update the view manually during the transition or the rendering could be incorrect.

Specifying Behavior via Methods

If you're using the older animation methods, then behavior options are configured using various methods. See Listing 17-6.

Example 17.6. Configuring animation behaviors using method based animation

UIView.BeginAnimations("MyAnimation");
UIView.SetAnimationDelay(2);
UIView.SetAnimationCurve(UIViewAnimationCurve.EaseIn);
UIView.SetAnimationDuration(1);
//... animation code goes here
UIView.CommitAnimations();

All the UIView animation methods are static, and the following are available to configure the animation properties if you're using the older animation methods. Most of these correspond 1:1 with either parameters in the Animate method or flags from the UIViewAnimationOptions enumeration:

  • SetAnimationDuration: The equivalent of specifying the duration parameter of the UIView.Animate method.

  • SetAnimationDelay: The equivalent of specifying the delay parameter of the UIView.Animate method.

  • SetAnimationStartDate: Specifies a DateTime at which the animation will start. You can use this in conjunction with specifying a delay. The delay will begin counting down at the DateTime you specify for start date.

  • SetAnimationCurve: Sets the animation curve. Values are available in the UIViewAnimationCurve enumeration and equivalent to the respectively named options in UIViewAnimationOptions.Curve* flags.

  • SetAnimationRepeatCount: Allows you specify how many times the animation will repeat. This option is not available in the block-based animation technique.

  • SetAnimationRepeatAutoReverses: The equivalent of the AutoReverse flag in the UIViewAnimationOptions enumeration.

  • SetAnimationBeginsFromCurrentState: The equivalent of the BeginFromCurrentState flag in the UIViewAnimationOptions enumeration.

  • SetAnimationsEnabled: Enables/disables animations.

  • SetAnimationDidStopSelector: Specifies a selector method that will run when the animation finishes. Similar to of specifying a completion parameter via the UIView.Animate method.

  • SetAnimationWillStartSelector: Specifies a selector method that will run with the animation starts.

If you're using the method-based animation technique, and you want to execute code after the animation finishes, you should call the SetAnimationDidStopSelector method, which takes a Selector that points to your method. See Listing 17-7.

Example 17.7. Configuring a method to be called when the animation completes

UIView.SetAnimationDidStopSelector(new Selector("AnimationStopped"));

Since the method takes a Selector, you must also mark your method with the ExportAttribute so it's visible to the underlying Objective-C runtime. See Listing 17-8.

Example 17.8. Marking a method with Export so that it's visible to the Objective-C runtime

[Export]
public void AnimationStopped()
{ Console.WriteLine("Animation completed"); }

Advanced Core Animation with Layers

View-based animation is great because it makes common animation tasks simple. However, sometimes you need more control than what it offers. For example, let's say you wanted to animate the moving of an object, but you want to specify a particular path that isn't just a straight line (see Figure 17-1).

Moving an object along this path is not all that easy using view-based animation.

Figure 17.1. Moving an object along this path is not all that easy using view-based animation.

You could possibly accomplish this by making lots of calls to the Animate method and moving the object a little bit each time, but that would mean a lot of math, a lot of code, and a lot of headache.

There's another place that view-based animation falls short: games. In addition to the lack of control (such as not being able to specify a path when moving objects), view-based animation lacks the speed necessary for game development. Thirty frames per second (fps) or higher has become standard in the gaming world. It's roughly the same frame rate as video/television (film is generally 24fps), and is generally accepted as the lowest framerate that provides a smooth gaming experience. Accomplishing that frame rate with view-based animation can be very difficult, because it's not guaranteed to be hardware-accelerated.

There is an alternative, however, that provides both higher control and much more performance through hardware acceleration: layer-based animation.

Layer-Based Animation

As the name suggests, layer-based animation involves the use of layers (represented by the CALayer object). Don't confuse CALayers with CGLayers. They are similar in concept, but are fundamentally different under the hood and cannot be interchanged.

You can create CALayers one of two ways. The easiest way is to simply get the layer from an existing UIView. All UIView objects in iOS are known as layer-backed views. This means that each view has a layer-tree hierarchy that represents the items in a view, and you can access it via the Layer property on any UIView object. The other way to get a CALayer is to create one manually, which is a bit more involved. We'll take a look at how to do that in just a second.

Layers are interesting in that they're not directly responsible for drawing themselves. Instead, they are an object hierarchy that contains information that describes the state of a UIView. However, under the hood, there is another set of layer information that is used in the rendering loop. When you change a property on the layer, it will actually feed incremental changes to the rendering loop so that the change that you've applied is rendered incrementally over the animation duration.

This functionality is all encapsulated for you, so you don't have to perform the math associated with changes, the layer system does it for you.

Layer Animation Types

There are two types of layer-based animations:

  • Explicit: You must create an Animation object and then apply that to the layer you want animated.

  • Implicit: Changes to properties are automatically animated. Implicit animations are only possible if you use CALayer objects directly, rather than ones that are created from a UIView.

Superficially, implicit animations seem easier to use. However, they required that you manually create your layers, which can be complicated. Explicit animations require more code to actually do the animation, but because you can use them with layers that are created via a view, they are actually easier to implement.

We're going to look at explicit animations first, and then we're going to turn to implicit animations and cover how to create CALayer objects manually.

Explicit Animations

Explicit layer-based animations are a little different from the other animations that we've looked at so far. Instead of creating an animation block and executing it, you configure the animation parameters and then tell the animation what property to listen for changes to. When that property's value changes, the animation specified runs. For instance, to achieve the animation illustrated in Figure 17-1, we would do something like Listing 17-9.

Example 17.9. Implementing a keyframe animation that moves an object along a path

this.btnAnimate.TouchUpInside += (s, e) => {
        //---- create a keyframe animation that listens for changes to the "position"
        // property
        CAKeyFrameAnimation keyFrameAnimation =
                (CAKeyFrameAnimation)CAKeyFrameAnimation.FromKeyPath("position");
        keyFrameAnimation.Path = this._animationPath;
        keyFrameAnimation.Duration = 3;
        //---- add the animation to the layer
        this.imgToAnimate.Layer.AddAnimation(keyFrameAnimation, "MoveImage");
        //---- kick the animation off by changing the position to the final position
        this.imgToAnimate.Layer.Position = new PointF(700, 900);
};

This would result in the animation shown in Figure 17-2.

An explicit layer animation that moves an object along a path

Figure 17.2. An explicit layer animation that moves an object along a path

You can see this animation at work in the Example_CoreAnimation companion code and application.

In Listing 17-9, we created instantiated a CAKeyFrameAnimation object and passed the constructor the name of the property we want to listen for. In this case, we want to listen for the Position property, but it expects the Objective-C visible property name (generally the same name but starting with a lowercase letter), in this case it would be "position." We'll talk about the different types of animations later, but for now, know that a keyframe animation will animate movement along a path.

Next, we configure our animation, this case, because it's a keyframe animation, we supply it a path defined in a CGPath object (check out Chapter 16 for more information about CGPaths).

After we've configured the animation, we add it to the layer and give it a name. We name it so that later on, if we want to stop the animation (by removing it), we can do something like Listing 17-10.

Example 17.10. Removing an animation, If you remove an animation while it's in flight, the animation is stopped.

this.imgToAnimate.Layer.RemoveAnimation("MoveImage");

Finally, to kick off our animation, we set the property that we're listening for changes on to its final value. Once we've done that, the animation will begin.

Animation Types

In Listing 17-9 we created a CAKeyFrameAnimation that animates along a set of positional points, however, there are a number of animation classes available for use:

  • CATransition: Used to provide transition animations, such as fading in/out, or pushing onto/off the view stack. You can extend the stock transition effects by creating your own custom Core Image filters.

  • CAPropertyAnimation: CAPropertyAnimation is an abstract base class for providing animations for custom layer properties that can be reused.

  • CABasicAnimation: Used for simple movement animations that go from from a start point to an end point.

  • CAKeyFrameAnimation: Animates an object along a path, as seen in Figure 17-2.

Additionally, there is one additional class, CAAnimationGroup, which allows you to group a set of the previous animation classes together into an array and run them concurrently. For a more in-depth look at these classes, checkout the Core Animation Programming Guide as well as the Animation Types and Timing Programming Guide in the Xcode developer documentation.

Creating CALayers Manually

Now that you understand how to animate layers that are attached to a UIView, let's take a look at how to create layers manually. By creating layers manually, you have a choice of whether to use either the explicit, or the implicit animation technique.

There are the following three ways to manually create a CALayer object:

  • From an image: One of the easiest ways to create a CALayer is to set its Contents property to a CGImage.

  • Specify a content delegate: You can create a CALayerDelegate class and assign it to your CALayer object. You should override DrawLayer in your delegate class to draw your layer content.

  • Subclass CALayer: You can create a custom class that derives from CALayer and override the DrawInContext method. This is effectively the same thing as creating a layer delegate.

If you choose to draw a layer manually, you can follow the techniques outlined in the last chapter.

All three of these methods are illustrated in Listing 17-11.

Example 17.11. The various ways to create a CALayer

//==== Method 1: create a layer from an image
protected CALayer CreateLayerFromImage()
{
        CALayer layer = new CALayer();
        layer.Contents = UIImage.FromBundle("Images/Icons/Apress-114x114.png").CGImage;
        return layer;
}

//==== Method 2: create a layer and assign a custom delegate that performs the drawing
protected CALayer CreateLayerWithDelegate()
{
        CALayer layer = new CALayer();
        layer.Delegate = new LayerDelegate();
        return layer;
}

public class LayerDelegate : CALayerDelegate
{
        public override void DrawLayer (
                CALayer layer, MonoTouch.CoreGraphics.CGContext context)
        {
                //---- implement your drawing
        }
}

//===== Method 3: Create a custom CALayer and override the appropriate methods
public class MyCustomLayer : CALayer
{
        public override void DrawInContext (MonoTouch.CoreGraphics.CGContext ctx)
        {
                base.DrawInContext (ctx);
//---- implement your drawing
        }
}

If you're interested in learning more about the details of layers, check out the Core Animation Programming Guide in the Xcode developer documentation.

Drawing Layers on a View

Once you've created the layer objects that you want to animate, you probably want to display them. The problem is, you can't add them directly to a UIView, as you would other view controls. Instead, you have to add them to a UIView's layer hierarchy via the AddSublayer method. For example, the UIViewController code in Listing 17-12 calls a method from Listing 17-11 to create a layer and then adds it to the controller's view layer tree.

Example 17.12. Adding a layer to a view's layer tree

this._imgLayer = this.CreateLayerFromImage();
this._imgLayer.Frame = new RectangleF(200, 70, 114, 114);
this.View.Layer.AddSublayer(this._imgLayer);

Implicit Animations

Implicit animations are animations that are automatically invoked whenever you a change a property. This may seem like an awesome way to do layer animation, but there is one huge caveat: implicit animations are not available on layers that are created from a UIView. Instead you have to either use explicit layer animations, or view-based animations. This is because when you change the value of a property on a layer, it asks its delegate whether or not it should animate. If a layer is attached to a UIView, the view acts as the layer's animation delegate and says "no." This, presumably, is because most view changes don't require animation.

Listing 17-13 uses the layer we created and added to the layer hierarchy in Listing 17-12. When a user clicks on a button, the code changes the Frame and Opacity properties of the layer.

Example 17.13. Implicitly animating a change to a layer

this.btnAnimate.TouchUpInside += (s, e) => {
        if(this._imgLayer.Frame.Y == 70)
        {
                this._imgLayer.Frame = new RectangleF(new PointF(200, 270)
                        , this._imgLayer.Frame.Size);
                this._imgLayer.Opacity = 0.2f;
        }
        else
        {
                this._imgLayer.Frame = new RectangleF(new PointF(200, 70)
                        , this._imgLayer.Frame.Size);
                this._imgLayer.Opacity = 1.0f;
        }
};

This results in a cinematic transition between the two states, as shown in Figure 17-3.

Implicit layer animation between two layer states

Figure 17.3. Implicit layer animation between two layer states

You can see this animation at work in the Example_CoreAnimation companion code and application.

Summary

In this chapter we covered the four ways to perform animation in iOS, the two options that are available via views, Animation Blocks and Method-Based Animation, as well as the two that are available via layers, Explicit and Implicit animations. You learned that layer-based animations offer far more control over the animation, and work much faster, however, they're more complicated to implement. I also covered how to work with layers, including creating and rendering.

This chapter is by no means an exhaustive look at animation in iOS. It should cover 95 percent of what you need to accomplish, but the core animation framework is very large and powerful and entire books are devoted to the subject. If you're interested in learning more, start with the Core Animation Programming Guide document in the Xcode developer documentation.

If you've been reading this book from front to back, you should now have an expert understanding of the presentation methods available in iOS. In the next chapter we're going to switch gears and talk about the various ways to work with data in iOS.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.117.109