Combining Blocks, Properties and Animation in Objective-C

Blocks are a recent addition to iOS and Mac development. It’s a C-level feature, and is available in the XCode tools in Snow Leopard and for iOS 4.0 and later. The support in UIKit and Foundation libraries is still a work in progress, but a lot of important Cocoa classes already offer support for this language feature.

I’m not going to explain all the details of blocks. The Apple documentation does a fair job of that already. Good resources are Block Programming Topics and A Short Practical Guide To Blocks. Instead of rehashing that stuff, I’d like to explore some ideas of how to use blocks in our Objective-C programs. Learning any new language feature is like getting a new hammer for Christmas: You start looking for nails. I’m still early in my search for nails to hit with this hammer, but the first thing I wanted to try was inspired by my very limited knowledge of the javascript language.

Javascript, and other languages, have the concept of closures, which is a powerful but often poorly understood concept, but it forms the basis for a lot of the really cool stuff that’s recently emerged in web development, like jQuery. Blocks are essentially closures for Objective-C. And I wondered if you could therefore start assigning anonymous bits of code (and the variables in their scope) to object properties in Objective-C. It turns out you can, and it’s awesome!

I will step through some code examples to show you what I mean. And I’ve posted the sample project that all this code comes from here: https://github.com/silromen/ObjectiveScript

This sample project, called ObjectiveScript, is pretty simple. There’s an image we can animate by tapping it. We can switch how the image animates by tapping one of three buttons: Rotate, Scale and Transform. Once a button is tapped, we tap the image and it performs the chosen type of animation.

There are two main classes: the ViewController and the View which contains the image we want to animate.

Let’s start with the animateable view, here called SpecialView:


//  SpecialView.h

#import
@interface SpecialView : UIView {
	void (^animation)(void);
}

@property (nonatomic, copy) void (^animation)(void);
@end

Look at that property declaration! It’s saying that this object has a property called animation that we can assign an arbitrary block of code to, which returns nothing and takes no arguments. This is different than declaring a method, because the code in a method has to be defined at compile time. With a block, we can assign any code we want to that property at run time.

Now let’s look at the source code for our SpecialView.


//  SpecialView.m

#import "SpecialView.h"

@implementation SpecialView
@synthesize animation;

- (void)dealloc {
    [super dealloc];
	[animation release];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
	[UIView animateWithDuration:0.85
						  delay:0
						options:UIViewAnimationOptionRepeat|UIViewAnimationOptionAutoreverse
					 animations:self.animation
					 completion:^(BOOL finished){
						 self.transform = CGAffineTransformIdentity;
					 }];
}
@end

There’s not a lot of code, but there’s a lot going on here. We’re synthesizing and releasing our property – that’s normal. We’re overriding touchesEnded and defining an animation we want to occur whenever a touch event ends on this view. Now, in the old days (last year), without going deep into Core Animation Layers, you animated a view by defining some changes to the view you wanted animated and bookending those instructions with [UIView beginAnimations: context:] and [UIView commitAnimations], and if it pleased you, you could specify a selector to run when the animation completed. But since iOS 4.0, there’s a block-friendly way of doing exactly the same thing, and that’s what is happening in this code sample. We’re calling the class method animateWithDuration:delay:options:animations:completion:. The animations and completion parameters are accepting blocks of anonymous code. What’s really special in this example, is we’re passing the contents of our view’s animation property as the block to execute for the animation. The completion code is always going to be the same, which is to undo any transformation that the animation has performed on the view by setting the UIView transform property to the identity matrix. But the animation itself could be any chunk of code we assign that animation property to.

So let’s see how the ViewController puts that cool feature into use.


//  ObjectiveScriptViewController.h

#import
#import "SpecialView.h"

@interface ObjectiveScriptViewController : UIViewController {
	IBOutlet SpecialView *specialView;
	IBOutlet UIButton *rotateButton;
	IBOutlet UIButton *translateButton;
	IBOutlet UIButton *scaleButton;
}

- (IBAction)onRotate:(id)sender;
- (IBAction)onTranslate:(id)sender;
- (IBAction)onScale:(id)sender;
- (void)setSelected:(UIButton*)sender;
@end

In the header, we have our outlets for the special view and the buttons. We have our handlers which are called in response to the buttons being touched, and we have a helper method that will be used to make the three buttons behave like radio buttons, so only one is selected at a time.

 
//  ObjectiveScriptViewController.m

#import "ObjectiveScriptViewController.h"

@implementation ObjectiveScriptViewController

- (void)dealloc {
	[specialView release];
        [super dealloc];
}

- (IBAction)onRotate:(id)sender {
	specialView.animation = ^{
		 [UIView setAnimationRepeatCount:1.0];
		specialView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, 135.0);
	};
	[self setSelected:(UIButton*)sender];
}

- (IBAction)onTranslate:(id)sender {
	specialView.animation = ^{
		 [UIView setAnimationRepeatCount:1.0];
		specialView.transform = CGAffineTransformMakeTranslation(-175, -175);
	};
	[self setSelected:(UIButton*)sender];
}

- (IBAction)onScale:(id)sender {
	specialView.animation = ^{
		 [UIView setAnimationRepeatCount:1.0];
		specialView.transform = CGAffineTransformMakeScale(0.1, 0.1);
	};
	[self setSelected:(UIButton*)sender];
}

- (void)setSelected:(UIButton*)sender {
	rotateButton.selected = ([sender isEqual:rotateButton]) ? YES : NO;
	translateButton.selected = ([sender isEqual:translateButton]) ? YES : NO;
	scaleButton.selected = ([sender isEqual:scaleButton]) ? YES : NO;
}
@end

Notice how each button’s handler method assigns a chunk of animation code to the SpecialView’s animation property. Tapping these buttons doesn’t execute the animation, but they tell the view which animations to perform by feeding it the code to execute.

The combination of blocks, properties and core animation is powerful, and this example is just scratching the surface. This gives developers another way to conceptualize and organize their code, and having more options is always a good thing.

Advertisements