Postage and WORD SPIN – 2009 Best App Ever

January 7th, 2010 by Chris

We are honored that both Postage and WORD SPIN have been nominated in the 2009 Best App Ever awards. If you enjoy our apps, please consider heading over to the Best App Ever web site and voting!

Vote for Postage ~ Postcards in Best Visual Design
Vote for WORD SPINĀ® in Best Word Game

Comments Off

iPhone Animation Sequence

December 11th, 2009 by Jeff

When writing Postage and WORD SPIN we encountered the same problem again and again: the need to chain a sequence of animations together. For example, when the user navigates to the next page we first animate away the controls for the current view, switch views, then animate the controls in for the new view. Initially, we set the UIView delegate and then in our specified selector implementation kicked off the next animation which itself would eventually call back into a completion selector, and so on. But it’s largely the same code over and over, and you either need a bunch of similar looking animationDidStop implementations, or a single implementation that branches based on the animation identifier. For example:

Old Way


- (void) doMoveFromView1ToView2Animation
{
    [app beginIgnoringInteractionEvents];

    [UIView beginAnimations: @"HideView1Animation" context: foo];
    [UIView setAnimationDuration: kDuration];
    [UIView setAnimationDelegate: self];
    [UIView setAnimationDidStopSelector:
        @selector(hideView1AnimationDidStop:finished:context:)];

    [self hideView1Code];

    [UIView commitAnimations];
}

- (void)hideView1AnimationDidStop:(NSString*)animationID
                         finished:(NSNumber*)finished
                          context:(void*)context
{
    [UIView beginAnimations: @"SwapView1AndView2Animation" context: foo];
    [UIView setAnimationDuration: kDuration];
    [UIView setAnimationDelegate: self];
    [UIView setAnimationDidStopSelector:
        @selector(swapView1AndView2AnimationDidStop:finished:context:)];

    [self swapView1AndView2Code];

    [UIView commitAnimations];
}

- (void)swapView1AndView2AnimationDidStop:(NSString*)animationID
                                 finished:(NSNumber*)finished
                                  context:(void*)context
{
    [UIView beginAnimations: @"ShowView2Animation" context: foo];
    [UIView setAnimationDuration: kDuration];
    [UIView setAnimationDelegate: self];
    [UIView setAnimationDidStopSelector:
        @selector(showView2AnimationDidStop:finished:context:)];

    [self showView2Code];

    [UIView commitAnimations];
}

- (void)showView2AnimationDidStop:(NSString*)animationID
                         finished:(NSNumber*)finished
                          context:(void*)context
{
    [app endIgnoringInteractionEvents];
   
    [self doStuff];
}

When we embarked on WORD SPIN it was time to build something to help tackle this problem. I settled on the concept of sequences, where each individual animation is a step in the overall animation sequence. For the user example of moving from one page to the next in Postage, the new code looks like this:

New Way


- (void) doMoveFromView1ToView2Animation
{
    NSMutableArray* steps = [NSMutableArray array];

    [steps addObject: [RSViewAnimationSequenceStep stepWithTarget: self
                                                         selector: @selector(hideView1Code)
                                                         duration: kDuration]];
    [steps addObject: [RSViewAnimationSequenceStep stepWithTarget: self
                                                         selector: @selector(swapView1AndView2Code)
                                                         duration: kDuration]];
    [steps addObject: [RSViewAnimationSequenceStep stepWithTarget: self
                                                         selector: @selector(showView2Code)
                                                         duration: kDuration]];
   
    [[RSSequenceManager sharedManager] invokeSequence: steps];
}

What I really like about this approach is that the entire animation sequence is specified in the same location and it’s clear how many independent steps there are up front, rather than jumping from one place in the code to the next to see what the whole animation will do. Granted, you could almost achieve the same visual effect by refactoring all the “beginAnimation” code into a reusable piece. In fact, that’s how I started.

The fundamental piece of this paradigm is the sequence step. RSViewAnimationSequenceStep inherits from RSSequenceStep, a base class to define the invocation and delegation of the step. RSViewAnimationSequenceStep is primarily a refactoring of the beginAnimation and commitAnimation boiler plate (I’ve removed the definition of the helper, init, and dealloc selectors for brevity):

RSViewAnimationSequenceStep


@interface RSViewAnimationSequenceStep : RSSequenceStep
{
    id      target_;
    SEL     selector_;
    float   duration_;
}

+ (id) stepWithTarget: (id) target selector: (SEL) selector duration: (float) duration;

- (void) invoke;

@end

@implementation RSViewAnimationSequenceStep

// helper, init, and dealloc removed for brevity

- (void) invoke
{
    [UIView beginAnimations: @"RSViewAnimationSequenceStep" context: nil];
    [UIView setAnimationDuration: duration_];
    [UIView setAnimationDelegate: self];
    [UIView setAnimationDidStopSelector:
        @selector(animationDidStop:finished:context:)];

    [target_ performSelector: selector_];

    [UIView commitAnimations];
}

- (void) animationDidStop:(NSString *)animationID
                 finished:(NSNumber *)finished
                  context:(void *)context
{
    // delegate is defined in the base class RSSequenceStep (see below)
    [delegate_ stepCompleted: self];
}

@end

The RSSequenceStep is a simple base class to define the general interface:

RSSequenceStep


@interface RSSequenceStep : NSObject
{
    id  delegate_;
}

@property (nonatomic, assign)   id  delegate;      
    // The object that implements stepCompleted.
       
- (void) invoke;
    // Called to invoke the code necessary for the step.
   
@end

@interface NSObject (RSSequenceStepDelegate)

- (void) stepCompleted: (RSSequenceStep*) step;

@end

RSViewAnimationSequenceStep encapsulates handling the beginning and ending of an animation, and then calls back to a delegate when complete. It is a self contained item that can be used independently. If so desired, we could stop here and have client code declare the RSViewAnimationSequenceStep, set the delegate to self, and achieve a large part of the refactoring. But the steps would still be spread apart in the code. That’s where the RSSequenceManager, and it’s implementation detail class RSSequence, comes into play, as those two handle chaining the steps together to encapsulate the entire animation.

RSSequenceManager


@implementation RSSequenceManager

// things removed for brevity

- (void) invokeSequence: (NSArray*) steps
{
    RSSequence* sequence = [RSSequence sequenceWithSteps: steps delegate: self];

    [sequences_ addObject: sequence];
   
    if ( [sequences_ count] == 1 )
    {
        [sequence invoke];
    }
}

- (void) sequenceCompleted: (RSSequence*) sequence
{
    [sequences_ removeObject: sequence];
   
    if ( [sequences_ count] > 0 )
    {
        [[sequences_ objectAtIndex: 0] invoke];
    }
}
@end

The bulk of the work, however, happens inside the RSSequence class:

RSSequence


@interface RSSequence : NSObject
{
    int         currentStepIndex_;
    NSArray*    steps_;
    id          delegate_;
}

- (id) initWithSteps: (NSArray*) steps delegate: (id) delegate;

- (void) invoke;
    // Called to initiate the sequence.
   
@end

@interface NSObject (RSSequenceDelegate)

- (void) sequenceCompleted: (RSSequence*) sequence;

@end


@implementation RSSequence

- (id) initWithSteps: (NSArray*) steps delegate: (id) delegate;
{
    self = [super init];

    if ( self )
    {
        steps_ = [steps retain];
       
        for ( RSSequenceStep* step in steps_ )
        {
            step.delegate = self;
        }
    }

    return self;
}

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

- (void) invoke
{
    [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
   
    currentStepIndex_ = 0;
   
    [self invokeNextStep];
}

- (void) invokeNextStep
{
    if ( currentStepIndex_ < [steps_ count] )
    {
        RSSequenceStep* step = [steps_ objectAtIndex: currentStepIndex_++];
       
        [step invoke];
    }
    else
    {
        [[UIApplication sharedApplication] endIgnoringInteractionEvents];
       
        [delegate_ sequenceCompleted: self];
    }
}

- (void) stepCompleted: (RSSequenceStep*) step
{
    [self invokeNextStep];
}

@end

The sequence manager doesn’t know or care that a sequence is an animation, and a sequence also doesn’t know or care what an individual sequence step does. Those classes only assume that a sequence step’s ending can, but doesn’t have to, come later than its beginning. Given that, we’ve also implemented other sequence step subclasses: one that is a simple selector call, one that encapsulates multiple simultaneous steps, and one that handles openGL animations. I’ve pared the code down a fair bit for this posting, and left out detail — for example, our view animation step handles delays, custom timing functions, and transitions. But the basic idea is all there.

Lastly, Joe Conway over at the Big Nerd Ranch posted a blog about this problem, and described a solution using a subclass of CALayer that calls completion targets for a given animation key. I like his approach because it theoretically handles all the different kinds of animations, and I’ll be thinking about how to work his idea into our sequences.

1 Comment »

New Apps – Word Spin Lite and Holiday ~ Postage

December 8th, 2009 by Chris

We have two new iPhone applications in the store this month that you might want to check out:

Holiday ~ Postage

First, Holiday ~ Postage is a special holiday themed edition of our virtual Postcard application – Postage. It has 10 great new postcard designs and the whole application interface is themed for the season. You’ll love the new snow globe design if you haven’t seen it yet. You can get it on the App Store now for just $1.99

HolidayPostage.png

Word Spin Lite

We have also released Word Spin Lite, a free edition of our highly addictive word game created in partnership with Geospace International

Word Spin Lite has the same engine as the full version of Word Spin and allows you to play challenges that created by your friends or practice the game. You can upgrade Word Spin Lite to the full version right inside the application. After you upgrade you’ll be able to create your own challenges, as well as play the full Solitaire game and compete for a spot on the global leader board.

Its free to try, so go ahead and give it a shot! Here are a couple of challenge codes you can try out with Word Spin Lite :

4 Wheels, 4 Rounds : UNQKQI

8 Wheels, 1 Round : KRRXDX

Click the links above if you are on an iPhone with Word Spin installed, otherwise you can just type in the six letter code into the Challenge screen.

WordSpin.png

2 Comments »