Developing an iPad PDF-Reader we decided to prepare high-res images of rendering intensive pages (lots of paths in them) and use those instead of the pdf pages to avoid performance issues. We decided that 3*768 by 3*1024 is a good compromise between readability and rendering performance which results in ~1.5 MB jpegs.
However we tested two implementations for displaying the image pages. One that uses a CATiledLayer subclass which is also responsible for handling the “normal” PDF pages (drawing with CGContextDrawImage) and another which uses UIImageView. The latter has the advantage that displaying and zooming is very quick, but memory usages is really bad – it takes about 30 MB in memory (which is consistent with the image’s bitmap size). The other approach (CATiledLayer) needs more time to first display the page and needs another two seconds to re-render after zooming (similar to pdf pages, but much faster) but doesn’t grab more memory than it needs to display a much smaller image or a PDF page.
Does anyone know what’s going on behind the scenes and if it’s possible to combine low memory usage of CGContextDrawImage with high performance of UIImageView by using the Quartz Framework.
Not sure if this question is still relevant, but if so, maybe this will help:
I’ve fought the battle of efficient display of large views in my tiled image view as well. There are a bunch of underlying parts to the problem:
UIView, includingUIImageView, seems to always be completely backed by memory for its image. Even if you implement thedrawRect:method, it always seems to pass the entire bounds of the view, not only the area visible within a scroll view. As you’ve discovered, this quickly takes up a lot of memory as each pixel takes 4 bytes.CATiledLayerdoes only request the contents for visible tiles. It also discards tiles that are no longer visible – this is where the memory savings come from. However, it does the notification and the drawing on a background thread, while animating from white to the contents. It seems to do this via a private API, I haven’t found a way to re-implementCATiledLayer‘s functionality as my own subclass ofCALayer, as there simply seems to be no notification mechanism that we can use as mere mortals.drawRect:messages appropriately as they are paged in. UIKit seems to struggle with too many subviews below a view, though.For you, I can see a couple of possible options:
CATiledLayer-based implementation. ThefadeDurationdefaults to 0.25 seconds, which may be too long if your load times are short. You can drop this right down to something like1.0/60.0, i.e. one frame. One thing that’s not clear from your description is whether your images cover the whole page size or just each 256×256 pixel tile. Decoding the whole JPEG over and over again for each tile will be much slower than decoding individual tile files.CATiledLayerthread is too high, you can manually create a bunch of tiles asUIImageViewsubviews to a blankUIViewwhich is the main subview to the scroll view. Each of these subviews is assigned its own tile image. If you’re lucky, UIKit will be smart enough to drop the contents of these views and re-load the corresponding JPEGs on demand.drawRect:. If it’s still too jerky, try with your own CALayers, which will be sublayers of your single view’s blank layer and again load their image on demand. This is what I eventually went with for my tiled image view.That should cover scrolling, but it could still be really slow if you allow the user to zoom right out (minification). In that case I recommend you store appropriate lower-resolution versions and load/show those at those outermost zoom levels.