_Google Places for iOS 5 SDK

I am integrating the Google Places API into an iPhone app that I am building.  I found an existing library that uses Google Local Search but that is deprecated.  So I forked that solution and came up with Google Places Library.

As the story goes…when the application is configured to allow current location, the fun begins.

First configure your project to include the CoreLocation and the MapKit framework.  I am using a single view project for this example but it can be any project really.

In the RootViewController I need to define some delegates.  Since I will be embedding a UITableView and UISearchBar I need the following to be added: <UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, CLLocationManagerDelegate, GooglePlacesConnectionDelegate>

UITableViewDelegate – will help populate the data on my table.

UISearchBarDelegate – will perform some interactions with the searching of places

CLLocationManagerDelegate – will get the location

GooglePlacesConnectionDelegate – will perform the main search

Once that is complete I can then add some outlets for my UI and set up some properties.

#import
#import
#import "GooglePlacesConnection.h"

@class GooglePlacesObject;

@interface RootViewController : UIViewController
{
    IBOutlet UITableView    *tableView;
    IBOutlet UIButton       *btnCancel;
    IBOutlet UISearchBar    *searchBar;

    CLLocationManager       *locationManager;
    CLLocation              *currentLocation;

    NSMutableData           *responseData;
    NSMutableArray          *locations;
    NSString                *searchString;

    GooglePlacesConnection  *googlePlacesConnection;
}

@property (nonatomic, getter = isResultsLoaded) BOOL resultsLoaded;

@property (nonatomic, retain) CLLocationManager *locationManager;
@property (nonatomic, retain) CLLocation        *currentLocation;

@property (nonatomic, retain) NSURLConnection   *urlConnection;
@property (nonatomic, retain) NSMutableData     *responseData;
@property (nonatomic, retain) NSMutableArray    *locations;

@end

Lets move on to the meat and potatoes of this app.  Within the .m file I need to initialize some of my variables and get the GoogleConnection going.

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.

    responseData = [[NSMutableData data] init];

    [[self locationManager] startUpdatingLocation];

    [tableView reloadData];
    [tableView setContentOffset:CGPointZero animated:NO];

   // [searchBar setDelegate:self];

    googlePlacesConnection = [[GooglePlacesConnection alloc] initWithDelegate:self];

}

Do not forget to set the url connection to nil in the viewDidUnload.

Lets move to the Location Manager methods.

The first one returns a location manager or creates one if necessary

- (CLLocationManager *)locationManager
{

    if (locationManager != nil)
    {
		return locationManager;
	}

	locationManager = [[CLLocationManager alloc] init];
	[locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
	[locationManager setDelegate:self];

	return locationManager;
}

The next method really starts the fun. In my case I am constructing a URL for the Google Places API. If you are not familiar with it you can find more about it here. The url takes the following parameters:

  • key: required – your API key
  • location: required – latitude/longitude around which to retrieve Place information. Location manager will help here
  • radius: required – distance in meters. For the initial search I have 500, for the UISearchBar I moved it out to 1000.
  • sensor: required – needs to be set to TRUE if came from a device with a location sensor
  • keyword: optional – a term to be matched if searching
  • language: optional – language codes
  • name: optional – term to be matched against the names of the Places
  • types: narrows the search to a specific type of place. Here is a list

At the end of all of this the url will look like this:

https://maps.googleapis.com/maps/api/place/search/json?location=-33.8670522,151.1957362&radius=500&types=food&name=harbour&sensor=false&key=AIzaSyAiFpFd85eMtfbvmVNEYuNds5TEF9FjIPI

- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation
{

    if ([self isResultsLoaded])
    {
		return;
	}

	[self setResultsLoaded:YES];

    currentLocation = newLocation;

    //What places to search for
    NSString *searchLocations = [NSString stringWithFormat:@"%@|%@|%@|%@|%@|%@|%@|%@|%@",
                                 kBar,
                                 kRestaurant,
                                 kCafe,
                                 kBakery,
                                 kFood,
                                 kLodging,
                                 kMealDelivery,
                                 kMealTakeaway,
                                 kNightClub
                                 ];

    [googlePlacesConnection getGoogleObjects:CLLocationCoordinate2DMake(newLocation.coordinate.latitude, newLocation.coordinate.longitude) andTypes:searchLocations];

}

The last methods are the setting of the objects and some clean up.

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

#pragma mark -
#pragma mark NSURLConnections

- (void)googlePlacesConnection:(GooglePlacesConnection *)conn didFinishLoadingWithGooglePlacesObjects:(NSMutableArray *)objects
{

    if ([objects count] == 0) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No matches found near this location"
                                                        message:@"Try another place name or address"
                                                       delegate:nil
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles: nil];
        [alert show];
    } else {
        locations = objects;
        [tableView reloadData];
    }
}

- (void) googlePlacesConnection:(GooglePlacesConnection *)conn didFailWithError:(NSError *)error
{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error finding place - Try again"
                                                    message:[error localizedDescription]
                                                   delegate:nil
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles: nil];
    [alert show];
}

I am not going to dive deep into the GooglePlacesConnection class or the GooglePlacesObject. However I will say this, the GooglePlacesObject is just that. An object of the Google Place. It has an init that will take the dictionary object from the JSON results. The GooglePlacesConnection does the work of constructing of the URL and issuing the request. One thing to note, the only error checking on the Google Place request is if there are 0 results or an error with the URL or request. There are other status codes if you want to add them in if needed.

Back into the RootViewController.m, we now should have a locations NSMutableArray so we can now populate our UITableView.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)theTableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)theTableView numberOfRowsInSection:(NSInteger)section
{
    return [locations count];
}

- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

	static NSString *CellIdentifier = @"LocationCell";

	// Dequeue or create a cell of the appropriate type.
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell                = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        cell.accessoryType  = UITableViewCellAccessoryDisclosureIndicator;
    }

    // Get the object to display and set the value in the cell.
    GooglePlacesObject *place     = [[GooglePlacesObject alloc] init];
    place                       = [locations objectAtIndex:[indexPath row]];

    cell.textLabel.text                         = place.name;
    cell.textLabel.adjustsFontSizeToFitWidth    = YES;
	cell.textLabel.font                         = [UIFont systemFontOfSize:12.0];
	cell.textLabel.minimumFontSize              = 10;
	cell.textLabel.numberOfLines                = 4;
	cell.textLabel.lineBreakMode                = UILineBreakModeWordWrap;
    cell.textLabel.textColor                    = [UIColor colorWithRed:0.0 green:128.0/255.0 blue:0.0 alpha:1.0];
    cell.textLabel.textAlignment                = UITextAlignmentLeft;

    cell.detailTextLabel.text                   = place.vicinity;
    cell.detailTextLabel.textColor              = [UIColor darkGrayColor];
    cell.detailTextLabel.font                   = [UIFont systemFontOfSize:10.0];

    return cell;
}

We set the count of the array to the number of rows in section. We then get the locations object at the indexPath and set that to a GooglePlacesObject and populate the cell.

To handle the UISeachBar here is the code:

- (void)updateSearchString:(NSString*)aSearchString
{
    searchString = [[NSString alloc]initWithString:aSearchString];

    //What places to search for
    NSString *searchLocations = [NSString stringWithFormat:@"%@|%@|%@|%@|%@|%@|%@|%@|%@",
                                 kBar,
                                 kRestaurant,
                                 kCafe,
                                 kBakery,
                                 kFood,
                                 kLodging,
                                 kMealDelivery,
                                 kMealTakeaway,
                                 kNightClub
                                 ];

    [googlePlacesConnection getGoogleObjectsWithQuery:searchString andCoordinates:CLLocationCoordinate2DMake(currentLocation.coordinate.latitude, currentLocation.coordinate.longitude) andTypes:searchLocations];

    [tableView reloadData];
}

- (void)searchBarTextDidBeginEditing:(UISearchBar *)theSearchBar
{
    [theSearchBar setShowsCancelButton:YES animated:YES];
    tableView.allowsSelection   = NO;
    tableView.scrollEnabled     = NO;

}

- (void)searchBarCancelButtonClicked:(UISearchBar *)theSearchBar
{
    [theSearchBar setShowsCancelButton:NO animated:YES];
    [theSearchBar resignFirstResponder];
    tableView.allowsSelection   = YES;
    tableView.scrollEnabled     = YES;
    theSearchBar.text           = @"";

    [self updateSearchString:searchBar.text];
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)theSearchBar
{
    tableView.allowsSelection   = YES;
    tableView.scrollEnabled     = YES;

    [self updateSearchString:theSearchBar.text];
}

The second, third and fourth method handle the interaction with the UISearchBar itself. Each calls the updateSearchString method and passes in the text from the search bar. updateSearchString performs a similar search to the location manager search, except it is passing in the search string as well. If you look in the GooglePlacesConnection method I am also expanding the search radius to 1000 from 500.

That is it. You can find the source on github.

My next update will be adding a ‘pull to refresh’.

Nov
28
2011

_jQuery Accordion with 8 Lines of Code

Ok.  Maybe more than 8 lines of code but very little.  I have been working with another agency (that will rename nameless) on a mobile web site for a client of ours.  The agency provided the HTML/CSS/Images and Script for our technology team to implement.  We hook up all the back end and call it a day.

The section that came into question was an ‘accordion’ style UI for a set of FAQ’s and other content.  When I first loaded up their pages, everything worked fine.  Upon further inspection of their code I noticed a lot of JavaScript libraries that were not needed.  They included jQuery AND Prototype along with some effects and accordion scripts.  (Prototype alone is almost 100kb in file size and the other scripts were 42kb.  That still does not include the 70kb for jQuery)  I thought to myself, this is a mobile site…why have extra scripts if not needed.  To make matters even more funny, we are supporting Mobile Safari (iOS), Mobile Chrome (Android) and the BlackBerry Browser (RIM), it does not work in BlackBerry at all.  Forget enabling or disabling JavaScript it just does not work.  Even the elements are hidden.

So it got me thinking about the old saying, “if you want something done right you have to do it yourself”.  So I did.

I removed all the libraries except for jQuery and made all of the elements visible by default.  Basically if a user does not have JavaScript enabled then they can see everything.

Here is my HTML

<div>
     <a href="#">Really Great Information</a>
     <div class="accordion_content">
          Content that will expand
     </div>
     <a href="#">Really Great Information</a>
     <div class="accordion_content">
         Content that will expand again
     </div> 
</div>

Styles and other HTML can be wrapped around it. You can change the paragraph tags to div’s if needed but that is the structure you need more or less.

Now for the JavaScript.

Make sure you embed jQuery (I did from Google)

$('.accordion_content').hide();
//If JavaScript is not enabled, all content containers stay open, which is nice.

//Next, bind the click event to all of the targets, in this case all 'a' elements in the 'accordion_toggle' class
$('.accordion_toggle a').click(function(e){
     //code to come
});
//Within the click function we need to first check if the element being clicked is set as the 'current' which means open.  If it is, we need to remove the class 'current' and close the content container.
if($(this).parent().hasClass('current')) {
     $(this).parent()
          .removeClass('current')
          .next('.accordion_content').slideUp();
} else {
...
}

Then hide all open content containers:

If the element does not have the ‘current’ class then we need to first remove the ‘old’ ‘current’, close the ‘old’ ‘current’ and open and set the new ‘current’.

$(document).find('.current')
     .removeClass('current')
     .next('.accordion_content').slideUp();

$(this).parent()
     .addClass('current')
     .next('.accordion_content').slideDown();

Done.

Here is the final:

$(function() {
     $('.accordion_content').hide();
     $('.accordion_toggle a').click(function(e){
          if($(this).parent().hasClass('current')) {
               $(this).parent()
                   .removeClass('current')
                   .next('.accordion_content').slideUp();
          } else {
               $(document).find('.current')
                    .removeClass('current')
                    .next('.accordion_content').slideUp();
              $(this).parent()
                    .addClass('current')
                    .next('.accordion_content').slideDown();
          }
          e.preventDefault();
      });
});

Depending on how you format the code and what you count as a ‘line’ it can be about 8 lines :)

Demo here – View in Mobile Browser.  Desktop browser will work as well.  Works on iOS, Android, BlackBerry.

Mar
10
2011

_My Top 5 iOS Annoyances

  1. Add To Home Screen – When I browse to a web site using mobile safari I might choose to add it to my home screen since it can be a nice way to launch the web browser.  When I do that task, Apple (for some reason) decides to leave the Safari window and show me where the icon is being placed.  How nice…and annoying.  Now I have to go back to my Safari Icon and open it back up, which will now RELOAD the web page (in some cases)…argh!
  2. Push Notifications – Not sure if this is an iOS thing or just certain apps.  Let’s take ESPN Scorecenter for example, a score happens, I get a push notification alert sound.  I go to my phone and see the notification.  I then do anything and “poof” it goes away.  Why not open the app?  Why not give me an option to close or open the app?
  3. Auto Correct – Wow.  I really hate this one.  Why not an Auto Suggest?  I might want the incorrect word?  Why should I perform another action to NOT correct the word.  I should type away, it should ‘suggest’ a new word, if I want to use the word then I can initiate it otherwise ignore it.  Right now, if it suggests a word it uses it unless I tell it not to…which is slower…argh.
  4. Installation of Apps – similar to Add to Home Screen.  When I install an app, it leaves the app store to show me the icon and the installation process.  Wow…how exciting.  Why?  Makes no sense.
  5. Multitasking – It really is a joke.  Your telling me there is no way to quit an app unless I go to the multitasking screen?  Obviously you do not have kids.  They open up 20-30 apps and quit them all the time but they are still running?  Why not prompt for a ‘quit’ when you ‘quit’ an app?  C’mon.
Dec
23
2010

_WordPress for the iPhone

I am using the WordPress app for the iPhone. I wonder if I can add images? Looks like I can.

I can now manage my blog on the run…like I really need that.

Nov
18
2009