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.
Related
I have a custom UITableViewCell modelled exactly from the custom UITableViewCell tutorials found here -
http://developer.apple.com/library/ios/#samplecode/AdvancedTableViewCells/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009111
and here -
http://developer.apple.com/library/ios/#samplecode/TableViewSuite/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007318
Everything is rendered using drawRect and the scrolling speed is 55 FPS when drawing 3 strings and 1 image. The image is drawn via -
[self.sampleImage drawInRect:CGRectMake(0, 0, 55, 55)];
Oddly enough, the scrolling speed reduces to 25 FPS when I render the same image but in a bigger area
[self.sampleImage drawInRect:CGRectMake(0, 0, 320, 80)];
I experimented with bigger images in drawRect in Apple's sample code and sure enough the frame rate drops to 25 FPS.
The catch is that the FPS goes back up to 55 when I draw the strings and display the image via a UIImageVew.
How can large images be drawn using drawRect in a UITableViewCell while maintaining 50 FPS scrolling speed?
Can UIImageView ever be faster than drawing an image in drawRect (for a UITableViewCell) ?
Are there any GPU vs CPU forces at play?
The drawRect will use core graphics to draw the image onto the screen where it uses the CPU. if you use UIImageView it will use GPU hence it is faster.
Solution is to not use drawRect.
If you absolutely need to use drawRect then you may need to draw it before scroll to some cache location/ cache the drawing and use that image for UIImageView.
#darcwader has it pretty much spot on. I think the reason why all the examples use drawRect is because it's more expensive initially (to set up the cells) but afterwards its much cheaper to animate them. For large images however it's going to be significantly more costly to draw them up and if you're scrolling past multiple ones the benefits of using drawRect aren't realized.
I make extensive use of -drawRect: to do some nice animations. A timer tries to fire 30 times per second with -setNeedsDisplay, but it feels like just 20 times. Also I can't use -setNeedsDisplayInRect: because the animation covers the entire thing.
Would it help to take some of those drawing operations out of -drawRect: and move them to a subview? -drawRect has to do less then, but instead the OS will have more work with compositing views.
Is there a rule of thumb which one is more worse? I remember from an apple text that they claimed Core Animation doesn't redraw during animation. So is that their secret of speed? Using subviews in animations?
Much of this will be similar to my answer to your other question:
Compositing is faster, by far. On the iPhone, content is drawn into a CALayer (or into a UIView's backing CALayer) using Quartz drawing calls or from a bitmapped image. This layer is then rasterized and effectively cached as an OpenGL texture on the GPU.
This drawing and caching operation is very expensive, but once the layer is on the GPU, it can be moved around, scaled, rotated, etc. in a hardware-accelerated manner. All the GPU has to do while the layer is animating is to composite it with the other onscreen layers every frame. This is why Core Animation can do 50 layers animating around the screen at 60 FPS on even the oldest iPhone models.
Layers and views only redraw themselves when prompted, or if resized when their needsDisplayOnBoundsChange property is set to YES. The drawing system is set up this way because of how expensive it is to redraw and recache the layer contents. If you can, avoid redrawing your layer content regularly, but instead split it into layers or views that you can animate around individually using Core Animation. If you need to animate a changing shape, look to CAShapeLayer, which makes this much more efficient than simply redrawing every frame.
As a worst case, yes, you can take the portions of your view that are static and move them to one view, then have a subview which has only the changing portion of your drawing within it. Performance won't be great, but it will be much better than if you had to redraw everything within the view. Compositing overhead will be negligible compared to the expense of drawing.
I created a minesweeper clone game in iphone. My implementaion of the cells in the grid is this, I created a UIView and added buttons in the UIView and then I added the UIView in a UIScrollView, but every time I zoom out or zoom in using zoomToRect method of UIScrollView the zooming is not smooth and the zoom out was distorted. How can implement smooth zooming in UIScrollView?
I would try rendering your content view into a bitmap image when scrolling or zooming begins, and replacing the large grid of buttons with the bitmap until the scrolling/zooming is completed. The UIScrollViewDelegate protocol should provide you with the necessary information to know when to swap the bitmap in or out. Part of the problem is that your content view is so computationally intensive to render (all those buttons).
A more sophisticated approach would be to re-implement your game grid at a lower level using coreanimation and more fundamental touch event handling, but that might be overkill if the bitmap hack works well enough.
You know how in the maps app, while panning or zooming, there are grey tiles that show up? That means that the iPhone is currently downloading the tiles. In Safari, there is a similar effect where Safari lays down checkered grey instead of webpage, as it is currently rendering it and will be just a moment. Both of these mean that scrolling will not wait for content to load before displaying the area, it will let scrolling be smooth.
You could try looking here [http://stackoverflow.com/questions/1098234/optimized-image-loading-in-a-uiscrollview] for some ideas, and a point in the right direction would be to use threading to load views in the background while displaying grey in its place.
I have a UIView with a large number of CALayers (~1000), each of which has a small image as its contents. This UIView is a subview of a scrollview (actually it's a subview of another view which is a subview of the scrollview). This draws relatively quickly at first (couple seconds). However when I scroll in the scrollview the frame rate is very low.
Is it trying to redraw the contents of each CALayer every time I scroll? Is there a way to disable this? Is something else going on?
Note: I set clearsContextBeforeDrawing to NOo n my UIView and this helped somewhat but it's still much slow than I would expect
The layers should not be redrawn, but they will be composited using the GPU. Try using the Sampler and OpenGL ES instruments in Instruments to see if your bottleneck is in the CPU or GPU. You can log extra detail within the OpenGL ES instrument by tapping on the "i" next to the instrument name and selecting the extra parameters (Tiler Utilization, Renderer Utilization, etc.). If one of the OpenGL values is near 100%, then it's the compositing that's holding you back. However, if the sampler is showing a number of calls to -drawRect: or similar methods, then your layers are being redrawn (for some reason) and that could be the problem.
Additionally, you can use the Core Animation instrument to color non-opaque layers, which can lower your display framerate.
Set the needsDisplayOnBoundsChange property on your layers to NO:
[layer setNeedsDisplayOnBoundsChange:NO];
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.