Monday, January 3, 2011

How To Use UIView Animation Tutorial

Use animation to see what's inside this picnic basket!
Use animation to see what's inside this picnic basket!
One of the coolest things about iPhone apps is how animated many of them are. You can have views fly across the screen, fade in or fade, out, rotate around, and much more!
Not only does this look cool, but animations are good indicators that something is going on that a user should pay attention to, such as more info becoming available.
The best part about animation on the iPhone is that it is incredibly easy to implement programmatically! It’s literally just a couple lines of code and you’re up and running.
In this tutorial, you’ll get a chance to go hands-on with UIView animation to create a neat little app about going on a picnic. The picnic basket opens in a neat animated way, and then you get to look what’s inside – and take decisive action!
In the process, you’ll learn how to use UIView animations in both the standard way and the post iOS4 manner, learn how to chain UIView animations, and learn how to tell the position of views while animations are running.
So grab your picnic basket and let’s get started!

Introduction to UIView Animation

Just so you can appreciate how nice and easy UIView animation is, consider what you’d have to do to animate a view moving across the screen if iOS didn’t provide you with built-in animation support:
  • Schedule a method to be called in your app every frame.
  • Every frame, calculate the new position of the view, based on the desired final destination, the time to run the animation, and the time run so far.
  • Update the view’s position to the calculated position.
That’s not a ton of work, but it’s annoying enough that it might make you think twice about implementing an animation. Plus, it gets a lot more complicated to keep track of the more animations you do.
But don’t worry – animations are extremely easy to use in iOS! There are certain properties on views, such as the view’s frame (its size and position), alpha (transparency), and transform (scale/rotation/etc.) which have built-in animation support. So instead of having to do all of the above, you simply:
  • Set up an animation, specifying how long it should run and a few other optional parameters.
  • Set an animatable property on a view, such as its frame, and start the animation running.
  • That’s it – UIKit will take over handle the calculations and updates for you!
So let’s dive in and see what this looks like in code, by creating an animation of a picnic basket opening when you start up the app.

An Opening Picnic Basket

Go to “File\New Project…”, choose iOS\Application\View-based Application, and click “Choose…”. Name the project Picnic, and click Save.
Next, download a copy of some images and sounds made by my lovely wife that you’ll need for this project. Unzip the file and drag the files to the Resources folder of your project. Verify that “Copy items into destination group’s folder (if needed”) is checked, and click Add.
Then double click on Resources\PicnicViewController.xib to bring up Interface Builder. Drag two UIImageViews to the view controller, one on top filling up about half the space, and one on the bottom filling up the bottom half. Set the top image view to door_top.png (and View\Mode to Top), and the bottom image to door_bottom.png (and View\Mode to Bottom), and resize the image views until they look OK, as shown below.
Basket View
Now open PicnicViewController.h and add two properties for the image views:
@property (assign) IBOutlet UIImageView *basketTop;
@property (assign) IBOutlet UIImageView *basketBottom;
You may be wondering why you didn’t create instance variables for these properties. Well, XCode has a relatively new feature where if you create a property, it will automatically create an instance variable backing the property. Cool, huh?
Also note that the properties are marked as assign here, just to make things easier for us (i.e. no need to release the instance vars since the class won’t be retaining them).
Now save the header and go back to PicnicViewController.xib and control-click on “File’s Owner” to bring up the menu. Control-drag on the circle next to basketBottom and connect it to the bottom image view, and control-drag on the circle next to basketTop and connect it to the top image view.
Now that you’ve connected the views you’ve created in Interface Builder to properties, you can animate them to open the basket when the view first appears. Switch to PicnicViewController.m and make the following modifications:
// At top, under @implementation
@synthesize basketTop;
@synthesize basketBottom;
 
// Uncomment viewDidLoad and add the following inside
CGRect basketTopFrame = basketTop.frame;
basketTopFrame.origin.y = -basketTopFrame.size.height;
 
CGRect basketBottomFrame = basketBottom.frame;
basketBottomFrame.origin.y = self.view.bounds.size.height;
 
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelay:1.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
 
basketTop.frame = basketTopFrame;
basketBottom.frame = basketBottomFrame;
 
[UIView commitAnimations];
Here you first calculate where to move the top and bottom images to – basically you move them just offscreen.
Then comes the fun part – the animation code! It uses beginAnimations:context to start an animation block, and then sets three parameters. The animation is set with a duration of 0.5 seconds, and not to start until 1 second in (so you can enjoy the pretty basket for a second), and the animation curve is set to ease out (so the animation goes a little bit slower at the end).
Then the frames of the two image views are set to their final destinations, and you call commitAnimations to start the animations. UIKit takes over from there and runs a neat animation of your basket opening up.
Compile and run the code to try it out for yourself – pretty easy to get such a neat effect, eh?
Basket Halfway Open

Alternate method for iOS 4+

The animation code as-written above is pretty easy, but there’s an even easier method on iOS 4. In iOS 4, UIView has some new methods you can use that use blocks:
  • animateWithDuration:animations:
  • animateWithDuration:animations:completion:
  • animateWithDuration:delay:options:animations:completion:
These methods let you put all of the parameters that you would have had to write multiple lines for in a single call. And the best part is it makes it really easy to call a block of code when the animation finishes too, via the completion parameter.
By the way, don’t be scared of blocks! Blocks have a funky syntax to get used to at first, but they can really help make your code a lot more terse and easier to read, and help keep related blocks of code closer together. Try them out and you’ll get used to them in no time!
So let’s give this a try. Go ahead and replace the code starting at [UIView beginAnimations…] and ending with [UIView commitAnimations] with the following alternative method:
[UIView animateWithDuration:0.5
    delay:1.0
    options: UIViewAnimationCurveEaseOut
    animations:^{
        basketTop.frame = basketTopFrame;
        basketBottom.frame = basketBottomFrame;
    } 
    completion:^(BOOL finished){
        NSLog(@"Done!");
    }];
This uses one of the new methods, specifying a block for the animations to run, and a block of code to be called when the animations complete. This does exactly what the code used to do, except it’s more terse, and we have an easy way to know when the animations are done.
So compile and run the code, and you should see the basket slide open as usual, but also see a console message when the basket is fully opened!
By the way – if you want to use the pre-iOS4 method and get notice when the animation completes, you can do that with the setAnimationDidStopSelector method.

A Second Layer of Fun

When you go out on a picnic, you usually don’t just throw your food straight into the basket – instead you put a little napkin on top to shield the food from pesky infiltrators. So why not have a little more fun with animations and add a napkin layer to open as well?
Go back to Interface Builder, and note that since the Image Views are taking up the whole screen, it’s going to be hard to keep track of things. To deal with this, let’s name the image views that are already there.
Click on the top image view, and in the Identity Inspector, set the name of the image view to “Basket Top”. Note this doesn’t affect anything in code, it just makes it easier to identify your views in the XIB. Repeat with the bottom image view, setting the name to Basket Bottom. At this point, your XIB should look like the following:
Using names in a XIB
Now, select the two Image Views in Interface Builder and go to Edit\Duplicate. Rename the two new views to “Napkin Top” and “Napkin Bottom”, and drag them under the UIView so they are children. Move the views above the two Basket views, since views are listed from bottom->top and you want the napkin to be below the basket. Finally, select each napkin image view individually, move it to the right spot, and set the image to XXX. At this point, your XIB should look like the following:
Adding napkins UIImageViewControllers to the XIB
Now that you have the new image views in your XIB, see if you can animate this yourself based on everything you’ve learned! The goal is to make the napkin move off screen also, but start moving slightly after the picnic basket starts moving. Go ahead – you can always check back here if you get stuck.
…
…waiting…
…
…waiitng…
…
…waiting…
…
Tomato-San is angry!
Tomato-San is angry!
What?! Are you still reading here?! You can do it – go ahead and try! :]

The Solution

In case you had any troubles, here’s the solution.
First add two new properties to PicnicViewController.h:
@property (assign) IBOutlet UIImageView *napkinTop;
@property (assign) IBOutlet UIImageView *napkinBottom;
Save your header, then in Interface Builder, control-click “File’s Owner” to bring up the menu of outlets, and connect the two napkin outlets to their appropriate image views.
Switch to PicnicViewController.m and make the following modifications:
// At top, under @implementation
@synthesize napkinTop;
@synthesize napkinBottom;
 
// At bottom of viewDidLoad
CGRect napkinTopFrame = napkinTop.frame;
napkinTopFrame.origin.y = -napkinTopFrame.size.height;    
CGRect napkinBottomFrame = napkinBottom.frame;
napkinBottomFrame.origin.y = self.view.bounds.size.height;
 
[UIView animateWithDuration:0.7
                      delay:1.2
                    options: UIViewAnimationCurveEaseOut
                 animations:^{
                     napkinTop.frame = napkinTopFrame;
                     napkinBottom.frame = napkinBottomFrame;
                 } 
                 completion:^(BOOL finished){
                     NSLog(@"Done!");
                 }];
Compile and run your code, and you should see the basket open in an even cooler manner!
Double door effect with UIView animation

How To Chain Animations

So far you’ve just been animating a single property on these UIViews – the frame. Also, you’ve done just a single animation, and then you were done.
However as mentioned earlier in this article, there are several other properties you can animate as well, and you can also trigger more animations to run after one animation completes. So let’s try this out by experimenting with animating two more properties (center and transform) and using some animation chaining!
But first – let’s add the inside of the picnic basket! Open up PicnicViewController.xib and drag yet another UIImageView as a subview of the View. Make sure it’s at the top of the list (so it’s underneath everything else) and set the image to plate_cheese.png. Resize the image view so it fills up the whole screen. You also might want to set the name of the Image View to “Contents” so it’s easy to identify.
There’s one more thing you have to add. Somehow, despite all of your precautions, a sneaky bug has made its way into the basket! Add another UIImageView as a subview of the View. Put it right underneath the Contents View, and set the image to bug.png. Set its frame to X 160, Y 185, width 135, height 142 in the Size Inspector. You also might want to set the name of the Image View to “Bug” so it’s easy to identify.
At this point, your XIB should look like the following:
Final XIB Layout
Next add a property for the new pest image view in PicnicViewController.h:
@property (assign) IBOutlet UIImageView *bug;
Save your header, switch back to Interface Builder, and connect the bug image view to the outlet in the same way you did with the other outlets earlier in this tutorial.
Next switch to PicnicViewController.m, and make the following modifications:
// At top, under @implementation
@synthesize bug;
 
// Add two new methods
- (void)moveToLeft:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationDelay:2.0];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(faceRight:finished:context:)];
    bug.center = CGPointMake(75, 200);
    [UIView commitAnimations];
 
}
 
- (void)faceRight:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationDelay:0.0];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(moveToRight:finished:context:)];
    bug.transform = CGAffineTransformMakeRotation(M_PI);
    [UIView commitAnimations];
 
}
 
- (void)moveToRight:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationDelay:2.0];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDidStopSelector:@selector(faceLeft:finished:context:)];
    bug.center = CGPointMake(230, 250);
    [UIView commitAnimations];
 
}
 
- (void)faceLeft:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationDelay:0.0];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDidStopSelector:@selector(moveToLeft:finished:context:)];
    bug.transform = CGAffineTransformMakeRotation(0);
    [UIView commitAnimations];
 
}
 
// Call the method at the bottom of viewDidLoad
[self moveToLeft:nil finished:nil context:nil];
First, note that we’re using the pre-iOS4 method of running the animations here, instead of the iOS4+ method we’ve been using lately. This is because there is a problem you use the iOS4+ method – the view doesn’t respond to touches while the animations are running! I’m not sure if this is a bug or I’m doing something wrong, but either way we need touches later on in this tutorial so we’ll revert to the pre-iOS4 method.
Update: Deian from the comments section has pointed out that you can enable UIView to continue to respond to touches in the iOS4 method by using UIViewAnimationOptionAllowUserInteraction in the options, w00t!
Second, you can see that the way animation chaining is done is by a) using setAnimationDelegate to set the view controller as the delegate, and b) using setAnimationDidStopSelector to set up a callback when the animation finishes. The callback just starts up the next animation, and continues the chain. The chain is move left -> face right -> move right -> face left -> move left again, and continue.
Third, note that we’re moving the bug by using the center property rather than the frame property. This sets where the center of the bug image is, which is a little easier to do than modifying the frame sometimes.
Fourth, note that you can get the right transform to rotate the bug, by using a little helper function called CGAffineTransformMakeRotation. The angle here is in radians, so you use M_PI to rotate 180 degrees.
Compile and run the code, and you should see the bug scurrying about!
Eek!  A bug's in our picnic!

Squash the Bug!

Now it’s the moment I know you’ve been waiting for – it’s time to squash that bug!
But first we have to detect when you’re tapping the bug. Try adding this code to PicnicViewController.m first:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInView:self.view];
 
    CGRect bugRect = [bug frame];
    if (CGRectContainsPoint(bugRect, touchLocation)) {
        NSLog(@"Bug tapped!");
    } else {
        NSLog(@"Bug not tapped.");
        return;
    }
 
}
This implements the touch handler, and tries to compare the point tapped to the frame of the bug. Seems like that should work, right? Well if you compile and run, you’ll see something weird: it will appear to work sometimes, but after the bug rotates, if you tapped on the bug it won’t work. However if you click in the area he’s facing, it will say you tapped the bug (which you obviously didn’t) – WTF!
Well, the reason this doesn’t work is because when an animation is running, it only updates the position of the view in something called the “presentation layer”, which is what is shown to screen. We’ll talk more about layers and what this all means in a future tutorial, but for now all you have to do is replace the line that gets the bugRect with this:
CGRect bugRect = [[[bug layer] presentationLayer] frame];
Now you will get the correct frame of the bug even when it’s moving! Mwuahaha… you know what’s coming next! First add the following instance variable to PicnicViewController.h:
bool bugDead;
Then make the following modifications to PicnicViewController.m:
// At the beginning of faceLeft, moveToRight, faceRight, and moveToLeft:
if (bugDead) return; 
 
// At bottom of touchesBegan
bugDead = true;
[UIView animateWithDuration:0.7 
                      delay:0.0 
                    options:UIViewAnimationCurveEaseOut
                 animations:^{                              
                     bug.transform = CGAffineTransformMakeScale(1.25, 0.75);
                 } 
                 completion:^(BOOL finished) {  
                     [UIView animateWithDuration:2.0 
                                           delay:2.0 
                                         options:0
                                      animations:^{                      
                                          bug.alpha = 0.0;
                                      } completion:^(BOOL finished) {
                                          [bug removeFromSuperview];
                                          bug = nil;
                                      }];                 
                 }];
Once the bug is tapped, it sets bugDead to true (so the animation chain stops running), and then uses the iOS4+ method (since we don’t care about touches anymore and this is more terse) so set up a mini-chain of animations.
It first squishes the bug (by applying a scale transform), and then makes the bug fade away (by setting the alpha to 0 after a delay). Finally, when it’s done it removes the bug from its super view and sets it to nil.
Compile and run the code, and now you should be able to squash the bug!
One squashed bug!

Gratuitous Sound Effect

This is totally unnecessary, but also totally fun – let’s add a sound effect when we squash the bug!
In XCode, right click on the Frameworks group, and choose Add\Existing Frameworks…. Choose AudioToolbox.framework, and click Add.
Then mad the following modifications to PicnicViewController.m:
// At top of file
#import <AudioToolbox/AudioToolbox.h>
 
// At bottom of touchesBegan
NSString *squishPath = [[NSBundle mainBundle] 
                        pathForResource:@"squish" ofType:@"caf"];
NSURL *squishURL = [NSURL fileURLWithPath:squishPath];
SystemSoundID squishSoundID;
AudioServicesCreateSystemSoundID((CFURLRef)squishURL, &squishSoundID);
AudioServicesPlaySystemSound(squishSoundID);
And that’s it! Compile and run your code and you can now squash the bug and get major audio satisfaction in the process.

Where To Go From Here?

Here is a sample project with all of the code we’ve developed in the above tutorial.
Now that you know the basics of using UIView animations, you might want to take a look at the Animations section in the View Programming Guide for iOS for additional useful info.
Now that you know a bit about UIView animations, you might want to check out the next tutorial about one of the underlying technologies of animation – layers! There are a lot of cool things you can do with layers, but they can be confusing at first, so I wanted to talk about that. This tutorial is also a good segway into Core Animation, which is the technology UIView animation is based upon.
How do you use UIView animation or Core Animation in your projects?

Introduction to CALayers Tutorial

Easily create rounded corners, shadows, and more by using CALayers!
Easily create rounded corners, shadows, and more by using CALayers!
If you’ve been programming for the iPhone, you’re probably really familiar with UIViews – buttons, text areas, sliders, web views, and more are all subclasses of UIView.
But you might not know much about the technology that UIView is built upon: CALayers! At least I didn’t, for quite a while.
It’s good to know a bit about CALayers, because you can use them to create some neat visual effects really easily. They’re also important to understand to work with Core Animation, which we’ll be discussing in a future tutorial.
In this tutorial, you’re going to learn the basics of using CALayers by making a simple app to create a layer and experiment with how it looks. In the process, you’ll learn what layers are, some neat properties you can set, and how to put images and custom-drawn content inside.
This tutorial assumes a basic familiarity with iPhone programming. If you are completely new, you might want to begin with the How To Create A Simple iPhone App Tutorial Series first.
So let’s get layering!

What Are CALayers?

CALayers are simply classes representing a rectangle on the screen with visual content.
“But wait a darn minute,” you may say, “that’s what UIViews are for!” That’s true, but there’s a trick to that: every UIView contains a root layer that it draws to! You can access this layer (created for you by default) with following bit of code:
CALayer *myLayer = myView.layer;
The neat thing about the CALayer class is that it contains a lot of properties that you can set on it to affect the visual appearance, such as:
  • The size and position of the layer
  • The layer’s background color
  • The contents of the layer (an image, or something drawn with Core Graphics)
  • Whether the corners of the layers should be rounded
  • Settings for applying a drop shadow to the layer
  • Applying a stroke around the edges of the layer
  • And much more!
You can use these properties to create some neat effects. For example, maybe you want to take an image, and put it in a white frame, and apply a shadow to it to make it look neat. Rather than whipping out Photoshop or creating a bunch of Core Graphics code, you can do this in a couple lines of code using CALayers!
The other neat thing about the properties on CALayer is that most of them are animatable. For example, you could start your image out with rounded corners, tap a button, and have it animate the corners back out to straight. This can make for some really neat effects!
You can use a CALayer on its own, or you can use one of the handy subclasses that are available, such as CAGradientLayer, CATextLayer, CAShapeLayer, and others. The default layer class for a UIView is the plain-old CALayer class, and that’s what you’ll be focusing on in this tutorial.

Getting Started

There’s no better way to understand using CALayers than to try them out yourself! So load up XCode, go to File\New Project, choose iOS\Application\View-based Application, and click “Choose…”. Name the project LayerFun, and click Save.
The View-based Application template starts with a single view controller, and as you know, each view controller has a root view. And as you learned above, every view has a root layer.
So now you’ll try this out for yourself by changing some properties on the view’s layer.
But first things first. To use CALayers and Core Animation, you need to use a framework that isn’t included by default in the View-based template: QuartzCore. So add it to your project by control-clicking the Frameworks group, selecting Add\Existing Framework…, and choosing QuartzCore.framework from the dropdown list.
Now that you’ve included QuartzCore, make the following changes to LayerFunViewController.m:
// Import QuartzCore.h at the top of the file
#import <QuartzCore/QuartzCore.h>
 
// Uncomment viewDidLoad and add the following lines
self.view.layer.backgroundColor = [UIColor orangeColor].CGColor;
self.view.layer.cornerRadius = 20.0;
self.view.layer.frame = CGRectInset(self.view.layer.frame, 20, 20);
Let’s go over this bit by bit since this is new stuff:
  • To get a pointer to the layer for the view, you just use self.view.layer. Remember, you can the root view for a View Controller by calling self.view. Once you have the view, you can get its default layer (created automatically) by using view.layer. By default, the layer is a CALayer (not a subclass).
  • Next you set the layer’s background color to orange. Note that the backgroundColor property actually takes a CGColor, but it’s easy to convert a UIColor to a CGColor by using the CGColor property.
  • Next you round the corners a bit by setting the corner radius. The value you pass in is the radius of the circle which makes up the corner, and it chooses 20 here for a nice rounded edge.
  • Finally, you shrink the frame a bit so it’s easier to see, by using the handy CGRectInset function. The CGRectInset function takes a frame and an amount to shrink it by (for both the X and Y), and returns a new frame.
Compile and run your code, and you should see a rounded orange rectangle in the middle of your screen:
A Simple CALayer

CALayers and Sublayers

Just like UIViews can have subviews, CALayers can have sublayers as well. You can create a new CALayer very easily with the following line of code:
CALayer *sublayer = [CALayer layer];
Once you have a CALayer, you can set any properties on it you’d like. But remember there’s one property you definitely have to set: it’s frame (or bounds/position). After all, the layer needs to know how big it is (and where it’s located) in order to draw itself! When you’re done, you can add your new layer as a sublayer of another layer by the following line of code:
[myLayer addSublayer:sublayer];
Ok, now try this out for yourself by adding a simple sublayer to the view’s layer. Add the following lines of code inside viewDidLoad, right after where you left off last time:
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [UIColor blueColor].CGColor;
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = [UIColor blackColor].CGColor;
sublayer.shadowOpacity = 0.8;
sublayer.frame = CGRectMake(30, 30, 128, 192);
[self.view.layer addSublayer:sublayer];
This creates a new layer, and sets a few properties on it – including a few you haven’t seen before to set shadows. You can see here how easy it is to set shadows on a layer – which can give some amazing effects with very little effort!
After setting the properties, it sets the frame of the layer and adds it as a sublayer to the view’s layer. Remember that these coordinates are relative to the parent layer’s frame, and since the parent layer begins at (20,20), the sublayer will be offset an additional (30,30) from that, so on the screen it will be at (50,50).
Compile and run your code, and you should now see a blue sublayer on the screen!
A CALayer Sublayer with a Shadow

Setting CALayer Image Content

One of the coolest things about CALayers is that they can contain more than just plain colors. It’s extremely easy to have them contain images instead, for example.
So let’s replace the blue sublayer with an image instead. You can take a Default.png from one of your iPhone apps to use as a test, or you can download a splash screen from one of my apps.
Add the splash screen to your project, and inside viewDidLoad right before the last line you added that adds the sublayer to self.view.layer, add the following lines of code:
sublayer.contents = (id) [UIImage imageNamed:@"BattleMapSplashScreen.png"].CGImage;
sublayer.borderColor = [UIColor blackColor].CGColor;
sublayer.borderWidth = 2.0;
This sets the contents of the layer to an image (that’s literally all it takes!) and also sets the borderColor and borderWidth to set up a black stroke around the edges, to demonstrate how that works.
Compile and run your code, and you should now see the blue layer’s contents replaced with your splash screen image!
A CALayer with Image Content

A Note about Corner Radius and Image Content

Now that you have this working, you might think it would be cool to round the corners of the splash screen as well, by setting the cornerRadius.
Well the problem is that as far as I can tell, if you set that on a CALayer with image contents, the image will still be drawn outside the corner radius boundary. You can solve this by setting sublayer.masksToBounds to YES, but if you do that the shadows won’t show up because they’ll be masked out!
I found a workaround by creating two layers. The outer layer is just a colored CALayer with a border and a shadow. The inner layer contains the image, and is also rounded but set up to mask. That way, the outer layer can draw the shadow, and the inner layer can contain the image.
Try this out by modifying the code to create the sublayer as follows:
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [UIColor blueColor].CGColor;
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = [UIColor blackColor].CGColor;
sublayer.shadowOpacity = 0.8;
sublayer.frame = CGRectMake(30, 30, 128, 192);
sublayer.borderColor = [UIColor blackColor].CGColor;
sublayer.borderWidth = 2.0;
sublayer.cornerRadius = 10.0;
[self.view.layer addSublayer:sublayer];
 
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = sublayer.bounds;
imageLayer.cornerRadius = 10.0;
imageLayer.contents = (id) [UIImage imageNamed:@"BattleMapSplashScreen.png"].CGImage;
imageLayer.masksToBounds = YES;
[sublayer addSublayer:imageLayer];
Compile and run your code, and now your image will have rounded corners!
A CALayer with Image Content and Corner Radius

CALayer and Custom Drawn Content

If you want to custom-draw the contents of a layer with Core Graphics instead of putting an image inside, that is quite easy too.
The idea is you set a class as the delegate of the layer, and that class needs to implement a method named drawLayer:inContext. This can contain Core Graphics drawing code to draw anything you want in that space.
Let’s try this out by adding a new layer, and drawing a pattern inside it. You’ll set the view controller as the delegate of the layer, and implement the drawLayer:inContext method to draw the pattern. As for the pattern drawing code, you’ll be using the same code as in the Core Graphics 101: Patterns tutorial posted earlier on this site.
Add the following code at the bottom of your viewDidLoad to add a new layer that will be custom drawn:
CALayer *customDrawn = [CALayer layer];
customDrawn.delegate = self;
customDrawn.backgroundColor = [UIColor greenColor].CGColor;
customDrawn.frame = CGRectMake(30, 250, 128, 40);
customDrawn.shadowOffset = CGSizeMake(0, 3);
customDrawn.shadowRadius = 5.0;
customDrawn.shadowColor = [UIColor blackColor].CGColor;
customDrawn.shadowOpacity = 0.8;
customDrawn.cornerRadius = 10.0;
customDrawn.borderColor = [UIColor blackColor].CGColor;
customDrawn.borderWidth = 2.0;
customDrawn.masksToBounds = YES;
[self.view.layer addSublayer:customDrawn];
[customDrawn setNeedsDisplay];
Most of the code here you’ve seen before (creating a layer, setting properties such as shadow/cornerRadius/border/masksToBounds), however there are two new pieces:
  1. First, it sets the delegate of the layer to self. This means that this object (self) will need to implement the drawLayer:inContext method to draw the contents of the layer.
  2. Second, after it adds the layer, it needs to tell the layer to refresh itself (and call drawLayer:inContext) by calling setNeedsDisplay. If you forget to call this, drawLayer:inContext will never be called, so the pattern won’t show up!
Next add the code to implement drawLayer:inContext, as shown below:
void MyDrawColoredPattern (void *info, CGContextRef context) {
 
    CGColorRef dotColor = [UIColor colorWithHue:0 saturation:0 brightness:0.07 alpha:1.0].CGColor;
    CGColorRef shadowColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.1].CGColor;
 
    CGContextSetFillColorWithColor(context, dotColor);
    CGContextSetShadowWithColor(context, CGSizeMake(0, 1), 1, shadowColor);
 
    CGContextAddArc(context, 3, 3, 4, 0, radians(360), 0);
    CGContextFillPath(context);
 
    CGContextAddArc(context, 16, 16, 4, 0, radians(360), 0);
    CGContextFillPath(context);
 
}
 
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {
 
    CGColorRef bgColor = [UIColor colorWithHue:0.6 saturation:1.0 brightness:1.0 alpha:1.0].CGColor;
    CGContextSetFillColorWithColor(context, bgColor);
    CGContextFillRect(context, layer.bounds);
 
    static const CGPatternCallbacks callbacks = { 0, &MyDrawColoredPattern, NULL };
 
    CGContextSaveGState(context);
    CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
    CGContextSetFillColorSpace(context, patternSpace);
    CGColorSpaceRelease(patternSpace);
 
    CGPatternRef pattern = CGPatternCreate(NULL,
                                           layer.bounds,
                                           CGAffineTransformIdentity,
                                           24,
                                           24,
                                           kCGPatternTilingConstantSpacing,
                                           true,
                                           &callbacks);
    CGFloat alpha = 1.0;
    CGContextSetFillPattern(context, pattern, &alpha);
    CGPatternRelease(pattern);
    CGContextFillRect(context, layer.bounds);
    CGContextRestoreGState(context);
}
This code is literally copied and pasted from the Core Graphics 101: Patterns tutorial (just with a different color, and using the passed in context and layer bounds), so we aren’t going to cover it again here. If you want to know what it does, refer to the above tutorial.
That’s it! Compile and run the code, and you should now see a blue grip pattern below the image.

Where To Go From Here?

Here is a sample project with all of the code we’ve developed in the above tutorial.
At this point, you should be familiar with the concepts of CALayers and how to create some neat effects with them really easily. In future tutorials, I’ll show you how you can animate CALayers using Core Animation, and use some really handy subclasses of CALayer such as CAGradientLayer, CATextLayer, and CAShapeLayer.
In the meantime, check out the Core Animation Programming Guide, which does a great job talking about Core Animation, and more details about CALayers.
If you’ve found some good ways to use CALayers in your projects, please share below! :]

Rendering views using CALayer, Part 1

Rendering views using CALayer, Part 1

13 Nov
For myDrumPad the main pad buttons are images. I create a UIButton object, and use setBackgroundImage:forState: to customize which image will be used for each state (UIControlStateNormal and UIControlStateHighlighted mainly).  I customize the title label font, shadow and color, and voilà I have a pad button that simulates the look and feel of  a Korg padKONTROL. There’s just a few small problems with it.
  1. The images on the iPad are fairly large, and memory is at a premium.
  2. The size of these buttons can change in portrait vs. landscape. It’s time-consuming to export different versions from Photoshop for the different orientations.
  3. The buttons are sized differently depending on the size of the button grid (e.g. a 3×3 grid of buttons have larger images than 4×4 or 5×5 grids). If I resize these images on-the-fly, then the edges look blurred and aren’t well-defined.
  4. The retina display complicates all of this, meaning I have to have two versions of each image.
  5. I want to be able to customize the buttons to have different colors when you’re on different drum sets.
Because of that long list, simply using an image isn’t good enough. But instead of drawing my images using regular Core Graphics drawing routines, I’m going to use Core Animation Layers, or CALayers, to accomplish the same thing. Ultimately I want my buttons to be able to be animated, to change color, and to feel more “alive” than a static image could accomplish.

Before you jump straight to the code…

Before you go any farther, I highly suggest you download the WWDC 2010 videos, and watch the sessions on Core Animation. There’s a lot of great information there and the presenters go to great lengths to show how flexible Core Animation is. The summary though is that CALayer objects can be stacked and nested just like layers in Photoshop, and actually form the foundation for how UIViews interact. The interesting thing about Core Animation is that when a parent layer is animated, moved, or in any other way interacted with, the sub-layers come along for the ride.
Most people focus on Core Animation as a way of, duh, animating their UI. While this is the really powerful capability that Core Animation exposes, don’t be confused by its name. Using its simpler capabilities can give you quite a lot, even without the animation capabilities. Think of it as going to Disney Land; yes there are plenty of amazing rides that cost millions of dollars and years of engineering to produce, but you can still have a fantastic time riding the Teacups. Core Animation provides some remarkable tools to do high-speed sophisticated path and keyframe animations, but layers, borders, shadows and gradient layers are still very effective tools.

Starting with your view

Typically when you’re creating your own custom view, you’ll start by subclassing UIView. This is what I’ll be doing here as well, but one of the things many people don’t realize is that with every instance of a UIView object, you get a CALayer for free. The layer property contains a default backing-store that your view will render itself to. At this point you can either draw directly to the layer using Core Graphics drawing operations, or you can create additional sub-layers for your view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self setupLayers];
    }
    return self;
}
- (void)setupLayers {
    self.layer.cornerRadius = 10.0;
    self.layer.borderColor = [UIColor redColor].CGColor;
    self.layer.borderWidth = 1.0f;
    self.layer.shadowOffset = CGSizeMake(0.0f, 2.0f);
    self.layer.shadowOpacity = 1.5f;
    self.layer.shadowColor = [UIColor blackColor].CGColor;
    self.layer.shadowRadius = 2.5f;
    self.layer.anchorPoint = CGPointMake(0.5f, 0.5f);
}

This makes your view look something like the screenshot you see here. You’ll notice that, since our layer is empty except for the border, the shadow extends into the view itself. So, lets give our layer some content to display. The easiest way is to simply assign an image to the content property, but I want to be a bit more creative than that.
Instead, I’ll create a gradient inside this view. Add the following code to the end of the setupLayers method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.anchorPoint = CGPointMake(0.0f, 0.0f);
gradient.position = CGPointMake(0.0f, 0.0f);
gradient.bounds = self.layer.bounds;
gradient.cornerRadius = 10.0;
gradient.colors = [NSArray arrayWithObjects:
                   (id)[UIColor colorWithRed:0.82f
                                       green:0.82f
                                        blue:0.82f
                                       alpha:1.0].CGColor,
                   (id)[UIColor colorWithRed:0.52f
                                       green:0.52f
                                        blue:0.52f
                                       alpha:1.0].CGColor,
                   nil];
[self.layer addSublayer:gradient];

This gives you something along these lines instead. While this is starting out quite simply, you can see how simple interfaces can be drawn by using declarative routines as opposed to using regular images, or even procedural calls using Core Graphics.
As I create more and more sophisticated custom views using CALayer, I’ll add more to my blog on the subject. Before I go, I’ll show a screenshot of what I’ve managed to accomplish for my new buttons.

Monday, December 20, 2010

Objective C basics

Defining a Class

Much of object-oriented programming consists of writing the code for new objects—defining new classes. In Objective-C, classes are defined in two parts:
  • An interface that declares the methods and instance variables of the class and names its superclass
  • An implementation that actually defines the class (contains the code that implements its methods)

    Class Interface

    The declaration of a class interface begins with the compiler directive @interface and ends with the directive @end. (All Objective-C directives to the compiler begin with “@”.)
    @interface ClassName : ItsSuperclass
    {
    instance variable declarations
    }
    method declarations
    @end
     

    Methods for the class are declared next, after the braces enclosing instance variables and before the end of the class declaration. The names of methods that can be used by class objects, class methods, are preceded by a plus sign:
    + alloc;
    The methods that instances of a class can use, instance methods, are marked with a minus sign:
    - (void)display;
     
     
    If a return or argument type isn’t explicitly declared, it’s assumed to be the default type for methods and messages—an id. The alloc method illustrated earlier returns id.
    When there’s more than one argument, the arguments are declared within the method name after the colons. Arguments break the name apart in the declaration, just as in a message. For example:
    - (void)setWidth:(float)width height:(float)height;
     




Thursday, December 16, 2010

I found the answer to my question. It turns out that if your application records and plays, the iphone will by default reroute your playback to the top speaker (the one by your ear), rather than to the speaker at the bottom of the phone. This was the reason for the low volume. If you add the following code, you can force the iphone to route the audio to the bottom speaker.

UInt32 doChangeDefaultRoute = 1;

AudioSessionSetProperty (
kAudioSessionProperty_OverrideCategoryDefaultToSpe aker,
sizeof (doChangeDefaultRoute),
&doChangeDefaultRoute);

You also have to include the audiotoolbox framework. The avfoundation framework by itself will not allow this code to work.

Tips & Tricks for conditional iOS3, iOS3.2 and iOS4 code

Tips & Tricks for conditional iOS3, iOS3.2 and iOS4 code

In this post, I'll show you ways to determine which version of iOS you are running on and show you how to write a macro that can both conditionally compile and runtime switch between the code for different versions of iOS.

A project or target that supports multiple versions of iOS

To make an application target that runs on multiple versions of iOS is relatively simple:
  • Set the "Base SDK" in your projects settings to the newest version number of iOS whose features you may want.
  • Set the "iPhone OS Deployment Target" to the oldest version number of iOS that you will support
However, getting the target settings correct is the easy part of the problem. The hard part is using new features on newer iOS versions without breaking the app on older versions.

Running in the 3.1.3 simulator

Before getting to the actual code, it might be worthwhile to discuss how to run your projects in older versions of the simulator.
The simulator is an important part of iOS development (since it is much faster and simpler than running your code on the device). But Apple have removed SDK versions earlier than 3.2 from the current Xcode builds. This makes it hard to verify your apps on earlier devices unless you install on a physical device.
Support for 3.1.3 is relatively important since it is the last version of iOS supported by the original iPhone and iPod Touch and it will be a few months before iOS 4 exceeds 80% of the remaining iPhone and iPod Touch market.
To allow simulation in 3.1.3, you must install an old version of Xcode. If you are a registered iPhone developer, you can download Xcode 3.1.4 for Leopard with iPhone SDK 3.1.3 or Xcode 3.1.4 for Snow Leopard with iPhone SDK 3.1.3. Be careful to install these in a different location to your Xcode 3.2.3 with iOS3.2/iOS4 (either select a different hard disk or rename your existing /Developer directory before you install).
Once you've got an old version of Xcode, you'll want to duplicate your main target and set the Base SDK to 3.1.3 in this duplicate (because it won't exist in this version of Xcode). You should use a second target for this because you shouldn't risk messing with your main target just to run code in the simulator.

Using features from newer iOS versions while supporting older iOS versions

For example, if you want to start an iOS4 background task in an application that you want to run on earlier versions of iOS, then you'll need to use code like this:
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
    if ([[UIApplication sharedApplication]
        respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)])
    {
        UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
            beginBackgroundTaskWithExpirationHandler:^{}];

        // Perform work that should be allowed to continue in background

        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    }
#endif
There are three important components:
  1. The #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 4000 compile-time conditional. This ensures that if we choose to build this project with a Base SDK lower than 4.0, then it won't cause compile problems. This is essential for running in older versions of the simulator.
  2. The runtime check that UIApplication supports the beginBackgroundTaskWithExpirationHandler method. Since the final release build will be built against the 4.0 SDK (even if users install on SDK 3.0) this runtime check ensures that the method we need is available.
  3. Everything else between the #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 4000 and the #endif is the iPhone OS 4 code.

Making the conditional work less ugly

The problem with the previous code is the compile-time conditional and the runtime check for the presence of methods is cumbersome since you must remember to to both.
If you want to integrate both a compile-time check and a runtime check, a better approach would look like this:
IF_IOS4_OR_GREATER
(
    UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
        beginBackgroundTaskWithExpirationHandler:^{}];

    // Perform work that should be allowed to continue in background

    [[UIApplication sharedApplication] endBackgroundTask:bgTask];
);
We can implement this macro as follows:
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
#endif

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
#define IF_IOS4_OR_GREATER(...) \
    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
    { \
        __VA_ARGS__ \
    }
#else
#define IF_IOS4_OR_GREATER(...)
#endif
If we want to include something only in OS versions prior to a a specific version, then we don't need the conditional compilation (since we still want the code to appear when compiled in a later version. In this case, only a runtime check is required. You can either do this directly, or for symmetry with other macros, you could use:
#define IF_PRE_IOS4(...) \
    if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
    { \
        __VA_ARGS__ \
    }
Three interesting points to note about these macros:
  1. I use the kCFCoreFoundationVersionNumber to determine the iPhone OS at runtime. There are many examples on the web using [[UIDevice currentDevice] systemVersion] but that method requires a string comparison and potentially handling of major and minor numbers within the string components. A single double comparison is far more straightforward.
  2. I have not used the typical do { x } while (0) wrapper around the macro, so you can simply tack an else onto the end if you choose (and it doesn't need conditional compilation of its own).
  3. I use a variable argument list for the macro. This is so that any number of commas may appear in the contents without causing problems.
A final point... the kCFCoreFoundationVersionNumber definitions may not be in every version of the SDK (each SDK normally contains definitions for versions up to but not including itself), so you should conditionally define them yourself in case they're missing. Here's a handy list:
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_2_0 478.23
#endif

#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_1
#define kCFCoreFoundationVersionNumber_iPhoneOS_2_1 478.26
#endif

#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_2
#define kCFCoreFoundationVersionNumber_iPhoneOS_2_2 478.29
#endif

#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_0 478.47
#endif

#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_1
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_1 478.52
#endif

#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_2
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_2 478.61
#endif

#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
#endif

Better still: solutions that don't require macros

Better than a simple macro is a simple function. This is a valid solution where the contents of your conditional code does not itself contain OS specific code (only the condition itself requires OS specific logic).
A common example is handing separate layout for iPad and iPhone versions. Ordinarily, if you're compiling for iPad 3.2 and iPhone 3.1.3, you need the following code:
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 30200
    if ([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)] &&
        [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
    {
        // iPad specific layout changes
    }
    else
#endif
    {
        // iPhone layout
    }
You can handle this with a conditional macro like the IF_IOS4_OR_GREATER but a far better solution is:
if (isIPad())
{
    // iPad specific layout changes
}
else
{
    // iPhone layout
}
Where all the conditional pollution is tidily kept in your isIPad() function:
BOOL isIPad()
{
    IF_3_2_OR_GREATER
    (
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
        {
            return YES;
        }
    );
    
    return NO;
}

Conclusion

Apple doesn't exactly make it easy to support old versions of the iPhone SDK. I'm sure they want everyone to keep up to date or buy new devices if their current device can't be updated.
That's not always a realistic attitude for App Store developers. You can't expect all your customers to upgrade as soon as possible.
The important point when writing for multiple versions of the SDK is to keep as few conditionals as possible. You don't want to have thousands of conditionals in your code for supporting different versions. Every conditional is extra testing work since different behaviors must be fully exercised on all different platforms.
While the conditionals and functions I've talked about here will help, if you find yourself needing a lot of conditionals you may also want to consider design changes like instantiating different subclasses for different OS versions.