It’s yet again time to write an article in English. This time about some iPhone development dilemma I stumbled upon lately working for my bachelor thesis. As you may know the iPhone Licence (or whatever) doesn’t allow developers to use Google Maps (called MapKit in the SDK) for – as they call it – “Turn by Turn”-navigation applications. I actually don’t want to do this but I’d still like to draw a route into the map. Sadly Apple obviously believed that the only reason one would want to do this would be to provide such a thing. Therefore they didn’t include an abstraction to easyly accomplish that. Yet I still wanted it and this is how I got it to work (with transitions and animations!).
The first thing that came to my mind was to put a non-opaque UIView above the MKMapView and to draw my route into that view. I would simply use convertCoordinate:toPointToView: to get the right points from the map mapped to my overlaying view. At first thought a good idea but it suffers from two serious problems. First (and worse) is the event tracking. You might think it would be easy to intercept events and implement the 4 methods (touchesBegan:… and so on) to move your route and then propagate the events to the underlying mapview but it isn’t. I really tried a lot things and googled up some stuff that sent messages directly to some subview of the MKMapView (which is a hack, of course) but the best thing i could come up with, was moving with your fingers down working but at the same time breaking the “pinch” for zooming which rendered this method pretty useless. Another problem was that you could scroll by sliding your finger on the screen fast and then let go which will let the map scroll further but no more events are fired as no finger is on screen. You could in a way combine this with handling the map-delegate methods didChangeRegion and willChangeRegion but it still won’t work the way you want it. Especially because willChangeRegion does fire somewhat unexpectedly and not constantly while scrolling or zooming in. Second thing thats wrong with this approach is that you will draw the route above the whole map layer which is unusable if you want to use the annotations of the map, too. To sum it up. This approach is crap. I really spend a lot of time trying stuff (even with timers redrawing the route…) but you can’t get it to work the way you want it.
After a little bit of more googleing I found this site of Craig who actually proposed the some approach but had posted an update where he suggested to draw the route into a custom annotation-view to solve the problem with the covered annotations. I liked that idea and tried some stuff on my own and finally this is how you get it done:
Create your own MKAnnotationView subclass which follows the MKAnnotation Protocol, too. Create your own UIView subclass on which you will draw the route later on. Your MKAnnotation subclass creates on initilization a new instance of your UIView subclass and pushes the route-points (e.g. array of CLLocationCoordinate2D) to this internal view. It also needs to set itself to not clipsToBounds, also set it’s bg-color to clearColor and opaque to NO. You also need to push a reference to the MKMapView to the internal view. The last thing to do is to add this internal view as a new subview to your custom MKAnnotation. Btw. you annotation can have a size of 0×0 as we don’t clip our subview and this is the one the route will be drawn upon.
Then you’ll implement the MKAnnotation method coordinate which will return [yourMapViewReference centerCoordinate]. This way – if added to the map – your custom annotation will always be positioned at the center of the map. Regardless of scrolling or zooming. This is important as the MKMapView will remove you route if the annotation is not currently visible (positioned) on the visible region of the map. Then create some method that will order the internal subview to redraw itself and reposition it to the currently visible rect of the map. Basicly it will look like this:
-(void) initRedraw { CGPoint origin = CGPointMake(0, 0); origin = [mapView convertPointrigin toView:self]; internalView.frame = CGRectMake(origin.x, origin.y, mapView.frame.size.width, mapView.frame.size.height); [internalView setNeedsDisplay]; }
Then you want to fire up that method in regionDidChangeAnimated in your MKMapViewDelegate. But this won’t do the trick entirely because it will only redraw the map when the region did change but we want to redraw constantly while we’re zooming and scrolling. Maybe you will also note that your internal view isn’t positioned correctly. This is because MapKit will first fire regionDidChangeAnimated then your redrawing will take place then MapKit will ask the your custom annotation for it’s corrdinate and will finally reposition it which you won’t note because there is no event or delegate method for that and this will result in your view beeing repositioned as well as your route. The trick is now to overwrite setCenter: in your custom MKAnnotation. As following the calls above your view will be repositioned using setCenter:. You can overwrite it and abuse it as event listener for any motion (zooming, pinching, scrolling) in the map by firing your initRedraw method in that overwritten thing (remember to call the super-method). If your drawRect in your internal subview is efficiently written and you don’t have too much route points this will allow great performance on 3GS hardware and acceptable performance on first-gen hardware. I didn’t optimize the whole thing and it works great for me with ~ 150 Points (even more on 3GS).
Actually I’m not sure about if you would really need the interal subview to draw upon but I took this idea from Craig’s site. You could also try to draw on your MKAnnotationView directly. I hope some of you can use this but remember not to break the rules Apple made on using MapKit. Also I’d like to thank Craig for his idea with the custom annotation.
Finally, I think this is the best approach to implement routes or polylines into MapKit. I’m currently too busy to put up an example project but if you read Craig’s posts and mine as well you should get it running. If someone is willing to create some abstraction for the MKAnnotation or maybe even a full MapViewController I will happily link it here. Comments are welcome. Good luck with your own projects.
Ich weiß gar nicht so recht was ich darüber schreiben soll. Ich kann nur sagen, dass ich die
Aussteller. Mit unserer Master-Projektgruppe “