_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

_2006 BMW 330xi For Sale

My Electric Red 2006 BMW 330xi 4-door sedan is for sale. Here are the specs on the vehicle.  Asking $19,000

 

 

 

 

 

 

 

 

Performance

  • 2,996 cc 3 liters in-line 6 front engine with 85 mm bore, 88 mm stroke, 10.7 compression ratio, double overhead cam, variable valve timing/camshaft and four valves per cylinder
  • Premium unleaded fuel 91
  • Fuel consumption: EPA urban (mpg): 19, country/highway (mpg): 28 and combined (mpg): 22
  • Fuel economy EPA highway (mpg): 28 and EPA city (mpg): 19
  • Multi-point injection fuel system
  • 15.9 gallon main premium unleaded fuel tank
  • Power: 190 kW , 255 HP SAE @ 6,600 rpm; 220 ft lb , 298 Nm @ 2,750 rpm
Handling
  • Four-wheel ABS
  • Brake assist system
  • Cornering brake control
  • Four disc brakes including four ventilated discs
  • Electronic brake distribution
  • Electronic traction control via ABS & engine management
  • Immobilizer
  • Center limited slip differential
  • Stability control
  • Strut front suspension independent with stabilizer bar and coil springs , multi-link rear suspension independent with stabilizer bar and coil springs
Exterior
  • Body side molding
  • Part-painted front and rear bumpers
  • Chrome/bright trim around side windows
  • Day time running lights
  • Driver power heated body color door mirrors , passenger power heated body color door mirrors with automatic
  • External dimensions: overall length (inches): 178.2, overall width (inches): 71.5, overall height (inches): 55.9, wheelbase (inches): 108.7, front track (inches): 59.1 and rear track (inches): 59.6
  • Front fog lights
  • High pressure headlight cleaners
  • Projector beam lens Bi-Xenon headlights
  • Heat reflective glass
  • Luxury trim leather on gearknob, wood/woodgrain on doors and wood/woodgrain on dashboard
  • Electric Red paint
  • Fixed rear window with defogger
  • Glass electric front sunroof
  • Tinted glass on cabin
  • Weights: curb weight (lbs) 3,627
  • Windshield wipers with automatic intermittent wipe and rain sensor
Interior
  • 12v power outlet: front and 1
  • Air conditioning with climate control
  • Diversity antenna
  • Front and rear ashtray
  • Audio anti-theft protection: code and integrated in dash
  • Harman/kardon RDS audio system with AM/FM and CD player CD player reads MP3
  • Cargo area light
  • Cargo capacity:
  • Front seats cigar lighter
  • Clock
  • Computer with average speed, average fuel consumption and range for remaining fuel
  • Full dashboard console , full floor console with covered storage box
  • Delayed/fade courtesy lights
  • Cruise control
  • Front seats and rear seats cup holders pop out
  • Door ajar warning
  • Door entry light
  • Door pockets/bins for driver seat and passenger seat
  • External temperature
  • Floor covering: carpet in load area
  • Driver front airbag intelligent , passenger front airbag with occupant sensors intelligent
  • Bucket electrically adjustable driver and passenger seat with height adjustment, four adjustments and tilt adjustment
  • Height adjustable 3-point reel front seat belts on driver seat and passenger seat with pre-tensioners
  • Front seat center armrest
  • Lockable glove compartment
  • Headlight control with time delay switch-off and dusk sensor active
  • Two height adjustable head restraints on front seats , three height adjustable head restraints on rear seats
  • Heated washer
  • Illuminated entry system
  • Internal dimensions: front headroom (inches): 37.4, rear headroom (inches): 37.1, front leg room (inches): 41.5, rear leg room (inches): 34.6, front shoulder room (inches): 55.4, rear shoulder room (inches): 55.1 and interior volume (cu ft): 93
  • Low fuel level warning
  • Low tire pressure indicator
  • Memorized adjustment with three settings on door mirror position with four driver’s seat positions
  • Remote power locks includes trunk/hatch and includes power windows
  • Power steering
  • Front and rear power windows with two one-touch
  • Front and rear reading lights
  • 3-point reel rear seat belts on driver side with pre-tensioners , 3-point reel rear seat belts on passenger side with pre-tensioners , 3-point reel rear seat belts on center side
  • Rear seat center armrest
  • Three fixed bench front facing rear seats with zero adjustments
  • Rear view mirror
  • Steering wheel mounted remote audio controls
  • Remote control remote trunk/hatch release
  • Front and rear roof airbag
  • Front seat back storage
  • Seating: five seats
  • Service interval indicator
  • Front side airbag
  • Smart card / smart key manual, includes central locking, includes memory seat adjustments and includes radio settings
  • Thirteen speaker(s)
  • Leather covered multi-function steering wheel with tilt adjustment and telescopic adjustment
  • Tachometer
  • Illuminated driver and passenger vanity mirror
  • Ventilation system with recirculation setting and micro filter
Options
  • Cold Weather Package
  • Rear seat center armrest with ski bag
  • Heated Front Seats
  • Folding rear seat center armrest
  • Asymmetrical front facing rear seats
  • Premium Package
  • Garage door opener
  • Coming home device
  • Driver and passenger seat with lumbar adjustment
  • Driver and passenger door mirrors with automatic operation
  • Compass
  • Adjustable seatback width and lumbar
  • Electric foldable mirrors;
  • Includes: Leather Upholstery
  • BMW Assist
  • 6-Speed Steptronic Transmission
  • Automatic six-speed transmission with mode select, lock-up, electronic control, manual mode, shift lever on floor, 4.170:1 first gear ratio, 2.340:1 second gear ratio, 1.520:1 third gear ratio, 1.140:1 fourth gear ratio, 0.870:1 fifth gear ratio, 0.690:1 sixth gear ratio and 3.400:1 reverse gear ratio ZF 6 HP and automatic with manual mode;
Extras
  • Beyern 17″ Mesh Wheels
  • Toyo Proxies 4 – brand new
  • Toyo Garit Winter Tires
Aug
23
2011

_Background Image Shrinking on iPad

Recently I was building a site that had full page backgrounds.  One of the pages had a long vertical background to accommodate the content.

Here is an example of what I am talking about:

body {
  background: #fff url(img/my-really-large.jpg) top center no-repeat;
}

Example (view on Desktop)

There really is not an issue (aside from image load) when viewing on Desktop browsers.  However, I would like this site to be compatible with the iPad as well.  Unfortunately this simple technique will not work on the iPad.

The iPad uses Mobile Safari which, by default, scales websites to fit it’s viewport.  It does this to text, images and background images.  It has to do with a resource limit put on the device from displaying large images.  The maximum number is 1024 x 1024 x 3 which is 3,145,728 (result varies a bit depending on iPhone or iPad).  However, Mobile Safari will scale your image accordingly to fit that size and your screen.

You will end up with something like this (view on iPad)

 

There is a simple fix and it has to do with some newer -webkit tags. Add the following to your body declaration in your CSS:

  -webkit-background-size: 1200px 1900px;

(where 1200 1900 is the size of your image)

This will only render on WebKit browsers and fix the issue of scaling.

See here (on iPad please)

This will force the iOS device to load the full background.

This tip will definitely be coming in handy again.

Jul
13
2011

_Amazon Associates No Longer Valid for Connecticut Residents

Amazon.com Associates

I have a problem with Governor Malloy of Connecticut.  His policies are not doing anything to grow the state.  Yes, we are in a financial recession.  Yes, the state has a pretty big deficit but the policies he has proposed or implemented in the first 6 months of office have not made Connecticut enticing to move to or stay in.  From raising income tax, sales tax, sales tax on clothing to taxing pedicures, tax on home buyers and now this.

Amazon.com had a nice little program for associates.  Launched in 1996, the program is one of the most successful affiliate programs on the internet.  It helps store owners, Amazon sellers and developers make money by advertising products from Amazon.com and it’s subsidiaries.  I could build a store front or link to a particular product.  If that product was purchased I would get a little percentage of the transaction.

Well, Governor Malloy decided he wants a piece of the action.  He is imposing a tax on any of the transactions that take place.  This is counterproductive to the whole idea of what Amazon put in place.

As of today, June 10 2011, this program is officially canned for Connecticut residents.  Nice job Malloy, take away yet another thing.

Here is the letter from Amazon:

Hello,
 For well over a decade, the Amazon Associates Program has worked with thousands of Connecticut residents. Unfortunately, the budget signed by Governor Malloy contains a sales tax provision that compels us to terminate this program for Connecticut-based participants effective immediately. It specifically imposes the collection of taxes from consumers on sales by online retailers - including but not limited to those referred by Connecticut-based affiliates like you - even if those retailers have no physical presence in the state.
We opposed this new tax law because it is unconstitutional and counterproductive. It was supported by big-box retailers, most of which are based outside Connecticut, that seek to harm the affiliate advertising programs of their competitors. Similar legislation in other states has led to job and income losses, and little, if any, new tax revenue. We deeply regret that we must take this action.
As a result of the new law, contracts with all Connecticut residents participating in the Amazon Associates Program will be terminated today, June 10, 2011. Those Connecticut residents will no longer receive advertising fees for sales referred to Amazon.com, Endless.com, MYHABIT.COM or SmallParts.com. Please be assured that all qualifying advertising fees earned on or before today, June 10, 2011, will be processed and paid in full in accordance with the regular payment schedule.
You are receiving this email because our records indicate that you are a resident of Connecticut. If you are not currently a resident of Connecticut, or if you are relocating to another state in the near future, you can manage the details of your Associates account here. And if you relocate to another state after June 10, 2011, please contact us for reinstatement into the Amazon Associates Program.
To avoid confusion, we would like to clarify that this development will only impact our ability to offer the Associates Program to Connecticut residents and will not affect their ability to purchase from www.amazon.com.
We have enjoyed working with you and other Connecticut-based participants in the Amazon Associates Program and, if this situation is rectified, would very much welcome the opportunity to re-open our Associates Program to Connecticut residents.
Regards,
The Amazon Associates Team
Jun
10
2011

_Incase Product Support

I recently purchased an iPad 2 and started my search for a case.  Apple makes the new case which has ‘magnets’ and puts the iPad to sleep when it is covered and wakes it up when you open the case.  Sounds pretty cool but it does not protect the back of the iPad.  Placing the iPad down on a desk or a park bench will allow it to get scratched easily so I ruled that one out pretty quickly.  I then narrowed in on a ‘notebook’ looking case.  Something that felt nice, protected it in the front and back and functional.  I wanted to the the ‘original’ iPad case from Apple, but the iPad 2 is much thinner and it will not work.

I found the Incase Book Jacket at BestBuy.  It looked great, felt great and had all of these ‘slots’ for different positions of the iPad.  So I picked one up and used it right away.

My overall reaction is it is nice.  Looks and feel is top notch.  Functionality is not.  There are 3 slots within the case to allow different angles of use.  1 slot works all the time.

This postion works well.  It is great for watching Netflix or any videos on it.  You can type on it but this position is ideal for viewing and swiping.

This is the second slot.  This one works pretty well.  Not as much as the first one but it works.  You can type on here but be careful, at random times it slips out of the slot.  A little annoying.

The third slot seems to be ideal for typing.  Just a slight angle and type away.  The problem is I can never get the iPad to stay in the slot.  It keeps slipping out.  And if I do get it to stay as soon as I breath on it let alone type, it slips out.

There’s not a whole lot I can do about it now. I contacted Incase and they responded with the following:

Hello Josh,

Thank you for contacting Incase Designs Corp.

If you have a problem with your warranted Incase product you may file a claim to have it replaced by going to the following URL:

http://www.goincase.com/warranty-claim-form/

Sincerely,

Incase Designs Corp.

I can’t really warranty it since I do not have the receipt and really do not want to deal with the hassle.  So I am informing others about the case.  Reviews posted on GoIncase.com, Amazon and BestBuy will hopefully educate buyers along with informing Incase that they need to fix the issue at some point.

May
07
2011