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?