Sunday, 6 September 2009

MKMapView overlay

This short blog post will be for iPhone OS v.3.0 programmers who use MapKit framework for displaying maps in their iPhone applications.

I faced a problem with MapKit which other programmers on the internet also seem to have, but I couldn't find a solution anywhere. But let's start from the beginning:

Why I have encountered the problem

I wanted to display map scales on top of MKMapView, so I have created MapScales subclass of UIView. My MapScales class has a reference to MKMapView and whenever it is displayed (drawRect is called), it checks the distances on the currently shown rectangle on MKMapView and draws nice scales. I made my MapScales instance semi-transparent and put it on top of MKMapView in my hierarchy of UIViews. Next I wanted my MapScales to be dispayed only when at least two user's fingers are on the map - so that the scales are shown only when the user pretends to zoom in or out. So I had to intercept the touch events in my MapScales. And here is:

The problem

MKMapView doesn't work correctly when it is not the first interceptor of touch events.

What happens:

  • Some of the standard animations in MKMapView are not working any more.
  • MKMapView is still usable, but it doesn't react to standard user interactions in a normal way any more.

When exactly it happens:

  • When some other component (like my MapScales) intercepts the event first, and then sends it to MKMapView.
  • It doesn't matter of the other component (MapScales) is a subview of MKMapView or a subview of MKMapView's superview.
  • It doesn't matter if the other component is sending events directly to MKMapView's instance or to the object obtained by calling hitTest:withEvent: on MKMapView's instance.
  • I also tried to fix the problem by making my MapScales a subview of MKMapView, but I couldn't get a satisfactory solution.

I don't know what causes this problem, but I suspect that MKMapView's behaviour may depend on UITouch'es read-only view field, which will be different whenever MKMapView is not the first event interceptor.

Well this problem is so ugly, that it invalidates the benefits of creating MapScales in my application.

My solution

It is not very clean solution, but it let's you create the MKMapView overlay that intercepts the events and doesn't spoil MKMapView's behaviour.

Before the events are distributed to UIView hierarchy, they first get to UIWindow, so I have created a UIWindow subclass (MyMainWindow) and made the main window of my application an instance of MyMainWindow. Then inside MyMainWindow I have implemented the method:

- (void)sendEvent:(UIEvent*)event {

[super sendEvent:event];

[listeners sendEvent:event];


As you can see I have also implemented a simple mechanism of observing MyMainWindow (listeners attribute). Then I have registered my MapScales instance as MyMainWindow's listener.

Of course I also had to disable user interaction flag in my MapScales instance, as now it is getting events from different source.

This solution has some disadvantages. For example, MapScales always gets all touch events, even when the user touches something else than the map - this is the price of skipping standard event delivery mechanism. Though it works very well for me.

If you find a better solution, please let me know.