Showing posts with label MapKit. Show all posts
Showing posts with label MapKit. Show all posts

Thursday, December 16, 2010

Using iPhone SDK MapKit Framework - A tutorial

Recently I was working on an application in which the map was required to be shown with in the application itself. I tried looking for some online resources that could be of some help but did not find any. I was not able to find any good tutorial that explains how can an address be shown on a map with the application. Therefore, I decided to write one and here it is. Hope it will be of some help. Lets create a simple application which displays the address entered by the user on the map within the application. We'll call it MapApp.
  1. First, create a Window based application and name the project as MapApp.
  2. Add the MapKit framework to the project. (Control + Click Frameworks folder -> Add -> Existing Frameworks)
  3. Create a new view controller class and call it MapViewController. Add a text field, button and map view to it.
#import <UIKit/UIKit.h>
    #import <MapKit/MapKit.h>

    @interface MapViewController : UIViewController<MKMapViewDelegate> {
        IBOutlet UITextField *addressField;
        IBOutlet UIButton *goButton;
        IBOutlet MKMapView *mapView;
    }

    @end
4. Now create a xib file named MapView.xib. Set its type to MapViewController and add a UITextField, UIButton and MKMapView to it. This how it will look like.
Make sure you set the delegate for the mapView to the controller class.
5. Once the view is ready, update the MapAppDelegate so that the view controller and the view is loaded.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
        mapViewController = [[MapViewController alloc] initWithNibName:@"MapView" bundle:nil];
        [window addSubview:mapViewController.view];
        [window makeKeyAndVisible];
    }
6. Now, build the app and check if the view appears correctly or not. We now have the UI ready for entering the address and button for updating the location in the map.
7. Add the class for showing the annotation on the location. Lets call this class as AddressAnnotation.
@interface AddressAnnotation : NSObject<MKAnnotation> {
        CLLocationCoordinate2D coordinate;
        NSString *mTitle;
        NSString *mSubTitle;
    }
    @end

    @implementation AddressAnnotation

    @synthesize coordinate;

    - (NSString *)subtitle{
        return @"Sub Title";
    }

    - (NSString *)title{
        return @"Title";
    }

    -(id)initWithCoordinate:(CLLocationCoordinate2D) c{
        coordinate=c;
        NSLog(@"%f,%f",c.latitude,c.longitude);
        return self;
    }
    @end
This class will basically show the title and the subtitle of the location on the map.
8. Lets add the function that will be called when the 'Go' button is tapped and this will contain the code that will actually display the address location on the map. We call that action as showAddress
- (IBAction) showAddress {
        //Hide the keypad
        [addressField resignFirstResponder];
        MKCoordinateRegion region;
        MKCoordinateSpan span;
        span.latitudeDelta=0.2;
        span.longitudeDelta=0.2;

        CLLocationCoordinate2D location = [self addressLocation];
        region.span=span;
        region.center=location;
        if(addAnnotation != nil) {
            [mapView removeAnnotation:addAnnotation];
            [addAnnotation release];
            addAnnotation = nil;
        }
        addAnnotation = [[AddressAnnotation alloc] initWithCoordinate:location];
        [mapView addAnnotation:addAnnotation];
        [mapView setRegion:region animated:TRUE];
        [mapView regionThatFits:region];
     }
9. The map view basically shows the location based on its latitude and longitude but we have the address in the textual form. Therefore we need to convert this into CLLocationCoordinate2D. Note that in the above code we call the function names addressLocation to perform this conversion.
-(CLLocationCoordinate2D) addressLocation {
    NSString *urlString = [NSString stringWithFormat:@"http://maps.google.com/maps/geo?q=%@&output=csv", 
                    [addressField.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    NSString *locationString = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString]];
    NSArray *listItems = [locationString componentsSeparatedByString:@","];

    double latitude = 0.0;
    double longitude = 0.0;

    if([listItems count] >= 4 && [[listItems objectAtIndex:0] isEqualToString:@"200"]) {
        latitude = [[listItems objectAtIndex:2] doubleValue];
        longitude = [[listItems objectAtIndex:3] doubleValue];
    }
    else {
         //Show error
    }
    CLLocationCoordinate2D location;
    location.latitude = latitude;
    location.longitude = longitude;

    return location;
}
The above code reads the address entered in the input box and gets the location from maps.google.com in CSV format. It then gets the latitude and longitude from it. The return code of 200 from google means success.
10. Finally, lets add the delegate function that will display the annotation on the map
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{
    MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"currentloc"];
    annView.pinColor = MKPinAnnotationColorGreen;
    annView.animatesDrop=TRUE;
    annView.canShowCallout = YES;
    annView.calloutOffset = CGPointMake(-5, 5);
    return annView;
}
This function basically creates a annotation view (a green color pin) with the annotation that we added earlier to the MapView. Tapping on the green pin will display the title and the sub-title.
Finally, here's how the map looks like when loaded.
So this was a very simple example of how a map can be shown from within an application. Hope this was helpful. Let me know your comments/feedback. Click here to download the code.
UPDATE: All this while Google was not looking for API key in the URL - http://maps.google.com/maps/geo?q=address&output=csv
You can obtain your API key here

iOS4 Map Kit – Draggable Annotation Views


21 Jul
Main Screenshot
Finished Product..
The new iOS4 update included many updates to the frameworks in the iPhone SDK. Today I will go through one of those updates which has to do with the Map Kit framework. Support for draggable map annotations is very useful in a location aware application. You will not always have the network connectivity you require to get an exact location for a user and, lets face it, Google does not always return the exact coordinate we desire. This tutorial will step you through creating a View Based application with a standard Map View and dropping a pin on the users current location. The user will then be able to tap and hold the pin to drag around the map. The callout accessory will show the Latitude and Longitude of the new location. Let’s begin. REMINDER: THESE INSTRUCTIONS ARE FOR iOS4 SDK. WILL NOT WORK WITH EARLIER SDK’s.

Step 1 – Create a new View Based application in XCode. Name is whatever you like. For now on I will refer to your projects name as ProdjectName.
New View Based Project
New View Based Project
Step 2 - Add MapKit.framework and CoreLocation.framework to the project.
Frameworks Needed
Project Frameworks
Step 3 – Open the ProdjectNameViewController.h. Here you will need to add an outlet to the map view and create a CLLocationManager and CLLocation. The CLLocation will keep up with our current location.
1:  @interface ProdjectNameViewController : UIViewController <UIAlertViewDelegate, CLLocationManagerDelegate, MKMapViewDelegate> {
2:     IBOutlet MKMapView* myMapView;
3:
4:     CLLocationManager* locationManager;
5:     CLLocation* currentLocation;
6:  }
7:
8:  @property (nonatomic, retain) IBOutlet MKMapView* myMapView;
9:
10:  @property (nonatomic, retain) CLLocationManager* locationManager;
11:  @property (nonatomic, retain) CLLocation* currentLocation;
12:
13:
14:  @end
You may want to go ahead and open your ProdjectNameView.xib and hook up the map view to the view controller outlet at this time.
Step 4 – Open the ProdjectNameViewController.m. You will need to synthesize the newly created variables and create your delegate methods for the Map View and Location Manager. You can see the full code in the attached project.  In the -viewDidLoad method I set the map view and location manager delegates to self, add a few checks for the location services, and start updating to the users current location.
1:  - (void)viewDidLoad {
2:    [super viewDidLoad];
3:
4:       [myMapView setMapType:MKMapTypeHybrid];
5:       [myMapView setDelegate:self];
6:
7:       locationManager = [[CLLocationManager alloc] init];
8:       [locationManager setDelegate:self];
9:       [locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
10:       if(!locationManager.locationServicesEnabled) {
11:            UIAlertView *locationServiceDisabledAlert = [[UIAlertView alloc]
12:                                                                    initWithTitle:@"Location Services Disabled"
13:                                                                    message:@"Location Service is disabled on this device. To enable Location Services go to Settings -> General and set the Location Services switch to ON"
14:                                                                    delegate:self
15:                                                                    cancelButtonTitle:@"Ok"
16:                                                                    otherButtonTitles:nil];
17:            [locationServiceDisabledAlert show];
18:            [locationServiceDisabledAlert release];
19:       }
20:
21:       [locationManager startUpdatingLocation];
22:  }
Step 5 – Let’s go ahead and create 2 new files. The first file will be a MKPlaceMark which we will call CurrentLocationAnnotation and the other will be a MKAnnotationView subclass which we can call AnnotationView.
CurrentLocationAnnotation.h:
1:  #import <MapKit/MapKit.h>
2:
3:  @interface CurrentLocationAnnotation : MKPlacemark {
4:
5:  }
6:
7:  @property (nonatomic, readwrite, assign) CLLocationCoordinate2D coordinate;
8:
9:  @property (nonatomic, retain) NSString *title;
10:  @property (nonatomic, retain) NSString *subtitle;
11:
12:  @end
CurrentLocationAnnotation.m:
1:  #import "CurrentLocationAnnotation.h"
2:
3:
4:  @implementation CurrentLocationAnnotation
5:  @synthesize coordinate;
6:  @synthesize title;
7:  @synthesize subtitle;
8:
9:  - (id)initWithCoordinate:(CLLocationCoordinate2D)coord addressDictionary:(NSDictionary *)addressDictionary {
10:
11:       if ((self = [super initWithCoordinate:coord addressDictionary:addressDictionary])) {
12:            // NOTE: self.coordinate is now different from super.coordinate, since we re-declare this property in header,
13:            // self.coordinate and super.coordinate don't share same ivar anymore.
14:            self.coordinate = coord;
15:       }
16:       return self;
17:  }
18:  @end
AnnotationView.h:
1:  #import <MapKit/MapKit.h>
2:
3:  @interface AnnotationView : MKAnnotationView {
4:
5:  }
6:
7:  @property (nonatomic, assign) MKMapView *mapView;
8:
9:  @end
AnnotationView.m:
1:
2:  #import "AnnotationView.h"
3:  #import "CurrentLocationAnnotation.h"
4:
5:  @interface AnnotationView ()
6:  @property (nonatomic, assign) BOOL hasBuiltInDraggingSupport;
7:  @end
8:
9:  @implementation AnnotationView
10:  @synthesize hasBuiltInDraggingSupport;
11:  @synthesize mapView;
12:
13:  - (void)dealloc {
14:       [super dealloc];
15:  }
16:
17:  - (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
18:
19:       self.hasBuiltInDraggingSupport = [[MKAnnotationView class] instancesRespondToSelector:NSSelectorFromString(@"isDraggable")];
20:
21:       if (self.hasBuiltInDraggingSupport) {
22:            if ((self = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier])) {
23:                 [self performSelector:NSSelectorFromString(@"setDraggable:") withObject:[NSNumber numberWithBool:YES]];
24:            }
25:       }
26:       self.canShowCallout = YES;
27:
28:       return self;
29:  }
30:  @end
Step 6 - Now that we have our annotation view and placemark we can now add these to the map. I add the CurrentLocationAnnotation to the map in the locationManager:didUpdateToLocation:fromLocation method in the ProdjectNameViewController.m like so.
1:
2:  - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
3:  {
4:       [locationManager stopUpdatingLocation];
5:       if(currentLocation == nil) currentLocation = newLocation;
6:       else if (newLocation.horizontalAccuracy < currentLocation.horizontalAccuracy) currentLocation = newLocation;
7:
8:       [myMapView setRegion:MKCoordinateRegionMake(currentLocation.coordinate, MKCoordinateSpanMake(0.05f, 0.05f))];
9:       [myMapView setShowsUserLocation:NO];
10:
11:       CurrentLocationAnnotation *annotation = [[[CurrentLocationAnnotation alloc] initWithCoordinate:self.currentLocation.coordinate addressDictionary:nil] autorelease];
12:       annotation.title = @"Drag Me!";
13:       annotation.subtitle = @"Drag pin to get desired location..";
14:
15:       [myMapView addAnnotation:annotation];
16:  }
Now as soon as the application is loaded and we call the [locationManager startUpdatingLocation] this method is called and we drop a pin on the users current location.
Step 7 – Now we see where the AnnotationView class comes into play. In the Map View’s viewForAnnotation delegate method in the ProdjectNameViewController.m file we call for an AnnotationView which returns a draggable pin view.
1:  - (MKAnnotationView *)mapView:(MKMapView *)MapView viewForAnnotation:(id <MKAnnotation>)annotation
2:  {
3:       static NSString * const kPinAnnotationIdentifier = @"PinIdentifier";
4:       MKAnnotationView *draggablePinView = [MapView dequeueReusableAnnotationViewWithIdentifier:kPinAnnotationIdentifier];
5:
6:       if (draggablePinView) {
7:            draggablePinView.annotation = annotation;
8:       } else {
9:            draggablePinView = [[[AnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:kPinAnnotationIdentifier] autorelease];
10:            if ([draggablePinView isKindOfClass:[AnnotationView class]]) {
11:                 ((AnnotationView *)draggablePinView).mapView = MapView;
12:            }
13:       }
14:       return draggablePinView;
15:  }
Step 8 – Once the users drags the pin, the didChangeDragState delegate method is called to update the latitude and longitude subtitle for the annotation.
1:  - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState
2:  {
3:       if (oldState == MKAnnotationViewDragStateDragging) {
4:            CurrentLocationAnnotation *annotation = (CurrentLocationAnnotation *)annotationView.annotation;
5:            annotation.subtitle = [NSString stringWithFormat:@"%f %f", annotation.coordinate.latitude, annotation.coordinate.longitude];
6:       }
7:  }
Conclusion – This is a very simple implementation of the draggable annotations that iOS4 has released but I feel that this will give you a base to creating a much more functional application with draggable pins. If there are any questions please feel free to comment. Project source is attached. Happy coding!
Source: iOS4DragDrop