how to speed-up drawRect? - iphone

I'm coding a map app for iPhone, like Google Maps but with my own maps.
What I have so far is working, but sadly, slower than I would like.
I'm basically drawing the correct map tiles in each drawRect:
[image drawAtPoint:CGPointMake(x, y)];
X and Y are being calculated in each call to drawRect
( these calcs are not very expensive ).
The maximum number of images being drawn at a time is 4.
Each image is 512 x 512.
I feel that some kind of optimization is lacking here.
Can anyone help with something?
Thanks you all.

Why not create UIImageView objects for each tile?
INstead of drawing each image in drawRect, just move the UIIMageView to the correct position in layoutSubviews
The UIImageView will handle drawing when it needs to and will cache it's drawn rect. You're re-rendering each image every time drawRect is called.
The trade-off will probably be more memory consumption as you're making UIImageView objects but you can find a scheme to release ones that aren't being displayed if you need to.

Look for Apple's developer notes on using a tiled scrollview. It may automatically do what you want.

Related

UIScrollView pinch zoom into CGContextStrokePath without changing line width

I have a UIScrollView that contains a custom UIView. In my UIView I am overriding drawRect to draw a path using CGContextStrokePath. I would like to slightly alter the way the zoom works. Pinch zooming out will show more of the paths on the screen. This is what I want but i want the line width to stay the same not shrink as you zoom out so that they are still clear to the user.
I thought I would just do this (myUIView zoom target is called _lineView)
-(void)scrollViewDidZoom:(UIScrollView *)pScrollView
{
_lineView.zoomScale = _scrollView.zoomScale;
[_lineView setNeedsDisplay];
}
and then just calculate an appropriate line stroke size in my _lineView drawRect method to give the effect of constant line width as you zoom out.
This is really slow and I have read that this is expected as drawrect is not optimised to be called many times a second.
I then started looking at using a GLKView instead and just rendering the whole thing in opengl directly. The problem with this is I will have to implement all of the zooming and panning myself (with all the lovely zoom and pan bounce effects you get for free in UiScrollView). I will also have to implement all the controls I want to use in opengl, buttons etc.
Is there a way to do this whilst still using Quartz2d? I feel like opengl will give me lots of power but it will take me much longer to get the rest of my app done if I go down that route.
I figured this out. I found a simple way to do this that does not slow down the zoom / bouncing animations at all and is very fast and fluid.
Quartz has a class called CAShapeLayer that lets you do some pretty cool stuff. Among these is being able to set a CGPath property and specify a linewidth. Changes are reflected in the view.
So i basically call shapeLayer.lineWidth from my scrollViewDidZoom method and it does exactly what i need.
What I would do if I were you is to use default zooming behavior while zooming (which does not redraw, but instead just applies a transform to the zoomed view, which can be done by the GPU very, very quickly, but as you have noticed can lead to inferior quality).
Then, when the user finished zooming, redraw the whole view as you do now. The appropriate delegate method is scrollViewDidEndZooming:withView:atScale:.
This way, you have fast (but slightly ugly) zooming, and nice (but slightly, probably unnoticeably, slower) display after the zoom is finished.

iPhone Quartz Image Drawing Performance.

Currently I have a horizontal UIScrollView populated with UIViews. The views are dequeued when moving off screen and reused. Each UIView is subclassed to draw images using drawInRect with [someView setNeedsDisplay] being called when the view enters the screen.
[_image drawInRect:imageRect];
My gradients, text, shapes, etc all load smoothly but as soon as I draw an image I suffer a noticeable performance hit. The size of the image doesn't seem to matter because the scroll view always lags. (I get the same result when using a UIImageView as well.) All of my images are loaded beforehand too.
Is there a better way to draw images that won't result in poor performance?
If you're just drawing images, you might consider using CALayers with CGImageRef contents—drawRect: overrides are going to be slower than CALayer compositing. Another thing to watch out for is alpha-blended images, which require frequent recompositing, or images that aren't aligned to integral pixels. The Improving Image Drawing Performance on iOS tech note is a great place to start on this kind of thing. Once you have the basics in hand, the Core Animation instrument in Instruments will be of great help to you—it watches out for blending, copied images, misaligned images, and screen updates.

how to draw an UIImageView into -drawRect of another view?

I have several UIImageView objects which have been rotated and scaled. Because they are just background images, and represent an non-highlighted-state, I want to draw them to the canvas of the view so that I can get rid of them in memory. The view has a big bitmap anyways, so it would save a lot of memory to put them in there rather than adding as subview.
It seems I can only call something like -drawInRect for an UIImage, but how about an UIImageView with transforms on it? Oh yes, and it's positioned with frame origin.
I just want to draw it to another UIView's bitmap the same way as it appears when adding as subview.
Using drawInRect: with a rect of different origin and size of the image, you can effectively make any scaling and translation you want. You can also handle rotations with the [CGContextRotateCTM][1] function (CTM = current transformation matrix). There are other CGContext..CTM functions as well, so you have multiple approaches.

CATiledLayer blanking tiles before drawing contents

All,
I'm having trouble getting behavior that I want from CATiledLayer. Is there a way that I can trigger the tiles to redraw without having the side-effect that their areas are cleared to white first? I've already subclassed CATiledLayer to set fadeDuration to return 0.
To be more specific, here are the details of what I'm seeing and what I'm trying to achieve:
I have a UIScrollView with a big content size...~12000x800. Its content view is a UIView backed by a CATiledLayer.
The UIView is rendered with a lot of custom-drawn lines
Everything works fine, but the contents of the UIView sometimes change. When that happens, I'd like to redraw the tiles as seamlessly as possible. When I use setNeedsDisplay on the view, the tiles redraw but they are first cleared to white and there's a fraction-of-a-second delay before the new content is drawn. I've already subclassed CATiledLayer so that fadeDuration is set to 0.
The behavior that I want seems like it should be possible...when you zoom in on the scrollview and the content gets redrawn at a higher resolution, there's no blanking before the redraw; the new content is drawn right on top of the old one. That's what I'm looking for.
Thanks; I appreciate your ideas.
Update:
Just to follow up - I realized that the tiles weren't being cleared to white before the redraw, they're being taken out entirely; the white that I was seeing is the color of the view that's beneath my CATiledLayer-backed view.
As a quick hack/fix, I put a UIImageView beneath the UIScrollView, and before triggering a redraw of the CATiledLayer-backed view I render its visible section into the UIImageView and let it show. This smooths out the redraw significantly.
If anyone has a better solution, like keeping the redraw-targeted tiles from going away before being redrawn in the first place, I'd still love to hear it.
I've found that if you set levelsOfDetailBias and levelsOfDetail both to the same value (2 in my case), then it only redraws the tiles that are touched by my setNeedsDisplayInRect: call, as you'd hope.
However if the levelsOfDetail is different to LODB, then any calls to setNeedsDisplayInRect: redraw all the tiles.
You could add another layer (possibly a CATiledLayer) behind the existing tiled layer. (Sort of a double-buffered solution.) You would call setNeedsDisplay: on the second layer from a timer that fires after a few seconds to ensure that that layer doesn't redraw at the same time as the front layer.
Another potential option is to use the same delegate to draw content to a bitmap context and swap the bitmap into the backing store once the content is refreshed. This should produce a flicker-free result. That being said, I can't tell you how this might be done, and one nice thing about CATiledLayers is they automatically generate tiles when you zoom and pregenerate tiles when you pan once zoomed in.
I would like to see how you implement your application. I have been looking for weeks to find an example that uses a combination of UIScrollView and a CATiledLayer-back view with a lot of custom drawn lines. Apple has some great sample code - but it all involves images rather than line art, so no help for me.
Having read through these answers without a solution, I discovered that tiling a page was the dominant background task.
Preparing my lo-res placeholder image on a high priority queue solved this issue - the images now appear while the tiling is occurring. Caching the placeholder images further improves their appearance - they appear before the tiling begins.
With newer devices, the tiling it so fast, these tricks might not matter. A sample PDF consisting of large scanned images (e.g. a scanned book) tiles the slowest in my experience, and makes for good test data.
I had the same problem with iPad.
The solution was more simple than I thought and far more simple than using UIImageView to render display before redrawing... :
Just don't set any background color for the Layer!
I had CATiledLayer set in a similar way:
layer = [[CATiledLayer alloc] init];
layer.masksToBounds = YES;
layer.contentsGravity = kCAGravityLeft;
//layer.backgroundColor = [[UIColor whiteColor] CGColor];
layer.tileSize = CGSizeMake(1004.0, 1004.0);
layer.levelsOfDetail = 16;
layer.levelsOfDetailBias = 8;
Note that I have commented out the line setting layer's background color to white.
After that the white blank before redraw problem disappeared!
Let me know if anyone has tried that.

Most efficient way to subclass UIView to create an animated UIImageView

I need to play frame based animations. I tried the UIImageView with animationImages but it doesn't give me any control over my animation. I can't pause it, I can't mask it, I can't know the current frame etc.
So I subclassed UIView and created something I called AnimationView which takes the array of images and does the animation using an NSTimer. In the drawRect: I can then do all the masking and everything else I want.
However this method is way too slow. I can't run an animation at 30fps, maybe not even at 10fps and I am testing on a 3GS. (I am only doing masking and blending on every frame :) - and using the same images plays fine at 30fps on a UIImageView).
So, what is the most efficient way to achieve this? Is the NSTimer a bad choice? Is there a better way around it?
Thanks :)
In this answer to this question Mo DeJong provides a link to a source code implementation of a class that manually animates PNG image frames. He claims to get 15 FPS for 480x320 images animating on a non-3GS iPhone.
The NSTimer is fine. The problem is that you're touching a lot of pixels using the CPU in every frame using Quartz (the drawRect:). What you want to do is either use OpenGL if you have to compose images using a mask or cache your images in CALayers or CGImages.