With the release of Apple maps as a part of the iOS 6, developers had to roll away from all the benefits google maps was offering to the detriment of pure Apple maps SDK.
That is said, Google still has a powerful map engine for iOS that you can use to empower your app. The google maps SDK for iOS offers plenty of great API to work with, one of them is the Google Places API.
In this tutorial, you will learn how to integrate the Google Maps SDK in your iOS project, and how to take advantage of the great Place Autocomplete service in order to build your own address locator map app.
Along the way, you gonna work with Google forward geocoding API to locate the suggested addresses and POI right on the map.
Without further ado, let’s Google map it!
To start off, download the starter project here. Open it with Xcode and run it in the simulator.
The user interface is split in two parts: the search bar where you will type in addresses and POI names and the map view holder where to show the Google map with the marker of the suggested place returned by Google API.
In just few minutes, you will implement all the stuff you need to make your app a real address/POI locator 🙂
Integrate the Google Maps SDK and get the API Key:
In order to use the Google Maps SDK for iOS, you need to follow some steps to get the SDK integrated into our Xcode project. These steps include installing the SDK as a pod using the cocoapods dependency manager.
I can explain the steps here in detail, however, since Google has made a straightforward, step by step guide, I will ask you to follow it instead.
Note: You don’t have to finish all the steps in that guide, just be sure that steps 1 to 5 are completed successfully. This include getting an API key to work with 🙂
Follow the steps using the Xcode starter project you just downloaded. And when configuring the cocoapods Podfile, make sure to make that from within the project directory as explained in the guide.
Important Note: While following the guide steps, make sure that the bundle identifier of the project (in this case com.medigarage.PlacesLookup) is listed in the credentials screen while generating the API Key. To verify, visit the following link, go to your project, then select APIs & auth from the left menu, and then select Credentials. If the bundle identifier does not figure in the list of authorised apps, then you may want to add it 🙂
Here is a screenshot of mine :
After following the steps, you will use the .xcworkspace file to open the project from this time onwards.
So now, I will assume you also got your API Key which may look something like this :
AIzaSyBdVl-cTICSwYKrZ95SuvNw7dbMuDt1KG0
Congratulations, now you are good to go 🙂
The first thing to do is to expose the API key to your project code, so that you can be granted access to the Google Maps SDK features through the code. The best place to do this is in the application:didFinishLaunchingWithOptions: application delegate method.
Select AppDelegate.swift file from the Project navigator view and place the following import at the top of the file:
import GoogleMaps
Next, locate the application:didFinishLaunchingWithOptions: method, and copy the following statement inside ( just before the return statement):
GMSServices.provideAPIKey("YOUR_API_KEY")
Obviously, you need to change the content between the quotes with the API Key that Google generated for you.
Now that the API Key is provided and exposed to all your app files, you can work with the Google package of API from within the code.
Remember the search button you saw in storyboard from the starter project ? Basically, you gonna show a search screen where the user can type in some text to look for addresses and POI.
The content you are going to present on the click of the search icon is managed by an instance of UISearchController class. If you are new to UISearchController, this class will mainly define an interface that manages the presentation of a search bar combined with a search results controller’s content.
Did I say a search results controller?
Well, yes indeed. The search results controller will be useful to hold the display of instant results returned from Google Places API each time the user is typing in the search bar. In other words, while user is typing, we need a controller to manage the content that you gonna display instantly after each typed character.
The following animation illustrates what I mean 🙂
Let’s implement the search results controller. Select “File\New\File” from the menu (or simply cmd+N). Choose “iOS\Source\Cocoa Touch Class” from the templates screen.
Name the class file “SearchResultsController”, and make sure it’s a subclass of UITableViewController, as shown in the screenshot below.
Switch to the Project navigator view, select SearchResultsController.Swift file to load it in the editor, and make the following variables declarations (just under the class name):
var searchResults: [String]! var delegate: LocateOnTheMap!
The searchResults variable will hold all the predicted places that google API will return while you search. While the delegate variable is a protocol reference that you gonna implement in other class file.
Place the following code right above the class name:
protocol LocateOnTheMap{ func locateWithLongitude(lon:Double, andLatitude lat:Double, andTitle title: String) }
The protocol above declares a method with three parameters, this method will be implemented in the main view controller where the map view is presented in order to load the address/POI location.
Always in the same class, locate the viewDidLoad method and implement the following code inside (right after the super.viewDidLoad call):
self.searchResults = Array() self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cellIdentifier")
You just allocated an empty array for the searchResults variable to work with later. And registered the current class as the one to use when creating table cells.
Remember this controller is a UITableViewController subclass, that means it will manage a table view object that you are going to use to show the places and addresses names. It also comes with a predefined table view data source protocol methods by default.
Let’s modify these protocol methods to make them work with the table view.
Locate the numberOfSectionsInTableView method and change its return value to 1, as below:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 }
Next, change the tableView:numberOfRowsInSection: method code to the following:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.searchResults.count }
Now, uncomment the tableView:cellForRowAtIndexPath: method and change its code to look like below:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: indexPath) cell.textLabel?.text = self.searchResults[indexPath.row] return cell }
You are done with the data source protocol methods, now the table view object knows how many sections it has, and how many rows it has to draw.
Let’s finish up by implementing a UITableViewDelegate protocol method that will be notifying the delegate each time the user select a row in the table view.
Place the following code before the class closing bracket:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){ // 1 self.dismissViewControllerAnimated(true, completion: nil) // 2 let correctedAddress:String! = self.searchResults[indexPath.row].stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.symbolCharacterSet()) let url = NSURL(string: "https://maps.googleapis.com/maps/api/geocode/json?address=\(correctedAddress)&sensor=false") let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in // 3 do { if data != nil{ let dic = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableLeaves) as! NSDictionary let lat = dic["results"]?.valueForKey("geometry")?.valueForKey("location")?.valueForKey("lat")?.objectAtIndex(0) as! Double let lon = dic["results"]?.valueForKey("geometry")?.valueForKey("location")?.valueForKey("lng")?.objectAtIndex(0) as! Double // 4 self.delegate.locateWithLongitude(lon, andLatitude: lat, andTitle: self.searchResults[indexPath.row] ) } }catch { print("Error") } } // 5 task.resume() }
Let’s breakdown the code above:
// 1: Here, you dismissed the presented search results controller. Keep in mind that once you start typing in the search bar, a table view controller will be presented on the screen. And once you click on a table row, this content should be dismissed to show the selected address/POI on the map.
// 2: Next, you changed the selected address string in a sort to be able to inject it as a parameter in the url of the google geocoder API. The stringByAddingPercentEncodingWithAllowedCharacters will replace all white spaces inside the address string with percent encoded characters. You then constructed the API url to request, and proceeded an NSURLSession task through the asynchronous dataTaskWithURL method of the NSURLSession great class.
// 3: Since the requested Google API can throw errors, it’s important to handle the returned response inside a do-catch statement. That’s what you did here. If the response is a valid JSON, then the code will retrieve the latitude and longitude of the address.
// 4: Here you called the delegate method locateWithLongitude:andLatitude:andTitle: which will proceed to the map location work effectively. You will implement this method right away.
// 5: After you create the NSURLSession task, it’s important to call its resume method to start it immediately.
Build the project, no errors ? Good job 🙂
Select ViewController.swift file from the Project navigator view. Now you gonna implement the protocol method you defined earlier, along with other stuff.
Let’s start by importing the google maps library in order to be able to use it from the class scope. Copy the line below at the top of the class file:
import GoogleMaps
Next, place the following variables declaration right below the class name:
var searchResultController:SearchResultsController! var resultsArray = [String]() var googleMapsView:GMSMapView!
The first variable is a reference of the SearchResultsController class that you implemented earlier. resultsArray is an array of strings that will store all the predicted places returned from the Google Places API, and googleMapsView is a reference of the google map view that will be drawn on the screen.
Now, implement the viewDidAppear method like below (copy all the code above inside the class):
override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) self.googleMapsView = GMSMapView(frame: self.mapViewContainer.frame) self.view.addSubview(self.googleMapsView) searchResultController = SearchResultsController() searchResultController.delegate = self }
The code above will instantiate a google map view object with the same frames as its container and add it to the root view. It also constructed an object for the SearchResultsController class and set the current class as its delegate.
Setting this class as the delegate for the SearchResultsController class requires two additional steps, the first is to make this class conform to the LocateOnTheMap protocol, and the second is to implement the method defined in that protocol, that is locateWithLongitude:andLatitude:andTitle.
So let’s go ahead and make the relevant changes for this. Modify the class declaration with the following:
class ViewController: UIViewController, LocateOnTheMap
Next, place the following protocol method implementation before the class closing bracket:
func locateWithLongitude(lon: Double, andLatitude lat: Double, andTitle title: String) { dispatch_async(dispatch_get_main_queue()) { () -> Void in let position = CLLocationCoordinate2DMake(lat, lon) let marker = GMSMarker(position: position) let camera = GMSCameraPosition.cameraWithLatitude(lat, longitude: lon, zoom: 10) self.googleMapsView.camera = camera marker.title = title marker.map = self.googleMapsView } }
The protocol method above will be called from within the SearchResultsController class to show the selected address on the map, it basically uses the longitude/latitude arguments to construct a marker with its position, then adjust the camera zoom level and assign a title to the marker.
Note: All the code inside the above method is done explicitly from within the main thread. This is because the caller of this method was running in a background thread, and since all UI work (including the map stuff) should be performed on the main queue, that what explain the switch to the UI thread before drawing to the map 🙂
Next, locate the showSearchController action method and implement the following code inside:
let searchController = UISearchController(searchResultsController: searchResultController) searchController.searchBar.delegate = self self.presentViewController(searchController, animated: true, completion: nil)
The action code above will instantiate a UISearchController object, and assign the searchResultController object you created in the viewDidLoad method as the results holder for the search controller. The search controller manages a search bar, so you set the current class to become the delegate of that search bar because you gonna implement a UISearchBarDelegate protocol method right away. Finally you presented the search controller on the main screen.
You are almost done, change the class declaration to the following to make this class conform to the UISearchBarDelegate protocol:
class ViewController: UIViewController,UISearchBarDelegate, LocateOnTheMap
Cool, build and run the project, and click on the search icon to reveal the presented search controller.
One missing thing to implement is the communication between the Google Places API (including its autocomplete service) and your app. This communication should be performed each time the user is typing in the search bar. So go ahead and implement the following search bar delegate protocol method before the closing bracket of the class:
func searchBar(searchBar: UISearchBar, textDidChange searchText: String){ let placesClient = GMSPlacesClient() placesClient.autocompleteQuery(searchText, bounds: nil, filter: nil) { (results, error:NSError?) -> Void in self.resultsArray.removeAll() if results == nil { return } for result in results!{ if let result = result as? GMSAutocompletePrediction{ self.resultsArray.append(result.attributedFullText.string) } } self.searchResultController.reloadDataWithArray(self.resultsArray) } }
The code above will mainly initialise a place client object to be able to communicate with the Google Places API, it then invokes the autocompleteQuery API from the Google Maps SDK to look for predicted places each time based on the typed text in the search bar. Once the results are returned from the server, they are appended into an array and passed as a parameter to a method of the searchResultController object to reload the table view with the list of places and addresses.
The reloadDataWithArray method is not implemented yet, so let’s implement it to finish up.
Switch back to the SearchResultsController.swift file class and copy the following code inside, just before the class closing bracket:
func reloadDataWithArray(array:[String]){ self.searchResults = array self.tableView.reloadData() }
The method above, once called, will refresh the table view inside the search results view with all the content from the searchResults data source array.
That’s it folks, build and run the project and look for your favorite starbucks. Enjoy your work 🙂
Where to go from here?
As usual, you can download the final project for this tutorial here.
You just used the API from the most powerful mapping services in the world. The set of data provided to you from Google Places API is the same used by Google itself.
In this tutorial, you took advantages from two powerful API services from the Google Maps SDK for iOS: The Google Places engine and the Place Autocomplete API, in addition to the Google forward geocoder webservice.
There is more to know about Google Maps SDK for iOS, its arsenal is full of reliable services that worth learning.
If you got any question about this tutorial, feel free to join the forum discussion here. Also, feel free to leave your comments below, I’d love to hear from you!
Discover more from SweetTutos
Subscribe to get the latest posts sent to your email.