Aug 27, 2011

Core Animation Part 2: View based animation in UIKit

Animating UIView Properties

Several view properties are animatable. They are the view's frame (the view's dimensions in its parent's coordinate space), its bounds(its dimensions in its own space), the center of the view, the transform (2D transforms only), the alpha, or transparency, the backgroundColor and the contentStretch.

Most of these properties are straightforward. The really interesting one is the
transform. The transform property allows you to apply an affine transform to
the view. This is really powerful. Generally, affine transforms allow you to scale a view (change its size), translate a view (change its location), or rotate a view, or any combination of the three. For UIView animations you use the CGAffineTransform structure. If you have a view and you want it to appear 50 percent smaller on the screen, you would simply:

view.transform = CGAffineTransformMakeScale(0.5, 0.5);

This will shrink the view 50 percent in each direction. It does this without changing the actual size of the view as far as the view is concerned. If you want to scale the view down by 50% and also rotate it 75 degrees you simply:

CGAffineTransform scale = CGAffineTransformMakeScale(0.5,0.5);
CGAffineTransform scaleAndRotate =
    CGAffineTransformRotate(scale, 75.0 * M_PI / 180.0);
view.transform = scaleAndRotate;

Thus you can combine multiple transforms together into a single transform. You also need to be careful with animating the frame if you have set the transform to anything except for the identity transform. Since I'm using transforms extensively in this app, I'm not going to touch the frame.

In CADemo the views are sized to fill most of the screen, with a bit of space around them. The views are positioned in the center. As far as these views are concerned, their size and rotation never change. View transforms like this are only 2D - all rotations are in the plane of the screen. If you want 3D rotation of your views, you'll have to step down into direct Core Animation.

Another interesting point is that the views can still interact with the user in their transformed state. You don't have to write a single line of code. The system knows how to deal with the transforms of views and makes sure that each view receives events in the coordinate space that it expects.

View Layout in CADemo Using View Animations

You can use these simple view animations to do some fun and surprisingly sophisticated things. For example, in CADemo, there are two different layouts (grid and circle) for the main screen. These views have some simple code to determine their layout. This code is defined in CardLayoutView.m in the demo project. The -updateCircle method computes the layout of the views in a circle. The -updateGrid method does the same thing for the grid.

The grid layout is a flexible grid which dynamically computes the size of each subview, based on the number of rows and columns desired, the spacing between the views, and the inset of the grid from the edge of the screen. We do some simple math:

- (void)updateGrid {
  CGRect bounds = self.bounds;

  subviewSize_.width = (bounds.size.width - (2 * inset_.width) -
                       ((columns_ - 1) * spacing_.width)) / columns_;
  subviewSize_.height = (bounds.size.height - (2 * inset_.height) -
                        ((rows_ - 1) * spacing_.height)) / rows_;

We'll get back to this in a moment in more detail, but here we start our animation transaction, set the animation curve (ease in/out starts and ends slowly), and the duration of the animation.

[UIView beginAnimations:@"gridView" context:NULL];
  [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
  [UIView setAnimationDuration:animationDuration_];

  NSUInteger index = 0;
  NSArray *newViews = [self viewsFromDataSource];
  for (DemoCardView *view in newViews) {
    if (!view.isZoomedIn) {
      NSUInteger row = index / columns_;
      NSUInteger col = index % columns_;
      row = row % rows_;

For each view index, I compute the row and column for that view. for this demo,
all the views fit on a single page, but it's also easy to compute an offset for
paging, which would allow you to have multiple pages of the grid.

// Compute the new rect
      CGRect newFrame = CGRectMake(
          inset_.width + col * (subviewSize_.width + spacing_.width),
          inset_.height + row * (subviewSize_.height + spacing_.height),
          subviewSize_.width, subviewSize_.height);
 
      // Use the transform to resize the view. Move it by setting the center.
      CGFloat scale = [GraphicsUtils scaleForSize:self.bounds.size
                                           inRect:newFrame];
      view.center = [GraphicsUtils centerOfRect:newFrame];
      view.transform = CGAffineTransformMakeScale(scale, scale);

Here we are moving the view into the correct location and scaling it to fit in the grid.

if (![self.subviews containsObject:view]) {
          [self addSubview:view];
      }
    }
    index++;
  }

Any view properties set between the +beginAnimations:context: are now animated at the same time.

[UIView commitAnimations];
}

For the circle layout, we put all the demo views into a circle around the screen. I've taken a few shortcuts here for the purposes of the demo, which you'd want to fix if you ever used this code for a real application

- (void)updateCircle {
  CGRect bounds = self.bounds;
  NSUInteger index = 0;
This is a repeat of the animation code in -updateGrid.
[UIView beginAnimations:@"circleView" context:NULL];
  [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
  [UIView setAnimationDuration:animationDuration_];

  // This is pure laziness. I should figure out the correct size based on
  // number of views and the the screen size.
  subviewSize_ = CGSizeMake(175, 175);

  CGPoint center = [GraphicsUtils centerOfRect:bounds];
  CGSize size = bounds.size;
  CGFloat offset = 0.5 * MIN(
      size.width - subviewSize_.width - inset_.width,
      size.height - subviewSize_.height - inset_.height);
The offset computes how far from the center of the screen we want the center of each view.
NSArray *newViews = [self viewsFromDataSource];
  CGFloat angle = 2 * M_PI / [newViews count];
We compute the change in angle by dividing a circle up by the number of views.
for (DemoCardView *view in newViews) {
    if (!view.isZoomedIn) {
     CGFloat xOffset = offset * cosf(angle * index);
      CGFloat yOffset = offset * sinf(angle * index);
      view.center = CGPointMake(center.x + xOffset, center.y + yOffset);
This little bit of trigonometry computes the location of the center of each view, rotated around the center of the screen.
CGRect newBounds = CGRectMake(0, 0, 
          subviewSize_.width, subviewSize_.height);
      CGFloat scale = [GraphicsUtils scaleForSize:view.bounds.size
                                           inRect:newBounds];
      CGAffineTransform scaleAndRotate = CGAffineTransformRotate(
          CGAffineTransformMakeScale(scale, scale),
          angle * index + M_PI_2);
      view.transform = scaleAndRotate;
Then we compute the scale and rotation of the view so that it is rotated with the top of the view towards the outside of the circle.
if (![self.subviews containsObject:view]) {
            [self addSubview:view];
        }
      }
      index++;
    }
And commit the animations.
[UIView commitAnimations];
}
I didn't talk about the animation code in here yet. The simple answer is that adding the 4 lines of code from +beginAnimations:context: to +commitAnimations is all you need to do to make these animate. When you toggle the UISegmentedControl in the app's toolbar, the views will smoothly animate between the two layouts. Really, that's all you have to do. View 0 is at the upper left hand corner in the grid view and rotated 90 degrees and all the way on the right side in the circle.

I don't have to tell that view how to get from the corner to the side. The animation system figures it out for me. If you toggle the grid/circle mode quickly, you'll see that the views stop in the middle of the animation and head off to their new location seamlessly. It's awesome. I recommend setting the animationDuration_ variable to something long, like 10 seconds so you can really play around with it.

One thing I haven't mentioned is that in iOS 4 and above, you should use blocks instead of the transaction style I'm using here. Blocks are recommended since it's easier to see everything together, and it's also easier to chain a series of animations together. With blocks, animations can be nested as well. I'll show that a little later, this post is getting long. Next time, I finish up with view animations, including animation delegates and blocks.

Aug 13, 2011

Core Animation Part 1: What is Core Animation?

Everything we do in user interfaces on our iPhones and iPads, and increasingly on our Macs is animated. Table views slide in and out of view smoothly. Buttons flow between different text and colors, popup views flip on to the screen. An indicator pulses smoothly to alert the user that his download is complete. When the device rotates, screen elements flow into place instead of just jumping jarringly between portrait and landscape.

This type of animation is more than just "eye candy". It helps the user feel connected to your application. It gives them important clues to how the application is operating, provides valuable visible feedback and projects a sense of high quality design to your user.

All of this is accomplished using Core Animation. Even if you have no idea what a CALayer is or never typed #import <QuartzCore/QuartzCore.h>  at the top of one of your header files you've used this technology as a developer. Animation is pervasive throughout the UIKit. UITableView has 11 methods which mention animated or animation, UINavigationController has 7. If you've ever pushed a view controller on a stack you've used it.

One key to understanding Core Animation is to know that it's extremely easy to use. If you've written standard animation code before you have to set up some sort of timer and animation loop. At each time interval, the system will call you back and tell you "Time is now .4 seconds." if you wanted your ball to move across the screen in 1 second, you have to compute the current position as 40 percent of the way between the start and end point, assuming you wanted to use linear interpolation. If you want a more natural movement, you might choose an easing function or some other type of animation timing function that gives more natural movement. Each time you get called you compute the updated position of everything in your scene and draw it. At 30 or 60 frames per second you get smooth motion.

If you want to do full 3D animation, such as in a game, or in my case, the 3D animated page curl we use in Google Books, you will have to do this sort of animation. Core Animation does have some 3D elements but at heart is is a 2D Technology (We might call it 2.5D).

Throw all that stuff out. With CA, you express intent. What I mean is that you express the state you want your object to start in, the state you want it to end in, and how long you want it to take. The CA system takes over for you from there. It's also possible to have the animation continue from the current state, so if you are moving something from right to left, and then decide it needs to move back to the right, it will pick up right in the middle of the previous animation. We'll see this in our demo application. The main screen of the app has two view modes - grid and circle, and smoothly animates the position of subviews to the desired end state even if the animation is still in progress. It's really quite amazing and requires no cleverness on the part of the developer.

The most basic element of Core Animation is the  layer (CALayer). That layer is then composited on to the screen using the graphics card so that you automatically get hardware accelerated rendering. Layers have animatable properties such as opacity, bounds, position, shadow and more. You animate layers by manipulating these properties, either directly, by attaching a concrete subclass of  CAAnimation to the layer, or implicitly, by simply setting the value of a property. Within a run loop, these property changes and animations are enclosed in a CATransaction so that they are all applied together. The animations run in their own thread, so they run without interfering with the rest of your program.

We'll get back to directly animating layers, the different types of layers and animations and implicit vs. explicit animation in future posts. In the next post, I'm going to talk about how you animate UIViews as opposed to CALayers using view animations. With nothing more than this, you'll see how easy it is to make some really cool animated user interfaces. We'll get into code as I explain how I implemented the home screen of my CADemo application. The code's up on GitHub if you want to follow along.

Aug 6, 2011

Introduction to Core Animation

I recently gave a talk introdcing the basics of Core Animation at the Boulder iOS Developers Meetup. I wrote an iPad application that shows off several of the aspects of Core Animation including UIKit animation, Basic Animations, Group Animations, Key Frame animation, Core Animation Transitions, Embedding video in a layer, 3D Transforms, and using Core Animation to build custom user interfaces.

Here's a screenshot of the app. May not look like much, but each pane of the UI shows a different demo and the entire application itself is implemented using Core Animation.

I'll be posting details of each of the demos and the application itself over the next several posts. My code is located on GitHub at https://github.com/PaulMaxime/CADemo if you'd like to follow along. The next post gives an introduction to Core Animation.