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.

Dec 2, 2008

Riding the Livestrong Challenge. Asking you for a contribution.

Hello friends-

As many of you know my life has been profoundly affected by cancer. Four years ago my dear former wife Susan died from metastatic breast cancer after a 15 year on and off battle with the disease. I was with her and held her hand as she passed. Just one year ago, my mother also died due to lung cancer after fighting for a couple of years herself. She had never smoked a day in her life. I know that many of you have faced this terrible disease either yourself or in your family. Everyone is or will be affected by this disease at some point in their life.

One of my favorite bloggers, Elden Nelson of fatcyclist.com is now facing the same battle with his wife also named Susan. His situation is remarkably similar to mine of four years ago. His Susan has had her cancer spread to her bones and her brain and she is on steroids and receiving oxygen. The many rounds of Chemotherapy have failed to eradicate the disease. She is now in hospice care.

Elden has decided to form a team to join the Livestrong Challenge. His goal is to raise over 1 million dollars for cancer research. We may not get a cure right away but we can strike a blow. I have decided to join with Elden and his Team Fatty in this year’s event in San Jose California. I plan on riding my bike the full 100 miles. We want to be the largest team in Livestrong history and raise the most money we can.

As part of this effort I’m asking you to give me a hand at raising as much money as possible. Please consider donating $25 or more to my effort on my personal page. I am planning on matching whatever amount I get personally, and then my employer, Google, will match me up to $3000. So, you can see that your donation will be multiplied by a factor of 3.

Thanks for your consideration in helping me in this effort.

Paul

Oct 30, 2008

Police nab bicycle thief thanks to stupidity.

This is a little different type of post for me, but last week I had three bicycles stolen from my locked storage unit at my apartment building here in San Francisco. Thanks to some quick thinking by one of Stacie's employees and a bit of finagling on my part, we were able to arrange for the "alleged" thief to get arrested and for me to recover at least one of the bikes. Instead of reposting the whole story I'd like to refer you to my daughter's blog: http://ilovelladro.blogspot.com/2008/10/sting-at-google.html

Oct 12, 2008

Organizing a complex UITableView with the iPhone SDK by using a dispatch table

Hi. I've been working on an iPhone SDK application for a while, and one major piece of this application is a fairly complex table view. The UITableView is the workhorse class of most iPhone applications - you see it everywhere. One problem with the table view is that the controller class tends to get very large and complex, especially if you have a multiple section table view that has different types of custom cells and behaviors. There are many things that can be done to make this job easier. One excellent technique was recently documented by Frasier Speirs: A technique for using UITableView and retaining your sanity.

I've developed a technique that can make use of this technique but that goes beyond it by allowing you to delegate the handling of each section in a table view to its own class. It is also easily configurable if you need to add or remove a section type from your controller later, and allows you to localize your changes so that you don't have to remember to modify code in several places.


As I'm sure many of you know, the UITableViewDelegate protocol contains about 16 methods that take either an NSIndexPath object that specifies a row in a specific section, or an NSInteger that specifies a section.

The most obvious way to implement this would be through a switch statement in each of these methods. In the case of my program, this rapidly became unworkable, since I have 5 different sections, some with their own custom view types and specific logic for each section that uses a different portion of the data model and logic to generate the table view.


The basis of my technique is to create a main view controller for the table view, in this case called NoteViewController, and separate section controllers for each section that inherit from an AbstractNoteSectionController class that I created. This class defines the interface for the section controllers and provides reasonable default behavior in case I don't need specific behavior in my subclasses. The interface for this class is:

@interface AbstractNoteSectionController: NSObject {
NSUInteger section;
NoteViewController *parent;
}

@property NSUInteger section;
@property (nonatomic,retain) NoteViewController *parent;

- (id)initWithNoteViewController:(NoteViewController *)cnt
                      andSection:(NSUInteger)aSection;

- (NSUInteger)numberOfRows;

- (UITableViewCell *)cellForRow:(NSUInteger)row;

- (UITableViewCellEditingStyle)editingStyleForRow:
   (NSUInteger)row;

- (void)selectedRow:(NSUInteger)row
    withEditingStyle:(UITableViewCellEditingStyle)style;

- (void)commitEditingStyle:(UITableViewCellEditingStyle)style
                    forRow:(NSUInteger)row;

- (BOOL)canEditRow:(NSUInteger)row;

- (BOOL)shouldIndentWhileEditingRow:(NSUInteger)row;

@end

I've only implemented the table view delegate methods that I need for my application. I then subclass this and implement the specific functionality for each section in a separate subclass. In the main NoteViewController class, I have a sectionDict instance variable that has an NSDictionary object keyed by the section number:

// Build a dispatch table so we can more
// easily navigate all the sections.
- (NSMutableDictionary *)buildSectionDict {
  return [NSMutableDictionary dictionaryWithObjectsAndKeys:
    [[[FirstSectionController alloc]
      initWithNoteViewController:self
                      andSection:FIRST_SECTION] autorelease], 
    [NSNumber numberWithUnsignedInteger:FIRST_SECTION],
...
    [[[LastSectionController alloc]
      initWithNoteViewController:self
                      andSection:LAST_SECTION] autorelease],
    [NSNumber numberWithUnsignedInteger:LAST_SECTION], nil];
}

Then, I have a method to select the section controller given a section:
- (AbstractNoteSectionController *)
    sectionControllerForSection:(NSUInteger)aSection {
  NSNumber *section =
    [NSNumber numberWithUnsignedInteger:aSection];
  return (AbstractNoteSectionController *)
    [sectionDict objectForKey:section];
}
After this, something like tableView:numberOfRowsInSection: is reduced to this simple piece of code from a fairly complex and difficult to maintain switch statement:

- (NSInteger)tableView:(UITableView *)tableView
  numberOfRowsInSection:(NSInteger)section {
  return [[self sectionControllerForSection:section]
    numberOfRows];
}

This allows each section controller to be focused and readable. For example, the numberOfRows method for the PhotosSectionController is very simple.

- (NSUInteger)numberOfRows {
  return ((self.cameraAvailable) ? 2 : 1) +
      [parent.note countOfPhotos];
}

I suppose that I should mention at this point that each section controller has a parent member that points to the NoteViewController which still mediates access to the model (parent.note)


I love programming with this type of dispatch table. It makes for extremely flexible code that is not at all brittle, because I can add or remove sections from the table view by simply editing the buildSectionDict method and writing a new subclass of AbstractNoteSectionController.  No other methods of the NoteViewController ever have to change, which is great because it gives me fewer things to think about.

Using this technique, I was able to split up a hard to read and manage 700 line behemoth class into much more managable 80-200 line pieces and improve the readability and flexibilty of my code immensely. Hope you find this useful in your iPhone development.

Jul 30, 2008

Frustration over iPhone SDK NDA

Just a short post. I wanted to express my frustration over the situation with the iPhone SDK and the NDA. I've been itching to start posting about iPhone development for a while now, and figured that I'd be able to do so as soon as iPhone OS 2.0 shipped. Here's hoping that Apple will make the decision to lift it soon. 

Some links about this topic:


These are just two of many. Come on, Apple allow us to blog, write books and discuss the SDK. The community will grow and you'll get better apps.