Downloading tiles for CATiledLayer with NSURLConnection - iphone

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.

Related

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

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.

Continuously drawing into an iPhone bitmap object? What am I missing?

I am pretty sure this is a straightforward problem I must be confused about (I am a total newbie on the iPhone): I am trying to continuously draw shapes using the CG APIs into a bitmap object, and to always show an up-to-date version of that bitmap on the screen, inside a UIScrollView object so the user can pan, zoom, etc.
Using UIView with drawRect is no good because this is an ongoing thing -- I have a separate thread where all the drawing commands are issued, and I need them to be applied and accumulated on that same framebuffer (kind of like a web page rendering engine). What I keep seeing is that all GCContext APIs related to images seem to CREATE a brand new image whenever you want it rendered to the screen. That sounds bad because it forces the creation of another copy of the image.
I guess I am looking for a way to create an off-screen bitmap, render to it as much as I want using Core Graphics, and whenever I want to, blt that image to the screen, but still retaining the ability to keep drawing to it and blt it to the screen again, later on.
Thanks in advance!
You could use a CGLayer. Create it once with CGLayerCreateWithContext() and later retrieve the layer's context with CGLayerGetContext() whenever you need to draw into it.
To draw the layer into another graphics context, call CGContextDrawLayerAtPoint() or CGContextDrawLayerInRect().

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.

Fastest iPhone Blit Routine?

I have a UIView subclass onto which I need to blit a UIImage. There are several ways to skin this cat depending on which series of APIs you prefer to use, and I'm interested in the fastest. Would it be UIImage's drawAtPoint or drawRect? Or perhaps the C-based CoreGraphics routines, or something else? I have no qualms about altering my source image data format if it'll make the blitting that much faster.
To describe my situation my app has anywhere from ~10 to ~200 small UIViews (64x64), a subset of which will need to be redrawn based on user interaction. My current implementation is a call to drawAtPoint inside my UIView subclass' drawRect routine. If you can think of a better way to handle this kind of scenario, I'm all ears (well, eyes).
Using an OpenGL view may be fastest of all. Keep an age cache of images (or if you know a better way to determine when certain images can be removed from the cache, by all means use that) and preload as many images as you can while the app is idle. It should be very quick, with almost no Objective-C calls involved (just -draw)
While not a "blit" at all, given the requirements of the problem (many small images with various state changes) I was able to keep the different states to redraw in their own separate UIImageView instances, and just showed/hid the appropriate instance given the state change.
Since CALayer is lightweight and fast I would get a try.
Thierry
The fastest blit implementation you are going to find is in my AVAnimator library, it contains an ARM asm implementation of a blit for a CoreGraphics buffer, have a look at the source. The way you could make use of it would be to create a single graphics context, the size of the whole screen, and then blit your specific image changes into this single graphics context, then create a UIImage from that and set it as the image of a UIImageView. That would involve 1 GPU upload per refresh, so it will not depend on how many images you render into the buffer. But, you will likely not need to go that low level. You should first try making each 64x64 image into a CALayer and then update each layer with the contents of an image that is the exact size of the layer 64x64. The only tricky thing is that you will want to decompress each of your original images if they come from PNG or JPEG files. You do that by creating another pixel buffer and rendering the original image into the new pixel buffer, that way all the PNG or JPEG decompression is done before you start setting CALayer contents.

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.