4
votes

I've been working on something for weeks now (iOS) and I can't seem to figure out an acceptable solution.

Requirements

  • Display a 10000x10000 pixel background image
  • Zooming and scrolling responsive
  • Overlay on top that has custom drawing
  • Elements in overlay are interactive (tapping and highlighting elements, etc) and pieces can be turned on or off.
  • iOS 4.0

Think of it as a geographic map of a fictional town, with a crazy system of roads, lines, areas and buildings drawn on top. A mixture of CGPaths with fills of various opacities, some icon images etc. It's like a more complicated version of google maps app.

What i've tried:


1 - Multiple CATiled Layers

View Hierarchy:

  • ScrollView
  • ->ContainerView
  • -->TiledView (CATiledLayer 10000x10000)
  • --->OverlayView (CATiledLayer 10000x10000)

Results: Drops tiles from magic apple cache left and right. Two CATiledlayers doesn't seem to be the right path. Updates to overlay aren't fast.


2 - Subclass TiledView for OverlayView

View Hierarchy:

  • ScrollView
  • ->OverlayView (Subclass of TiledView CATiledLayer 10000x10000)

Results: Takes way too long to render updates for the overlay. Slow to update tiles


3 - Scrollview w/ a single container view

View Hierarchy:

  • ScrollView
  • ->ContainerView (10000x10000)
  • -->OverlayView (CALayer 10000x10000)
  • -->TiledView (CATiledLayer 10000x10000)

Results: Fails to work because OverlayView consumes too much memory. TileView is fine because it is backed by CATiledLayer


4 - OverlayView that uses scroll delegate and CTM scale/translate to simulate being large

View Hierarchy:

  • OverlayView (CALayer 1024x768)
  • ScrollView
  • ->TiledView (CATiledLayer 10000x10000)

Results: I use the scrollview delegates to adjust the offset and zoom of the overlay view. The problem with this method is that drawrect is called like 100 times a second and the overlay view can't draw fast enough so it's completely lagged down to 1fps.


So that's where I am at. I feel this last method is on to something, but would require some crazy work to tame the drawrect madness. Other thoughts would be to try to tackle something in OpenGL.

Before I go about doing those I'd thought I'd ask the community to see what they would do/have done facing similar requirements.

Thanks for any help.

3

3 Answers

2
votes

My suggestions is to have two views in your scrollview. The background CATiledLayer view. The second view is a custom UIView that sits above the background view - this is where you do your custom drawing. The custom view should have a drawRect: method that for now does nothing.

So you try this with nothing in your customView, and make sure scrolling etc is as you want it. If the problem is in the background view and you need to resolve that.

At that point, your custom view starts drawing stuff. Note that you will get a rect to update in drawRect. Perhaps you have a thousand items that might need drawing over the whole canvas - so each has a frame property, so you can determine given current zoom and frame what needs to be drawn. You only draw those items.

This general technique has worked well in the past for me.

EDIT2:

So running on a device, I duplicated the "cannot allocate" messages. So plan B. What you can do is have a container view, which has two views - the scroll view and a new UIView subclass. The scrollView has the huge monster view. The subclass view has a frame that is exactly the UIScrollView frame.

You would then make sure that you get the scrollView messages "scrollViewDidScroll:", and probably others.

When the scrollView is stable, or you et the above message, then the the custom UIView subclass gets a message that says draw but using an offset which is the scrollView contentOffset.

The work the custom class has to do is the same. Instead of using its drawRect origin point, it takes that point and offsets it by the contentOffset of the scrollView. Now, when you draw, you are looking for objects that are in scope of the point passed in the drawRect:rect offset positively by the scrollview offset.

1
votes

I drawed a path-based map once, but i did not have a background image, only paths. I used only one View with drawrect (no Scrollview) and implemented the scrolling/zooming by myself. There where some performance issues, especially on ipad3 with highresolution as it has problems with allocating a full resolution imagecontext.

  • do not use shadows etc. on a Path, this will kill your performance.
  • precalculate all bounding-boxes of your Paths/objects and store them in some order so you can lookup the visible paths/objects as fast as possible.
  • use different-detailed pathsets for different zoomlevels.

but at the end i think openGL is the better solution.

0
votes

Old question, but for everybody struggling with this as well. You should use two Tiled Views which are zoomed/scrolled in the same way, but the overlay view should NOT be a subview of the background view. That is, you should have:

  • ScrollView
  • ->ContainerView
  • -->TiledView (CATiledLayer 10000x10000)
  • -->OverlayView (CATiledLayer 10000x10000)

Then for drawing speed, it really comes down to how you do the drawing for the overlay. For each tile, you should make sure that you only draw that which overlays the tile, not all the other stuff that falls outside the boundary of the tile.