random random - 1 month ago 9
iOS Question

Adding multiple overlays to MKMapView

I am having some trouble understanding how to add multiple overlays to MKMapView.

I have an array routes tracked. The routes tracked are arrays of CLLocations. So I have an array of arrays.

I am able to take one route and draw it on the map view. However, I need to draw ALL the routes onto the map view.

So here is how I go about creating the MKPolyline for 1 route:

-(void) loadRoute
{
//So we grab an array that holds all the locations of one route.
NSArray *locations = [[NSArray alloc] initWithArray:[routesArray objectAtIndex:0]];

// while we create the route points, we will also be calculating the bounding box of our route
// so we can easily zoom in on it.
MKMapPoint northEastPoint;
MKMapPoint southWestPoint;

// create a c array of points.
MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) *[locations count]);

for(int idx = 0; idx < [locations count]; idx++)
{
CLLocation *tempLoc = (CLLocation*)[locations objectAtIndex:idx];

CLLocationDegrees latitude = tempLoc.coordinate.latitude;
CLLocationDegrees longitude = tempLoc.coordinate.longitude;

// create our coordinate and add it to the correct spot in the array
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);

MKMapPoint point = MKMapPointForCoordinate(coordinate);

// if it is the first point, just use them, since we have nothing to compare to yet.
if (idx == 0) {
northEastPoint = point;
southWestPoint = point;
}
else
{
if (point.x > northEastPoint.x)
northEastPoint.x = point.x;
if(point.y > northEastPoint.y)
northEastPoint.y = point.y;
if (point.x < southWestPoint.x)
southWestPoint.x = point.x;
if (point.y < southWestPoint.y)
southWestPoint.y = point.y;
}
pointArr[idx] = point;
}


self.routeLine = [MKPolyline polylineWithPoints:pointArr count:[locations count]];

//Zoom in to fit route on screen
_routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);

// clear the memory allocated earlier for the points
free(pointArr);

}


Here is the MapKit delegate call that returns the overlay:

#pragma mark MKMapViewDelegate

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
MKOverlayView* overlayView = nil;

if(overlay == self.routeLine)
{
//if we have not yet created an overlay view for this overlay, create it now.
if(nil == self.routeLineView)
{
self.routeLineView = [[MKPolylineView alloc] initWithPolyline:self.routeLine];
self.routeLineView.fillColor = [UIColor redColor];
self.routeLineView.strokeColor = [UIColor redColor];
self.routeLineView.lineWidth = 3;
}
overlayView = self.routeLineView;
}

return overlayView;
}


So all the code given above works fine. I can't figure out how to load more overlays to show the other routes.

I thought that maybe in loadRoute method, running through all the routes and creating a MKOverlayView for each one. Storing all those MKOverlayView in an array then doing:
[self.mapView addOverlays:array];

But it doesn't work. The problem is that in the delegate method I somehow need to know which overlay to return.

So if I could do something along the lines of:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
int tag = overlay.tag;
return (MKOverlayView*)[arrayOfViews objectAtIndex:tag];
}


it would work fine. But unfortunately it doesn't work like that :(

Any help would be very much appreciated!

EDIT:

I use this in ViewDidLoad to add the overlay:

[self loadRoute];

// add the overlay to the map
if (nil != self.routeLine) {
[self.mapView addOverlay:self.routeLine];
}

// zoom in on the route.
[self zoomInOnRoute];


This is in the header:

// the data representing the route points.
MKPolyline* _routeLine;

// the view we create for the line on the map
MKPolylineView* _routeLineView;

// the rect that bounds the loaded points
MKMapRect _routeRect;

Answer

The routeLine and routeLineView variables can only point to one overlay and overlay view at a time so you would theoretically need an array of such variables.

However, based on the code shown, you don't need to keep references to these in the first place.

I suggest removing the routeLine and routeLineView variables.

Then you just create an overlay object locally and in viewForOverlay return a view for the requested overlay parameter.

In the loadRoute method, you can loop through the routesArray and for each route, create a local overlay object, and call addOverlay on it.

There's also an easier way to construct a map rect that bounds all the routes so you can set the map's region to show them all.

Example:

-(void) loadRoute
{
    _routeRect = MKMapRectNull;

    for (int raIndex = 0; raIndex < routesArray.count; raIndex++) 
    {
        //So we grab an array that holds all the locations of one route. 
        NSArray *locations = [[NSArray alloc] initWithArray:
                                 [routesArray objectAtIndex:raIndex]];
        //note we are getting object at raIndex (not 0) above

        //...no change to existing code here...

        //self.routeLine = [MKPolyline polylineWithPoints:pointArr count:[locations count]];
        //replace above line with this...
        MKPolyline *pl = [MKPolyline polylineWithPoints:pointArr count:[locations count]];
        [mapView addOverlay:pl];

        //Zoom in to fit route on screen
        //_routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);
        //replace above line with this to create a rect around ALL routes...
        if (MKMapRectIsNull(_routeRect))
            _routeRect = pl.boundingMapRect;
        else
            _routeRect = MKMapRectUnion(_routeRect, pl.boundingMapRect);

        // clear the memory allocated earlier for the points
        free(pointArr);
    }    
}

In viewDidLoad, just call loadRoute and don't call addOverlay.


The viewForOverlay method becomes much simpler:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
    MKPolylineView *pv = [[MKPolylineView alloc] initWithPolyline:overlay];
    pv.fillColor = [UIColor redColor];
    pv.strokeColor = [UIColor redColor];
    pv.lineWidth = 3;
    return pv;
}


By the way, in loadRoute, this line:

MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) *[locations count]);

should be:

MKMapPoint* pointArr = malloc(sizeof(MKMapPoint) *[locations count]);

It should use the size of MKMapPoint (not CLLocationCoordinate2D).
They are not the same thing (even though the structs happen to be the same byte size).