Pergunta

When I initialize the map, it shows both my location and the destination (forward geocoded from a string) but it does not draw directions between them.

Here is my code :

#import "EventDetailMapViewController.h"

@interface EventDetailMapViewController ()
@property (nonatomic,strong) MKMapItem *destination;
@end

@implementation EventDetailMapViewController

CLPlacemark *thePlacemark;
MKRoute *routeDetails;


- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    _mapView.showsUserLocation = YES;
    self.navigationController.toolbarHidden = NO;

    _mapView.delegate = self;
    [self getRoute];

}


- (void)addAnnotation:(CLPlacemark *)placemark {
    MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
    point.coordinate = CLLocationCoordinate2DMake(placemark.location.coordinate.latitude, placemark.location.coordinate.longitude);
    point.title = [placemark.addressDictionary objectForKey:@"Street"];
    point.subtitle = [placemark.addressDictionary objectForKey:@"City"];
    [self.mapView addAnnotation:point];
}



-(void)showRoute:(MKDirectionsResponse *)response{

    for (MKRoute *route in response.routes)
    {
        [_mapView
         addOverlay:route.polyline level:MKOverlayLevelAboveRoads];

        for (MKRouteStep *step in route.steps){
            NSLog(@"%@",step.instructions);
        }



    }

}

-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
    // If it's the user location, just return nil.
    if ([annotation isKindOfClass:[MKUserLocation class]])
        return nil;
    // Handle any custom annotations.
    if ([annotation isKindOfClass:[MKPointAnnotation class]]) {
        // Try to dequeue an existing pin view first.
        MKPinAnnotationView *pinView = (MKPinAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:@"CustomPinAnnotationView"];
        if (!pinView)
        {
            // If an existing pin view was not available, create one.
            pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"CustomPinAnnotationView"];
            pinView.canShowCallout = YES;
        } else {
            pinView.annotation = annotation;
        }
        return pinView;
    }
    return nil;
}
-(void)getRoute {
    [self getLocationFromString];

    MKDirectionsRequest *directionsRequest = [[MKDirectionsRequest alloc] init];
    MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:thePlacemark];
    [directionsRequest setSource:[MKMapItem mapItemForCurrentLocation]];
    [directionsRequest setDestination:[[MKMapItem alloc] initWithPlacemark:placemark]];
    directionsRequest.transportType = MKDirectionsTransportTypeAutomobile;
    MKDirections *directions = [[MKDirections alloc] initWithRequest:directionsRequest];
    [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
        if (error) {
            NSLog(@"Error %@", error.description);
        } else {
            routeDetails = response.routes.lastObject;
            [self.mapView addOverlay:routeDetails.polyline];

        }
    }];
}
-(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    MKPolylineRenderer  * routeLineRenderer = [[MKPolylineRenderer alloc] initWithPolyline:routeDetails.polyline];
    routeLineRenderer.strokeColor = [UIColor redColor];
    routeLineRenderer.lineWidth = 5;
    return routeLineRenderer;
}


- (IBAction)changeMapType:(id)sender {
    if (_mapView.mapType == MKMapTypeStandard)
        _mapView.mapType = MKMapTypeSatellite;
    else
        _mapView.mapType = MKMapTypeStandard;
}


-(void)getLocationFromString{

    CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    [geocoder geocodeAddressString:self.agendaEntry.address completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error) {
            NSLog(@"%@", error);
        } else {
            thePlacemark = [placemarks lastObject];
            float spanX = 1.00725;
            float spanY = 1.00725;
            MKCoordinateRegion region;
            region.center.latitude = thePlacemark.location.coordinate.latitude;
            region.center.longitude = thePlacemark.location.coordinate.longitude;
            region.span = MKCoordinateSpanMake(spanX, spanY);
            [self.mapView setRegion:region animated:YES];
            [self addAnnotation:thePlacemark];
        }
    }];
}

@end

I'm relatively new to this so some of this is probably wrong. What I want is when I push a button (in another view) the "agendaEntry" gets passed on the segue. It contains a string with an address, this address gets forward-geocoded and the map displays directions/routes from the user's location to the address in that string.

I don't know how to make it show driving/walking directions or to make it show multiple routes. But for starters, it would be great if it would work.

Foi útil?

Solução

The reason it doesn't draw directions is because the directions request is being made before the destination placemark (thePlacemark) is actually set by the geocodeAddressString completion handler block.

Note what the documentation says about geocodeAddressString:completionHandler::

This method submits the specified location data to the geocoding server asynchronously and returns.

So even though getLocationFromString (which calls geocodeAddressString) is called before the directions request, execution returns and continues in getRoute immediately after the geocodeAddressString is initiated (but not completed).

Therefore, thePlacemark is still nil when getRoute sets up directionsRequest and you're probably getting an error logged such as "directions not available".

To account for this, you can move the call to the directions-request-set-up code to inside the geocodeAddressString completion handler block (after the add-annotation code).

There are three things to change for this:

  1. In viewDidLoad, do [self getLocationFromString]; instead of [self getRoute];.
  2. In getRoute, remove the call to getLocationFromString.
  3. In getLocationFromString, in the completion handler block, after [self addAnnotation:thePlacemark];, do [self getRoute];.


Regarding showing multiple routes, first you'll need to actually request alternate routes (default is NO):

directionsRequest.requestsAlternateRoutes = YES;

The other part of the problem you may have experienced is due to this line in rendererForOverlay:

MKPolylineRenderer  * routeLineRenderer = [[MKPolylineRenderer alloc] 
    initWithPolyline:routeDetails.polyline];

It's creating a polyline renderer using an external instance variable instead of the overlay parameter provided to the delegate method. This basically means the delegate method will only work for that one specific overlay (routeDetails.polyline) and since there's no control over when exactly the delegate method will be called by the map view, you can't reliably set routeDetails.polyline from outside the delegate method to be sure it points to the right overlay. Each alternate route is a separate polyline and the map view will make separate calls to rendererForOverlay for each one.

Instead, create the polyline renderer using the overlay parameter (and first check if overlay is an MKPolyline):

-(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    if ([overlay isKindOfClass:[MKPolyline class]])
    {
        MKPolylineRenderer  * routeLineRenderer = 
            [[MKPolylineRenderer alloc] initWithPolyline:overlay];
        routeLineRenderer.strokeColor = [UIColor redColor];
        routeLineRenderer.lineWidth = 5;
        return routeLineRenderer;
    }

    return nil;
}

Then in getRoute, instead of this:

routeDetails = response.routes.lastObject;
[self.mapView addOverlay:routeDetails.polyline];

call the showRoute method you already have which adds all the routes:

[self showRoute:response];
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top