Compiled Chronicles

A software development blog by Angelo Villegas

iOS: Venues using Foursquare API and iOS MapKit

Foursquare (foursquare) in their own words is a location-based mobile platform that makes cities easier to use and more interesting to explore. By “checking in” via a smartphone app or SMS, users share their location with friends while collecting points and virtual badges. Foursquare guides real-world experiences by allowing users to bookmark information about venues that they want to visit and surfacing relevant suggestions about nearby venues.

Important: Before continuing, make sure you have a valid Client ID and Secret to use for your app. For more information, go here: https://developer.foursquare.com/

We can display venues using the foursquare Venues Project. The foursquare Venues Project is an API that makes it easy to display the foursquare venues in various ways.

Using the API we can display venues’ informations from the foursquare venues database in JSON format. In this tutorial, we will use the information from the database to display the data in a map using the built-in MapKit of iOS and a JSON parser to parse the returned information. Yes, we can do that because the information from their database includes latitude and longitude.

Note: The JSON parser I used in this project is SBJSON, you can download it here: https://github.com/stig/json-framework/

Important: The MapKit framework uses Google services to provide map data. Use of this class and the associated interfaces binds you to the Google Maps/Google Earth API terms of service. You can find these terms of service at http://code.google.com/apis/maps/iphone/terms.html.

Lastly, we will need an annotation to display the pins to their right places on the map.

Creating the application

Creating Custom Annotation Class

Create a new Objective-C class and import the MKAnnotation.h file. Then implement the MKAnnotation protocol and add 3 properties necessary. I named the file FoursquareAnnotation.

#import <UIKit/UIKit.h>
#import <MapKit/MKAnnotation.h>

@interface FoursquareAnnotation : UIView <MKAnnotation>

@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;

Tip: Only add the MKAnnotation header file instead of adding all the framework’s protocols since we will only need to use the MKAnnotation protocol.

The implementation file should look like this:

@synthesize coordinate;
@synthesize title;
@synthesize subtitle;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

- (void)dealloc
{
    [title release];
    [subtitle release];
    
    [super dealloc];
}

Note: Remember that the MKAnnotation requires 3 properties as stated in http://developer.apple.com/library/ios/#documentation/MapKit/Reference/
MKAnnotation_Protocol/Reference/Reference.html

CLLocationCoordinate2D will hold the latitude and longitude. To know more about CLLocationCoordinate2D, go here: http://developer.apple.com/library/mac/#documentation/
CoreLocation/Reference/CoreLocationDataTypesRef/Reference/reference.html#jumpTo_3

Importing the MapKit Framework

First, you’ll need a view-based application. After creating the project, we will need to import the MapKit Framework to our project. Click the project in the Project Navigator then import the MapKit.framework in the Link Binaries With Libraries.

@interface

Once the framework has been imported to the project we will now start coding. Import the MapKit framework and implement the MKMapViewDelegate Protocol to your view.

#import <MapKit/MapKit.h>
@interface Foursquare_TestViewController : UIViewController <MKMapViewDelegate>

We now need to create properties, 4 properties including 1 IBOutlet connection.

@property (nonatomic, retain) IBOutlet MKMapView *mvFoursquare;

@property (nonatomic, retain) MKUserLocation *userCurrentLocation;

@property (nonatomic, retain) NSURLConnection *urlConnection;

@property (nonatomic, retain) NSMutableData *mutableData;

Note: MKMapView and MKUserLocation is a member of the MapKit Framework the reason we need to import the MapKit first.

@implementation

Open the implementation file and make sure to @synthesize, release all objects we created and nil out the view objects in - viewDidUnload method.

@synthesize mvFoursquare;
@synthesize userCurrentLocation;
@synthesize urlConnection;
@synthesize mutableData;
- (void)dealloc
{
    [mvFoursquare release];
    [userCurrentLocation release];
    [urlConnection release];
    [mutableData release];
	
    [super dealloc];
}
- (void)viewDidUnload
{
    [self setMvFoursquare: nil];
    [self setUrlConnection: nil];
	
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

Don’t forget to #import the JSON parser and the annotation header file. This is needed to parse the data response from foursquare and to display a pin on the map.

#import "SBJSON.h"
#import "FoursquareAnnotation.h"

We now need to implement the MKMapViewDelegate Protocol method - mapView:didUpdateUserLocation:. This method will be called once the map found the user’s location. The map may call this method more than 1 time so make sure to erase and nil out the mutableData property and remove all annotations (if any) as well, to avoid duplicate pins in a coordinate.

[self setMutableData: nil];
[mapView removeAnnotations: [mapView annotations]];

After making sure our app will avoid duplicating pins, it’s time to create the main body of our code – connection. First we need the web service URL, this URL will contact the web service.

// create a request and make a connection
NSString *stringURL = [NSString stringWithFormat: @"https://api.foursquare.com/v2/venues/search?ll=%f,%f&client_id=[YOUR_CLIENT_ID&client_secret=[YOUR_CLIENT_SECRET]", [userLocation coordinate].latitude, [userLocation coordinate].longitude];

Note: The Client ID and Client secret is required for the app to work.

Both the user’s latitude and longitude is needed in the URL so make sure to call coordinate property of the user as the key for the ll parameter, they will be separated by a comma, then followed by the client_id and client_secret. We used the stringWithFormat method to format the URL with the required parameters.

Once the string is formatted, create a NSURL object to create a URL using the string that contains the formatted URL. We’ll do this using the URLWithString method.

NSURL *URLWithString = [NSURL URLWithString: stringURL];

After setting up the URL, the next step is to create the request using the NSURLRequest.

NSURLRequest *urlRequest = [NSURLRequest requestWithURL: URLWithString cachePolicy: NSURLRequestReloadIgnoringCacheData timeoutInterval: 30.f];

The request will contain the URL, a cache policy, and a timeout interval. The urlConnection property will now be allocated and initialised.

urlConnection = [[NSURLConnection alloc] initWithRequest: urlRequest delegate: self];

The connection should start operating after that line. After setting up the connection, we will now update the map to display the user’s location. Create a CLLocationCoordinate2D, this will hold 2 variables from the user’s location, a latitude and longitude. Then we’ll pass it to an MKCoordinateRegion, a structure that defines the area spanned by a map region, and set the region of the map.

CLLocationCoordinate2D userCoords = [userLocation coordinate];
MKCoordinateRegion region = { { userCoords.latitude , userCoords.longitude }, { 0.009f , 0.009f } };
[mapView setRegion: region animated: YES];

The finished code for the method should look like this:

- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
	[self setMutableData: nil];
	
	[mapView removeAnnotations: [mapView annotations]];
	
	NSString *stringURL = [NSString stringWithFormat: @"https://api.foursquare.com/v2/venues/search?ll=%f,%f&client_id=[YOUR_CLIENT_ID&client_secret=[YOUR_CLIENT_SECRET]", [userLocation coordinate].latitude, [userLocation coordinate].longitude];
	NSURL *URLWithString = [NSURL URLWithString: stringURL];
	NSURLRequest *urlRequest = [NSURLRequest requestWithURL: URLWithString cachePolicy: NSURLRequestReloadIgnoringCacheData timeoutInterval: 30.f];
	urlConnection = [[NSURLConnection alloc] initWithRequest: urlRequest delegate: self];
	
	CLLocationCoordinate2D userCoords = [userLocation coordinate];
	MKCoordinateRegion region = { { userCoords.latitude , userCoords.longitude }, { 0.009f , 0.009f } };
	[mapView setRegion: region animated: YES];
}

We now need to implement 2 NSURLConnection delegate method.

– connection:didReceiveData:
– connectionDidFinishLoading:

First, we’ll implement the - connection:didReceiveData:. We will need to initialise the mutableData property, if it’s nil, then append the data from the receiver.

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
	if (mutableData == nil)
	{
		mutableData = [[NSMutableData alloc] init];
	}
	
	[mutableData appendData: data];
}

In every movie there’s a climax, and in this tutorial we’re in that climax. We will now implement the - connectionDidFinishLoading: method of the NSURLConnection and inside this method will contain the allocation and initialisation of some needed objects, transferring the data for displaying, setting up the annotations, and displaying it on the map.

The first part of our method will be to initialise the necessary objects. First is to create a JSON parser, the parser is required so our app can read the data coming from foursquare.

SBJSON *jsonParser = [[SBJSON alloc] init];

Then we will need to create an NSString object and initialise it with the data from the mutableData object.

NSString *strData = [[NSString alloc] initWithData: mutableData encoding: NSUTF8StringEncoding];

After transferring the data, we now should use the parser to parse the string object and transfer it’s data as an NSDictionary object.

NSDictionary *dictVenues = [jsonParser objectWithString: strData error: nil];

We don’t need every details inside the received data so we will only retrieve the necessary details. In this case, what we need is items inside the groups that is inside the response dictionary.

NSDictionary *items = [[[[dictVenues objectForKey: @"response"] objectForKey: @"groups"] objectAtIndex: 0] objectForKey: @"items"];

Tip: You can use NSLog function to see the details inside the parsed data.

The last thing to do for the allocation part is to initialise and allocate an array that will hold the annotations. This needs to be mutable so we can change the data inside the object.

NSMutableArray *annotations = [[NSMutableArray alloc] init];

After allocating necessary objects and data, the next thing to do is to enumerate each dictionary using fast enumeration.

Note: To know more about fast enumeration, go here: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/
Chapters/ocFastEnumeration.html

for (NSDictionary *venues in items)
{
	NSString *name = [venues objectForKey: @"name"];
	NSString *address = [[venues objectForKey: @"location"] objectForKey: @"address"];
	
	CGFloat latitude = [[[venues objectForKey: @"location"] objectForKey: @"lat"] floatValue];
	CGFloat longitude = [[[venues objectForKey: @"location"] objectForKey: @"lng"] floatValue];
	
	FoursquareAnnotation *foursquareAnnotation = [[FoursquareAnnotation alloc] init];
	MKCoordinateRegion region = { { latitude , longitude } , { 0.01f , 0.01f } };
	
	[foursquareAnnotation setCoordinate: region.center];
	[foursquareAnnotation setTitle: name];
	[foursquareAnnotation setSubtitle: address];
	
	[annotations addObject: foursquareAnnotation];
	[foursquareAnnotation release];
}

[mvFoursquare addAnnotations: annotations];

What we did inside the for loop was to create objects that will hold the data from the venues object. Inside the venues, we will need to get the name of the venue and 3 location details, address, latitude, and longitude. The next line was we allocated an object using our custom annotation class, FoursquareAnnotation. Then we’ll create a MKCoordinateRegion, a structure that defines which portion of the map to display.

Note: MKCoordinateRegion is a Map Kit data type. To know more about the Map Kit data type, go here: http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MapKitDataTypesReference/Reference/reference.html

[foursquareAnnotation setCoordinate: region.center];
[foursquareAnnotation setTitle: name];
[foursquareAnnotation setSubtitle: address];

[annotations addObject: foursquareAnnotation];
[foursquareAnnotation release];

Next stop is to setup our annotation object. Setting the necessary properties to ensure that the data will be displayed correctly on our map. As I explained earlier, there are 3 required properties needed, coordinate, title, and subtitle. After setting up the annotation, add it to our annotations object and release the annotation.

The last thing to do is to add the annotations to our map, and then release every object we allocated, only the things we allocated, and nil out the mutableData object.

[mvFoursquare addAnnotations: annotations];

[jsonParser release];
[strData release];
[annotations release];

[self setMutableData: nil];

The finished method should look like this:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
	SBJSON *jsonParser = [[SBJSON alloc] init];
	
	NSString *strData = [[NSString alloc] initWithData: mutableData encoding: NSUTF8StringEncoding];
	
	NSDictionary *dictVenues = [jsonParser objectWithString: strData error: nil];
	NSDictionary *items = [[[[dictVenues objectForKey: @"response"] objectForKey: @"groups"] objectAtIndex: 0] objectForKey: @"items"];
	
	NSMutableArray *annotations = [[NSMutableArray alloc] init];
	
	for (NSDictionary *venues in items)
	{
		NSString *name = [venues objectForKey: @"name"];
		NSString *address = [[venues objectForKey: @"location"] objectForKey: @"address"];
		
		CGFloat latitude = [[[venues objectForKey: @"location"] objectForKey: @"lat"] floatValue];
		CGFloat longitude = [[[venues objectForKey: @"location"] objectForKey: @"lng"] floatValue];
		
		FoursquareAnnotation *foursquareAnnotation = [[FoursquareAnnotation alloc] init];
		MKCoordinateRegion region = { { latitude , longitude } , { 0.01f , 0.01f } };
		
		[foursquareAnnotation setCoordinate: region.center];
		[foursquareAnnotation setTitle: name];
		[foursquareAnnotation setSubtitle: address];
		
		[annotations addObject: foursquareAnnotation];
		[foursquareAnnotation release];
	}
	
	[mvFoursquare addAnnotations: annotations];
	
	[jsonParser release];
	[strData release];
	[annotations release];
	
	[self setMutableData: nil];
}

Interface Builder

The only thing needs to be done here is to add an MKMapView object and connect it to the mvFoursquare property.

Testing the App

Run the app and it should load a map with pins of venues inside where you are if you ran the app on your device, and a map near Apple’s headquarter with pins of venues near that area.

Note: As you may already know, the map view will only display 1 location, the location of Apple’s headquarter back in Cupertino. So if you want to test the app to display your location, you will need to run it on your device.

Conclusion

In this tutorial, you learned how to use the foursquare Venues API, how to create a custom annotation, and how to use a map view with annotations.

Using an API is not very difficult. You only need to learn how the API works and how you can integrate it with your App and all will go smoothly, in this case the foursquare Venues Project.

Annotations can have more than 3 properties, the only reason we only created 3 for this project is because they are the only thing required and dimmed necessary.

Comments

Leave a Reply

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