How do I know when a CATiledLayer has rendered all visible tiles? - iphone

I am working on an application where I render PDF content in a CATiledLayer. I want to trigger one method after the rendering of the tiled layer is complete.
Is there any delegate method that will be called immediately after the rendering of all visible tiles is completed? Is there any other way of knowing when this is finished?

You can calculate the number of tiles your drawing requires before it's drawn. In drawRect of the tilingview, each tile is drawn only ONCE. So put a counter in part of the draw rect that calls a new tile. When your counter reaches the total number, call your method.
Keep in mind that drawrect for tiling is done on a background thread.

This requires some creative thinking. I've had a similar problem where I've needed to abort the rendering of a tiled layer mid-cycle. The way I worked round it is somewhat complex, but seems to work reasonably well. It involves wrapping the draw calls to the tiled layer inside a NSThread. Threads have a isFinished bool that you can key-value observe to discover when a tiled layer has completed its render.
If you're not comfortable with threading on iOS this may be more trouble than its worth, but will give you the advantage of knowing when the rendering has finished, and also being able to cancel the thread operation (and thus the render) if required.

Related

Continuous drawing with Quartz? frame by frame possible?

I've been working on custom drawings using drawRect in UIView subclasses. That's cool, but you have to wait until the end of the run loop for drawRect to be called and I'm wondering how you can control frame by frame animations where you change the drawings over time, or if this is possible? Perhaps Quartz isn't really designed for this type of animated graphics? I gather that perhaps it is designed for static drawings that don't change so frequently.
Quartz by itself its not able to sustain a high frame rate, due to its need to redraw everything each time. But you can have Quartz work together with CoreAnimation to have Quartz based animations. The idea behind this is that you can cache previously drawn content inside CALayer objects and then use CoreAnimation to create the continuous drawing effect.
A good example of this technique can be see in the AccelerometerGraph sample code provided by Apple. Inside this sample the UIView subclass that uses this technique is the "GraphView" object. Basically this object draws as completely new only a portion of the graph (the newly generated segments), backs it in a dedicated layer and then animates the layers in order to provide the "scrolling graph" animation.
Clearly this technique works only when you have full control of the drawing elements and can manage this incremental way of adding objects in the screen. Of course things become much more complicated when you must redraw many different parts of the screen and you need to modify previously generated layers.
Anyway have a look at the mentioned code: it is quite interesting.
Your app should exit to the run loop before each frame. Do all your custom frame animation setup between each frame. So frame-by-frame drawing in drawRect should work just fine. This can work in iOS apps at a 60 Hz frame update rate, not just for static views, as long as all your methods between frame times, as well as your drawRects, are fast enough. Chop them up if needed.

Downloading tiles for CATiledLayer with NSURLConnection

I'm looking into the Apple ScrollViewSuite and the Photoscroller, and I wonder how to implement a CATiledLayer when downloading the tiles through an NSURLConnection:
how do I notify drawRect: that a specific tile has been downloaded and how do I keep track of the rects and contexts associated with each tile?
Regards Fredrik
When you finish downloading, cache the results, and then call setNeedsDisplayInRect: with the tile's rect. It will call drawLayer:inContext: again, and you can then draw the cached results.
I thing there are just no way for doing this because it is supposed to work to other way around. Tiles are rendered in separated thread and thus you could start downloading the tile from the drawLayer:InContext: method. Of course don't forget to implement caching for the downloaded tiles otherwise you will kill both your app and your invoice :-)
There's no way to get the necessary information out of the tiled layer. I'm currently simply invalidating/redrawing the layer once every few seconds to get some kind of "eventual consistency". That is, at some point in time I expect all visible tiles to have been loaded and cached (by my own controller). The redraw will then simply render all tiles using images from my cache.
For that to work, you need to be able to calculate the set of visible tiles and (re)download them if they're not cached. This approach has the huge benefit of allowing me to cancel connections for tiles that are no longer visible. And it allows the map to, eventually, recover from connection/server errors etcetera.
The tiled layer would need to export a lot of internals if you wanted to get away from this 'polling' approach, like which tiles it has cached and which it is currently interested in.

Improving drawing performance on custom UIView

I have a custom UIView which is composed of many images, their positions are changing in response to the user touch.
The view must track the user touch and i'm experiencing a performance bottleneck in the drawing of such view, preventing me to follow the input in realtime.
At the beginning i was drawing everything in the [UIView drawRect:] method and of course it was way too slow because everything was redrawn even if not necessary.
Then, i used more CALayers to update only the layer that was changing and this gave me much better responsiveness.
But still, when i have to draw the same image many times on a layer it takes up to 500ms.
Since the images are placed at fixed positions it there a way to pre-draw them? Should i consider putting them in many CALayers and just hide/show them?
Also, i don't understand why a [CALayer setNeedsDisplayInRect:] exists but the delegate has (apparently) no way to know what the invalid rect is to optimize the drawing.
Solution
Following the advice in the answer I finally created many CALayers for the images and set the contents property the first time the layer was being shown. This is a lazy-loading compromise: in a first attempt i set the contents of every layer at the creation time but this caused to pre-draw any possible image on the program launch, freezing the application for seconds.
From the documentation for -[CALayer drawInContext:]:
Default implementation does nothing. The context may be clipped to protect valid layer content. Subclasses that wish to find the actual region to draw can call CGContextGetClipBoundingBox. Called by the display method when the contents property is being updated.
The default implementation of display calls drawInContext: on an automatically-created context; presumably setting the bounding box as well (which is presumably passed to drawRect:).
If you're drawing several static images, I'd just stick each one in its own UIView; I don't think the overhead is that big (if it is, the CALayer overhead should be smaller). If they all animate, I'd definitely use UIView/CALayer. If some of them don't animate (much) and you notice significant slowness, you can pre-render those. It's a trade-off between rendering in drawRect: (or similar) and layer compositing on the GPU, but in general I'd assume that the latter is much faster.

Drawing/Rendering a UIView Within a Separate Thread so NSTimer Always Fires Timely

I'm using a NSTimer to fire a drawRect within the app's main view. The drawRect draws a few images, and blends each using kCGBlendModeScreen (which is an absolute must). However, the drawRect takes just a tad longer than desired, so the timer doesn't always get fired at the desired rate (more like half as often).
I've optimized the graphics used as much as I feel is possible, so I'm wondering if it's possible to "outsource" the drawing by creating a new view, and calling that view's drawRect from within a thread created inside of the timer's callback method. (In other words, thread the call to a new view's drawRect, such as [someNewView setNeedsDisplay] ...)
If so, how might I approach something like that, in code?
...
I'd use Core Animation, but I remember reading that it didn't support alpha blend modes. If I'm wrong, I'd be open to seeing some example code that allows animation of all the images in separate transformations (e.g. individual rotations for each image), while still keeping them able to blend using kCGBlendModeScreen that I'm currently implementing.
Thanks for any tips!
The answer is "no." You should never, ever do drawing(or anything with UIKit) from a secondary thread. If you're experiencing performance issues, you should perform all of your computations on another thread ahead of drawing so that drawing takes a minimal amount of time.

How to detect when a CATiledLayer has finished drawing all tiles

I need to detect when a CATiledLayer has finished drawing. I tried subclassing and overriding -(void)display to set/clear a flag, but it seems that tile drawing is happening in a different thread (display just returns and then seconds later, the layer is finished drawing)
Some things are not clear from your question. Are you asking whether all tiles have been drawn or just whether the visible tiles have finished drawing?
Assuming the later you could try drawInContext: but will likely still not give you the answer if there is scrolling. Because the tiles are cached and we have no way to know when the cached tiles are dumped you would not be able to tell if a tile has not been drawn yet or if it was just drawn from the cache.
You might want to describe what you are trying to accomplish and see if people have ideas for another way to do it.