Help needed with drawRect:
- by Andrew Coad
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?