views:

445

answers:

1

Hi, I'm having a fundamental issue with use of drawRect: Any advice would be greatly appreciated.

The application needs to draw a variety of .png images at different times, sometimes with animation, sometimes without.

A design goal that I was hoping to adhere to is to have the code inside drawRect: very simple and "dumb" - i.e. just do drawing and no other application logic.

To draw the image I am using the drawAtPoint: method of UIImage. Since this method does not take a CGContext as a parameter, it can only be called within the drawRect: method. So I have:

- (void)drawRect:(CGRect)rect {

 [firstImage drawAtPoint:CGPointMake(firstOffsetX, firstOffsetY)];

}

All fine and dandy for one image. To draw multiple images (over time) the approach I have taken is to maintain an array of dictionaries with each dictionary containing an image, the point location to draw at and a flag to enable/suppress drawing for that image. I add dictionaries to the array over time and trigger drawing via the setNeedsDisplay: method of UIView. Use of an array of dictionaries allows me to completely reconstruct the entire display at any time. drawRect: now becomes:

- (void)drawRect:(CGRect)rect {


 for (NSMutableDictionary *imageDict in [self imageDisplayList]) {
  if ([[imageDict objectForKey:@"needsDisplay"] boolValue]) {
   [[imageDict objectForKey:@"image"] drawAtPoint:[[imageDict objectForKey:@"location"] CGPointValue]];
   [imageDict setValue:[NSNumber numberWithBool:NO] forKey:@"needsDisplay"];
  }
 }
}

Still OK. The code is simple and compact. Animating this is where I run into problems. The first problem is where do I put the animation code? Do I put it in UIView or UIViewController? If in UIView, do I put it in drawRect: or elsewhere? Because the actual animation depends on the overall state of the application, I would need nested switch statements which, if put in drawRect:, would look something like this:

- (void)drawRect:(CGRect)rect {

 for (NSMutableDictionary *imageDict in [self imageDisplayList]) {
  if ([[imageDict objectForKey:@"needsDisplay"] boolValue]) {

   switch ([self currentState]) {
    case STATE_1:
     switch ([[imageDict objectForKey:@"animationID"] intValue]) {
      case ANIMATE_FADE_IN:
       [self setAlpha:0.0];
       [UIView beginAnimations:[[imageDict objectForKey:@"animationID"] intValue] context:nil];
       [UIView setAnimationDelegate:self];
       [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
       [UIView setAnimationDuration:2];
       [self setAlpha:1.0];
       break;
      case ANIMATE_FADE_OUT:
       [self setAlpha:1.0];
       [UIView beginAnimations:[[imageDict objectForKey:@"animationID"] intValue] context:nil];
       [UIView setAnimationDelegate:self];
       [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
       [UIView setAnimationDuration:2];
       [self setAlpha:0.0];
       break;
      case ANIMATE_OTHER:
       // similar code here
       break;
      default:
       break;
     }
     break;
    case STATE_2:
     // similar code here
     break;
    default:
     break;
   }
   [[imageDict objectForKey:@"image"] drawAtPoint:[[imageDict objectForKey:@"location"] CGPointValue]];
   [imageDict setValue:[NSNumber numberWithBool:NO] forKey:@"needsDisplay"];
  }
 }

 [UIView commitAnimations];

}

In addition, to make multiple sequential animations work correctly, there would need to be an outer controlling mechanism involving the animation delegate animationDidStop: callback that would set the needsDisplay entries in the dictionaries to allow/suppress drawing (and animation).

The point that we are at now is that it all starts to look very ugly. More specifically:

  • drawRect: starts to bloat quickly and contain code that is not "just drawing" code
  • the UIView needs implicit awareness of the application state
  • the overall process of drawing is now spread across three methods at a minimum

And on to the point of this post: how can I do this better? What would the experts out there recommend in terms of overall structure? How can I keep application state information out of the view? Am I looking at this problem from the wrong direction. Is there some completely different approach that I should consider?

+1  A: 

I'd start off by not re-inventing Core Animation

Bill Dudney's is probably the best: Core Animation for Mac OS X and the iPhone: Creating Compelling Dynamic User Interfaces

geowar