views:

3752

answers:

3

My problem is this: I have a simple block image that I want to make a grid out of. I have created an array of CGPoints in my UIView. Then I used

 blackImage = [UIImage imageNamed:@"black.png"];

and then

- (void)drawRect:(CGRect)rect {
     for (int j = 0 ; j <= 11; j++)
    {
     for (int i = 0 ; i <= 7; i++) 
     {
      [blackImage drawAtPoint: (CGPointFromString([[self.points objectAtIndex:j] objectAtIndex:i]))];
     }

    }
    [whiteImage drawAtPoint:(CGPointMake((CGFloat)(floor((touchX+0.001)/40)*40), (CGFloat)(floor((touchY+0.001)/40))*40))];

    // [whiteImage drawAtPoint:(CGPointMake(240.0, 320.0))]; 
}

Where TouchX and TouchY are the CGPoints where the user has touched the screen. So I'm basically displaying a different image at the point on the grid where the user touched.

First of all, the problem is that the whole screen is redrawn every time I call DrawRect. I want to be able to save the state (by changing an array of BOOL's?) and have the user drag on the screen and change multiple images. However, I'm unable to use the "drawAtPoint" method on the UImage when I'm outside of DrawRect. (throws an error).

Anyhow, I started looking into the CoreAnimation and Quartz docs, and got really confused. I'm not sure if I should be creating multiple UIViews for each brick (seems excessive), or a grid of CALayers or CGLayers... I think it's pretty simple what I want to do... but I don't really understand Layers, and the difference between using a CALayer and a CGLayer. Apparently "All UIViews are layer backed" What does that mean?

I'm kind of lost on how to implement this. I basically want to swap images in the grid so that the user can essentially 'paint' across the screen and toggle the images.

+++++++++ Edit +++++++++

I've tried implementing this using [self setNeedsDisplayinRect], and passing in the CGRect that is under the user's finger. However, this is still calling DrawRect within TouchesMoved, which IS giving me the effect I want, but is way too slow on the iPhone. Is there a more efficient way to accomplish the same effect using CALayers? I'm new to Core Animation and don't really understand how to use a layer to solve this problem.

+1  A: 

To invalidate only a portion of your view, call setNeedsDisplayInRect: with just the area that has changed. If multiple rects have changed, you can call it twice with different rects.

The other method of tackling this is to create multiple CALayers and move/hide them.

Also, you should never call drawRect: directly, use setNeedsDisplay instead. drawRect: will be called by UIKit whenever an area of a view is invalid and needs painting.

rpetrich
+2  A: 

I think in your case, UIImage should handle this app and give good performance. The issue looks to be that you are forcing the whole screen to redraw in response to every drawRect: message. The rect that is passed in specifies the 'dirty area' that you need to redraw. Any other drawing is just wasting time.

Similarly the rect you pass to your view's setNeedsDisplay: is the rect you specifically want to invalidate. Calculate a rect around the touch and invaliate that. UIKit will (potentially) union mutiple dirty rects and send a single drawRect:.

I would suggest you perform the following optimisations:

  1. check the dirty rect passed to your drawRect: (in the debugger) and if it is less than the full screen write code to make sure you only repaint images within the dirty rect
  2. Use the touches event to make sure you only invalidate the areas that need redrawing and then revisit 1.
  3. If this doesn't get the performance you need, use Quartz to draw the black and white rects rather than using images incase it is some UIImage weirdness (but I doubt this is the case)

If you need to display bitmaps and 1 & 2 don't get the performance you need, you will have to resort to lower level technology. There are lots of examples around including this question on SO.

Roger Nolan
+1  A: 

77 discrete images is not too burdensome to do as a grid of CALayers or UIViews (assuming your images are only a few pixels wide), so I'd recommend that approach. UIViews are not much heavier than CALayers, so what I'd do is build a grid of discrete UIImageViews (assuming your black image is more than just a black color, if not you can just set a standard UIView's backgroundColor to black or white). If you didn't need to track touches as they move across the views, you could even do the touch handling for toggling the view image within a custom subclass of UIImageView or through delegate methods in your controller.

This way, the only portion of the display that re-renders will be specific UIViews that are changing. I recommend UIViews over CALayers simply because UIViews are a little easier to place within the view hierarchy.

For a clear explanation of how CALayers, UIViews, and the like interact on the iPhone, I'd recommend reading the "The View Rendering Architecture" subsection within the "View Architecture and Geometry" section of the "iPhone Application Programming Guide".

Brad Larson