Apple MapKit – Loading Maps, Finding User Location, Searching for a Location, Adding Annotation, Interacting with Annotations, Drawing Lines in Maps, Adding Circle, Showing Route between two locations.

By | July 25, 2016

In this demo we will be doing the following things using Apple MapKit and Google Maps API.

  1. Load Maps
  2. Add Annotation
  3. Search for a Location
  4. Decode an Address
  5. Find route between locations

For starting we will add a MapView to the Storyboard and hook it to a variable named mapView.
You can simply run the project and see the Maps.

Make sure you add “App Transport Security Settings” in the info.plist as shown in the below image

Also you need to add “NSLocationWhenInUseUsageDescription” in the info.plist with your description.

Apple Maps info plist

You can simulate location in the Simulator using the Following way.

1. Click on the Location Icon in the Console Window.
2. Go to Xcode – > Debug – > Simulate Location.

Apple MapKit iOS

Now we will create a class LocationManager.

LocationManager.h


#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>


@protocol LocationCallBack;

@interface LocationManager : NSObject <CLLocationManagerDelegate>
{
    CLLocationManager *locationManager;
    id<LocationCallBack> callback;
    CLGeocoder *geocoder;
    CLPlacemark *placemark;
}

@property (strong, nonatomic) id<LocationCallBack> callback;

+ (instancetype)sharedInstance;

-(void) setDelegate : (id <LocationCallBack>) callBack;

@end



// Delegate

@protocol LocationCallBack<NSObject>

@optional

-(void) onLocationFound : (CLLocationCoordinate2D) location;
-(void) onLocationError :(NSString * ) errString;

@end

LocationManager.m


#import "LocationManager.h"

@implementation LocationManager

@synthesize callback;

- (id) init
{
    self = [super init];
    
    if (self != nil)
    {
        [self locationManager];
    }
    return self;
}

-(void) setDelegate : (id <LocationCallBack>) callBack{
    
    self.callback = callBack;
    
}

+ (instancetype)sharedInstance
{
    static LocationManager *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[LocationManager alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

- (void) locationManager
{
    if ([CLLocationManager locationServicesEnabled])
    {
        locationManager = [[CLLocationManager alloc] init];
        geocoder = [[CLGeocoder alloc] init];
        
        locationManager.delegate = self;
        locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        locationManager.distanceFilter = kCLDistanceFilterNone;
        if ([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)])
        {
            [locationManager requestWhenInUseAuthorization];
        }
        [locationManager startUpdatingLocation];
    }
    else{
        
        UIAlertView *servicesDisabledAlert = [[UIAlertView alloc] initWithTitle:@"Location Services Disabled" message:@"You currently have all location services for this device disabled. If you proceed, you will be showing past informations. To enable, Settings->Location->location services->on" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:@"Continue",nil];
        [servicesDisabledAlert show];
        [servicesDisabledAlert setDelegate:self];
    }
}

- (void)requestWhenInUseAuthorization
{
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
    
    // If the status is denied or only granted for when in use, display an alert
    if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusDenied) {
        NSString *title;
        title = (status == kCLAuthorizationStatusDenied) ? @"Location services are off" : @"Background location is not enabled";
        NSString *message = @"To use background location you must turn on 'Always' in the Location Services Settings";
        
        UIAlertView *alertViews = [[UIAlertView alloc] initWithTitle:title
                                                             message:message
                                                            delegate:self
                                                   cancelButtonTitle:@"Cancel"
                                                   otherButtonTitles:@"Settings", nil];
        [alertViews show];
    }
    // The user has not enabled any location services. Request background authorization.
    else if (status == kCLAuthorizationStatusNotDetermined) {
        [locationManager requestWhenInUseAuthorization];
    }
}


#pragma mark - CLLocationManagerDelegate

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    NSLog(@"didFailWithError: %@", error);
    [self.callback onLocationError : error.description];
}

-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
    switch (status) {
        case kCLAuthorizationStatusNotDetermined:
        case kCLAuthorizationStatusRestricted:
        case kCLAuthorizationStatusDenied:
        {
            // do some error handling
        }
            break;
        default:{
            [locationManager startUpdatingLocation];
        }
            break;
    }
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
    
    CLLocation *location =  [manager location];
    CLLocationCoordinate2D coordinate = [location coordinate];
    
    [self.callback onLocationFound:coordinate];
    
    //[locationManager stopUpdatingLocation];
    
    [self findAddress:location];
    
}

/* Find address of the given location */
-(void) findAddress :(CLLocation *) location
{
    // Reverse Geocoding
    NSLog(@"Finding Address...");
    [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
        //NSLog(@"Found placemarks: %@, error: %@", placemarks, error);
        if (error == nil && [placemarks count] > 0) {
            placemark = [placemarks lastObject];
            NSString *place = [NSString stringWithFormat:@"%@, %@, %@",
                               placemark.locality,
                               placemark.administrativeArea,
                               placemark.country];
            NSLog(@"Place %@", place);
        } else {
            NSLog(@"%@", error.debugDescription);
        }
    } ];
}


@end

ViewController implements the LocationManager to get the user location, searching for a location,
decoding an address and showing route between two location points.

Now the ViewController.h


#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import "LocationManager.h"
#import <MapKit/MapKit.h>

@interface ViewController : UIViewController<CLLocationManagerDelegate, LocationCallBack, MKMapViewDelegate>
{
    
    LocationManager *locationManager;
    __weak IBOutlet MKMapView *mapView;
    MKMapCamera *camera;
    MKCircle *circle;
    int locationCount;
    CLLocationCoordinate2D coordinateArray[2];
}

@property (nonatomic, retain) MKPolyline *routeLine; //your line
@property (nonatomic, retain) MKPolylineView *routeLineView; //overlay view

@end

ViewController.m



#import "ViewController.h"


@interface ViewController ()

@end


@implementation ViewController

@synthesize routeLine;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.title = @"Maps";
    
    // Create an instance of the LocationManager Class
    locationManager = [LocationManager sharedInstance];
    [locationManager setDelegate:self];
    
    // Show user Location on Map
    [mapView setShowsUserLocation: YES];
    [mapView setUserTrackingMode: MKUserTrackingModeFollow animated: YES];
    
    // Add the Delegate to Listen to the Map Delagate Methods
    [mapView setDelegate: self];
    
    locationCount = 0;
    
    // Add Long Tap recognizer on the MapView
    UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];
    [mapView addGestureRecognizer:longPressGesture];
    
    // Finding Search Location
    CLLocationCoordinate2D searchLocation = [self geoCodeUsingAddress:@"Los Angeles"];
    [self addAnnotation:@"Los Angeles" :@"Los Angeles" inLocation:searchLocation];
    
    // Find route between two points
    [self findRoutes];
    
}

/* Gesture Recognizer function */
-(void)handleLongPressGesture:(UIGestureRecognizer*)sender {
    
    
    // This is important if you only want to receive one tap and hold event
    if (sender.state == UIGestureRecognizerStateEnded)
    {
        //[mapView removeGestureRecognizer:sender];
    }
    else
    {
        // Here we get the CGPoint for the touch and convert it to latitude and longitude coordinates to display on the map
        CGPoint point = [sender locationInView:mapView];
        CLLocationCoordinate2D locCoord = [mapView convertPoint:point toCoordinateFromView:mapView];
        [self addAnnotation:@"Location" :@"Location Subtitle" inLocation:locCoord];
        
        if(locationCount == 0){
            coordinateArray[0] = CLLocationCoordinate2DMake(locCoord.latitude, locCoord.longitude);
            locationCount = 1;
        }else{
            coordinateArray[1] = CLLocationCoordinate2DMake(locCoord.latitude, locCoord.longitude);
            locationCount = 0;
            [self drawRoute];
        }
        
    }
}

/* Draw the route */
-(void) drawRoute{
    self.routeLine = [MKPolyline polylineWithCoordinates:coordinateArray count:2];
    [mapView setVisibleMapRect:[self.routeLine boundingMapRect]];
    [mapView addOverlay:self.routeLine];
}

/* Adds the annotation to the Map */
-(void) addAnnotation : (NSString *) title : (NSString *) subtitle inLocation :(CLLocationCoordinate2D )coord
{
    MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
    point.coordinate = coord;
    point.title = title;
    point.subtitle = subtitle;
    [mapView addAnnotation:point];
    
    [self positionCamera:coord];
}

/* Position the camera after updating the maps */
-(void) positionCamera : (CLLocationCoordinate2D )coord{
    camera = [MKMapCamera cameraLookingAtCenterCoordinate:coord fromEyeCoordinate:coord eyeAltitude:10000000];
    mapView.camera = camera;
}

- (void)mapView:(MKMapView *)mapView1 didUpdateUserLocation:(MKUserLocation *)userLocation
{
    [self addAnnotation:@"My Location" :@"My Location Subtitle" inLocation:userLocation.coordinate];
    
    if (circle != nil) {
        [mapView removeOverlay:circle];
    }
    
    circle = [MKCircle circleWithCenterCoordinate:userLocation.coordinate radius:30];
    [mapView addOverlay:circle];
    
    if (camera == nil) {
        camera = [MKMapCamera cameraLookingAtCenterCoordinate:userLocation.coordinate fromEyeCoordinate:userLocation.coordinate eyeAltitude:300];
        mapView.camera = camera;
    }
}

/* Find the location from the Address and update on Map */
- (CLLocationCoordinate2D) geoCodeUsingAddress:(NSString *)address
{
    double latitude = 0, longitude = 0;
    NSString *esc_addr =  [address stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
    NSString *req = [NSString stringWithFormat:@"http://maps.google.com/maps/api/geocode/json?sensor=false&address=%@", esc_addr];
    NSString *result = [NSString stringWithContentsOfURL:[NSURL URLWithString:req] encoding:NSUTF8StringEncoding error:NULL];
    if (result) {
        NSScanner *scanner = [NSScanner scannerWithString:result];
        if ([scanner scanUpToString:@"\"lat\" :" intoString:nil] && [scanner scanString:@"\"lat\" :" intoString:nil]) {
            [scanner scanDouble:&latitude];
            if ([scanner scanUpToString:@"\"lng\" :" intoString:nil] && [scanner scanString:@"\"lng\" :" intoString:nil]) {
                [scanner scanDouble:&longitude];
            }
        }
    }
    CLLocationCoordinate2D center;
    center.latitude = latitude;
    center.longitude = longitude;
    return center;
}

/* Get the view for the Annotation */
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
    MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"loc"];
    annotationView.canShowCallout = YES;
    annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
    
    return annotationView;
}

/* Handle Click on the annotation */
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
    NSLog(@"Clicked on Accessory View");
}

/* If you implement this, you will not see the annotation view */
- (void)mapView:(MKMapView *)mapView1 didSelectAnnotationView:(MKAnnotationView *)view
{
    [mapView deselectAnnotation:view.annotation animated:YES];
     NSLog(@"didSelectAnnotationView");
}

/* This function renders the Overlay if it is a polyline or Circle */
-(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    
    if([overlay isKindOfClass:[MKCircle class]]){
        MKCircleRenderer *circleView = [[MKCircleRenderer alloc] initWithOverlay:overlay];
        circleView.strokeColor = [UIColor blueColor];
        circleView.fillColor = [[UIColor blueColor] colorWithAlphaComponent:0.4];
        circleView.lineWidth = 1;
        return circleView;
    }
    
    MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
    renderer.strokeColor = [UIColor blueColor];
    renderer.lineWidth = 5.0;
    
    return renderer;
}

/* Delegate function if Location is found */
-(void) onLocationFound : (CLLocationCoordinate2D) location{
    
    NSString *_longitude = [NSString stringWithFormat:@"%f",location.longitude];
    NSString *_latitude = [NSString stringWithFormat:@"%f",location.latitude];
    
    NSLog(@"Lat %@, Long %@", _latitude, _longitude);
}

/* Delegate function if Location is Error */
-(void) onLocationError :(NSString *) errString{
    
    NSLog(@"Error Finding Location %@", errString);
    
}

/* Find route between two locations */
-(void) findRoutes{
  
    MKPlacemark *source = [[MKPlacemark alloc]initWithCoordinate:CLLocationCoordinate2DMake(37.776142, -122.424774) addressDictionary:[NSDictionary dictionaryWithObjectsAndKeys:@"",@"", nil] ];
    
    MKMapItem *srcMapItem = [[MKMapItem alloc]initWithPlacemark:source];
    [srcMapItem setName:@""];
    
    MKPlacemark *destination = [[MKPlacemark alloc]initWithCoordinate:CLLocationCoordinate2DMake(37.73787, -122.373962) addressDictionary:[NSDictionary dictionaryWithObjectsAndKeys:@"",@"", nil] ];
    
    MKMapItem *distMapItem = [[MKMapItem alloc]initWithPlacemark:destination];
    [distMapItem setName:@""];
    
    MKDirectionsRequest *request = [[MKDirectionsRequest alloc]init];
    [request setSource:srcMapItem];
    [request setDestination:distMapItem];
    [request setTransportType:MKDirectionsTransportTypeWalking];
    
    MKDirections *direction = [[MKDirections alloc]initWithRequest:request];
    
    [direction calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
        
        NSLog(@"response = %@",response);
        NSArray *arrRoutes = [response routes];
        [arrRoutes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            
            MKRoute *rout = obj;
            
            MKPolyline *line = [rout polyline];
            [mapView addOverlay:line];
            NSLog(@"Rout Name : %@",rout.name);
            NSLog(@"Total Distance (in Meters) :%f",rout.distance);
            
            NSArray *steps = [rout steps];
            
            NSLog(@"Total Steps : %d",(int)[steps count]);
            
            [steps enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                NSLog(@"Rout Instruction : %@",[obj instructions]);
                NSLog(@"Rout Distance : %f",[obj distance]);
            }];
        }];
    }];
    
}

@end

All Done.

You can download the complete source code from here.

Leave a Reply

Your email address will not be published. Required fields are marked *