You're all set!

To start developing, please head over to our developer documentation.

Activate the Google Places API for iOS

To get you started we'll guide you through the Google Developers Console to do a few things first:

  1. Create or choose a project
  2. Activate the Google Places API for iOS
  3. Create appropriate keys
Continue

My Places

This tutorial shows you how to create an app that lets users create and save custom lists of their favorite places. Follow this tutorial to build an iOS app using the Places API for iOS, and Firebase Realtime Database.

Get the code

Clone or download the Google Maps iOS samples repository from GitHub.

Follow these steps to install the Google Places API for iOS and the Google Maps SDK for iOS:

  1. Download and install Xcode.
  2. If you don't already have the CocoaPods tool, install it on macOS by running the following command from the terminal:
    sudo gem install cocoapods
  3. Clone or download the Google Maps iOS samples repository.
  4. Navigate to the my-places directory, where the project's Podfile is located.
  5. Run the pod install command. This will install the APIs specified in the Podfile, along with any dependencies they may have.
  6. Open my-places.xcworkspace in Xcode.

For detailed installation instructions, see Getting Started (Maps), and Getting Started (Places).

Get an API key and enable the necessary Geo APIs

To complete this tutorial, you need a Google API key that's authorized to use the Google Places API for iOS, and the Google Maps SDK for iOS. Click the button below to get a key and activate the APIs.

To complete this tutorial, you need a Google API key that's authorized to use the Google Maps SDK for iOS and the Google Places API for iOS. Click the button below to get a key and activate the APIs.

Get a Key

For more details, see Get an API key (Maps) , and Get an API key (Places).

Add the API key to your application

In your AppDelegate.swift, add the following to your application(_:didFinishLaunchingWithOptions:) method, replacing YOUR_API_KEY with your API key:

GMSPlacesClient.provideAPIKey("YOUR_API_KEY")
GMSServices.provideAPIKey("YOUR_API_KEY")

Set up the database

Set up Firebase Realtime Database

Follow these instructions to set up the Firebase Realtime Database Be sure to use the same account that you used to enable the Google Places API for iOS and the Google Maps SDK for iOS. Return to this point in the tutorial when your database is ready.

Update database rules

The Firebase Realtime Database requires user authorization in order to read and write data. Since authentication and authorization are beyond the scope of this tutorial, take these steps to add database rules that will allow anyone to make changes to the database:

  1. View Database Rules in the Firebase Realtime Database console.
  2. Paste the following JSON block:

    {
      "rules": {
        ".read": true,
        ".write": true
      }
    }
    

Insert example data

Follow these steps to populate your database with some example data to make it easier to see what's going on.

  1. On your local computer, paste the following code into a new text file and save the file as my-places.json:

    {
      "collections" : {
        "-Kamug00tdDwoSmniu8C" : {
          "key" : "-Kamug00tdDwoSmniu8C",
          "title" : "Yarn Stores"
        },
        "-Kan5kVVaDvx1yzXLT7Z" : {
          "key" : "-Kan5kVVaDvx1yzXLT7Z",
          "title" : "Electronics Supply Stores"
        }
      },
      "places" : {
        "-Kamug00tdDwoSmniu8C" : {
          "-Kan5eZrPnubwTxhFEZJ" : {
            "name" : "Yarn of Eden",
            "placeId" : "ChIJ8zH9ga0PkFQRr615c2-jV10"
          },
          "-KanYQRxAKrUvAD8e2DG" : {
            "name" : "Yarn Country, LLC",
            "placeId" : "ChIJJYk26_RxkFQRyO8_VoGGzaA"
          },
          "-Kao7_Svh-qhZZu54n04" : {
            "name" : "Yarnell",
            "placeId" : "ChIJxekscJQx04ARIMbFVPFqrBc"
          }
        },
        "-Kan5kVVaDvx1yzXLT7Z" : {
          "-Kan628KszkjMIqKqGmI" : {
            "name" : "Vetco Electronics",
            "placeId" : "ChIJCVyVhvtskFQResCv_D3nKaI"
          },
          "-Kan65oh9uRuZ2Nq9hvf" : {
            "name" : "RadioShack",
            "placeId" : "ChIJlZ1nsvQUkFQRpMuXzfTph7c"
          },
          "-Kan7pFIVgGW9l9BdETf" : {
            "name" : "Electronics Mobile Outlet",
            "placeId" : "ChIJp3op7MtykFQRNb-3HYPZxiU"
          }
        }
      }
    }
    
    

  2. Go to the Database section of the Firebase console.

  3. In the Firebase console, click the upper-right menu, and select Import JSON.
  4. Navigate to the file you just saved, and click IMPORT.

Build and run your app

  1. Connect an iOS device to your computer, or select a simulator from the Xcode scheme pop-up menu.
  2. In Xcode, click the Product/Run menu option (or the play button icon).

Xcode builds the app, and then runs the app on the device or on the simulator.

You should see a table listing the default place collections from the example data. Tap a row to see the list of places belonging to the collection.

Understand the code

This part of the tutorial explains the most significant parts of the my-places app, to help you understand how to build a similar app.

The my-places app features four view controllers:

  • MyPlacesViewController is the main view controller for the app. When the app loads, this view displays the list of collections created by the user.
  • PlaceCollectionViewController displays the list of places for the selected collection.
  • PlaceViewController displays details for the selected place, and features buttons to launch Google Maps, and a link which launches the phone app to dial the place's phone number.
  • NameEditor presents an AlertController for editing collection names.

When the app first loads, the app connects to the Firebase database, and the initial view displays a list of collections in a UITableView. When you select a collection, the collection view displays the list of places in the selected collection. When you select a place, the place view shows details for the place. Tap the plus sign (+) in the My Places view to add a new collection; tap the plus sign in the Collection view to launch Place Picker and add a new place.

The data

The Firebase Realtime Database uses JSON to represent data. The example data for this tutorial contains two nodes: collections, which stores the list of place collections, and places, which stores the lists of places for each collection. In the following simplified example, the database contains a single collection containing two places.

  • collections
    • collection-01-key
      • key: collection-01-key
      • title: Collection 01
  • places
    • collection-01-key
      • place-01-key
        • name: Place 01
        • placeId: place-01-ID
      • place-02-key
        • name: Place 02
        • placeId: place-02-ID

In the data for this tutorial, each item is a node in the JSON tree, identified by its associated key. Note that the key for each collection is the same under both the collections and places nodes.

Learn more about structuring data in Firebase Realtime Database.

To simplify working with place collection data, there are models (structs) to represent the two types of data handled by the app:

  • PlaceCollection.swift defines a place collection containing a database key and a display title.
  • PlaceItem.swift defines a place item containing a database key, as well as the place name, and place ID as obtained from the Places API for iOS.

These models expose the data as an object, making it easier to read and write data.

Connect to the database

The DatabaseWrapper helper class functions as a wrapper around Firebase, and contains functions for creating, updating, and deleting data from the database. The following code example from the DatabaseWrapper class shows creating and initializing two database references, one for the list of collections (collectionRef), and one for lists of places within individual collections (placesRef). The initialization function is called each time the class is instantiated.

private let collectionRef: DatabaseReference
private let placesRef: DatabaseReference

let database = Database.database()

private init() {
  // Create a reference to the collections Firebase DB (get the list of place collection titles).
  collectionRef = database.reference(withPath: "collections")

  // Create a reference to the places Firebase DB (get the list of places in a collection).
  placesRef = database.reference(withPath: "places")
}

Show collections and listen for changes

The observeCollections() function (DatabaseWrapper class) listens for changes to the collections database node, building a fresh list of items (newItems) each time a change occurs. This list is then used to populate a table with the list of place collections. The following code example shows the observeCollections() function.

func observeCollections(changeHandler: @escaping ([PlaceCollection]) -> ()) {
  collectionRef.observe(.value, with: { snapshot in
    var newItems: [PlaceCollection] = []
    for item in snapshot.children {
      let collectionItem = PlaceCollection(snapshot: item as! DataSnapshot)
      newItems.append(collectionItem)
    }

    changeHandler(newItems)
  })
}

The viewDidLoad function of the MyPlacesViewController class calls observeCollections on the shared database instance to populate the table when the app first launches, storing the list of collections in an array named items.

DatabaseWrapper.sharedInstance.observeCollections { newCollections in
  self.items = newCollections
  self.collectionTable.reloadData()
}

The UITableViewDataSource delegate functions are used to populate the table with the list of collections (the items array). These functions are implemented in the MyPlacesViewController class by using a protocol extension. The following code example shows the protocol extension and delegate functions.

extension MyPlacesViewController: UITableViewDataSource {
  // Get the number of rows from the data source.
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return items.count
  }

  // Create a cell for each table view row.
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // Create a new cell if needed, or reuse an old one.
    let cell = collectionTable.dequeueReusableCell(withIdentifier: cellReuseIdentifier)! as UITableViewCell
    let collectionItem = items[indexPath.row]

    cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
    cell.textLabel?.text = collectionItem.title

    return cell
  }
}

Show places and listen for changes

The pattern for showing the list of places belonging to a collection is exactly the same as that used to show the list of collections. In MyPlacesViewController, when you tap a collection from the collection list, the didSelectRowAt delegate function calls the edit function with the selected collection.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  edit(items[indexPath.row])
}

The edit function then performs a segue to PlaceCollectionViewController to show the places in the selected collection.

// Edit the selected collection.
func edit(_ collection: PlaceCollection) {
  // Store the selected collection so it can be accessed in prepare(for:sender:)
  selectedCollection = collection

  performSegue(withIdentifier: "segueToEdit", sender: self)
}

// If a collection was selected, pass the id to the new view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if segue.identifier == "segueToEdit" {
    if let nextViewController = segue.destination as? PlaceCollectionViewController {
      nextViewController.collection = selectedCollection

      // Reset this to nil for the next time.
      selectedCollection = nil
    }
  }
}

The viewDidLoad function of the PlaceCollectionViewController class calls observePlaces on the shared database instance to populate the table of places for the selected collection, storing the list of places in an array named items. This is the same pattern that is used to fill the collections table in MyPlacesViewController.

DatabaseWrapper.sharedInstance.observePlaces(for: collection) { newPlaces in
  self.items = newPlaces
  self.placesTableView.reloadData()
}

The UITableViewDataSource delegate functions populate the table with the list of places (the items array). These functions are implemented in the PlaceCollectionViewController class by using a protocol extension. Once again this is the same pattern that is used to listen for changes and fill the collections table in MyPlacesViewController. The following code example shows the protocol extension and delegate functions in PlaceCollectionViewController.

extension PlaceCollectionViewController: UITableViewDataSource {

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return items.count
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // Create a new cell if needed, or reuse an old one.
    let cell = self.placesTableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier)! as UITableViewCell
    let collectionItem = items[indexPath.row]

    cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
    cell.textLabel?.text = collectionItem.name

    return cell
  }
}

Show details for the selected place

When you select a place from the list, the prepare function in PlaceCollectionViewController performs a segue to PlaceViewController, which calls lookUpPlaceId to get the place, then displays the name, address, phone number, attribution, and the first photo (a place can have up to 10 photos). The following code example shows the getDetails() function, which gets the selected place and populates the view with details for that place:

func getDetails() {
  placesClient!.lookUpPlaceID(place.placeId, callback: { (place, error) -> Void in
    if let error = error {
      print("lookup place id query error: \(error.localizedDescription)")
      return
    }

    if let place = place {
      self.placeNameLabel.text = place.name
      self.addressLabel.text = place.formattedAddress
      self.phoneButton.setTitle(place.phoneNumber, for: .normal)
      self.placeDetails = place
    } else {
      print("No place details for \(self.place.placeId)")
    }
  })

  // Get metadata for photos belonging to the selected place.
  // The photos result returns metadata for up to 10 photos.
  placesClient!.lookUpPhotos(forPlaceID: place.placeId, callback: { (photos, error) -> Void in
    if let error = error {
      // TODO: handle the error.
      print("Error: \(error.localizedDescription)")
    } else {
      // If there are photos, call loadImage with the metadata for the first one.
      // The result ('photos') is an array of GMSPlacePhotoMetadata objects.
      if let firstPhoto = photos?.results.first {
        self.loadImage(for: firstPhoto)
      }
    }
  })
}

Tapping the location icon in the upper-right corner calls launchMaps(), which uses a URL to launch the Google Maps app, centered on the selected place. The following example shows the launchMaps() function. Learn more about launching Google Maps.

@IBAction func launchMaps(_ sender: Any) {
  if let address = placeDetails.formattedAddress {
    let formattedAddress = address.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
    let placeUrl = URL(string: "https://maps.google.com?q=\(formattedAddress)&zoom=14")
    if (UIApplication.shared.canOpenURL(URL(string:"https://maps.google.com")!)) {
      UIApplication.shared.open(placeUrl!, options: [:], completionHandler: nil)
    } else {
      print("Can't use https://maps.google.com");
    }
  }
}

Add a new collection

In the main view, you can add a new collection by tapping the plus sign in the upper-right. This calls addNewCollection(), which displays the name editor. When you enter a name for the new collection and tap OK, addNewCollection() calls the addCollection() method on the shared database instance, and performs a segue to the collection view where you can add places to the collection. The following example shows the addNewCollection() function.

@IBAction func addNewCollection(_ sender: Any) {
  let nameEditor = NameEditor.create { collectionName in
    if let collectionName = collectionName {
      let newCollection = Database.sharedInstance.addCollection(named: collectionName)

      self.edit(newCollection)
    }
  }

  present(nameEditor, animated: true, completion: nil)
}

Rename a collection

In collection view, you can rename a collection by tapping the editing icon in the upper-right. This calls editCollectionName(), which displays the name editor showing the current name for the collection. When you tap OK, editCollectionName() calls the update() function on the shared database instance, and updates the label to show the new name.

@IBAction func editCollectionName(_ sender: Any) {
  let nameEditor = NameEditor.create(existingText: collection.title) { newTitle in
    if let newTitle = newTitle {
      self.collection.title = newTitle
      Database.sharedInstance.update(self.collection)

      // Update the text label.
      self.title = newTitle
    }
  }

  present(nameEditor, animated: true, completion: nil)
}

Add a new place

In collection view, you can add a new place by tapping the plus sign in the upper-right. This calls pickPlace(), which launches the Place Picker, as shown in the following example:

@IBAction func pickPlace(_ sender: UIBarButtonItem) {
  let config = GMSPlacePickerConfig(viewport: nil)
  let placePicker = GMSPlacePickerViewController(config: config)
  placePicker.delegate = self

  self.present(placePicker, animated: true, completion: nil)
}

In order to use the Place Picker, your view controller must implement the GMSPlacePickerViewControllerDelegate protocol. In the following example, this is done by using an extension clause:

extension PlaceCollectionViewController: GMSPlacePickerViewControllerDelegate {

  // Update the collection with the user's chosen place.
  func placePicker(_ viewController: GMSPlacePickerViewController, didPick place: GMSPlace) {
    let _ = DatabaseWrapper.sharedInstance.add(place, to: self.collection)

    // Dismiss the place picker, as it cannot dismiss itself.
    viewController.dismiss(animated: true, completion: nil)
  }

  func placePickerDidCancel(_ viewController: GMSPlacePickerViewController) {
    // Dismiss the place picker, as it cannot dismiss itself.
    viewController.dismiss(animated: true, completion: nil)

    print("No place selected")
  }

  func placePicker(_ viewController: GMSPlacePickerViewController, didFailWithError error: Error) {
    print("Pick Place error: \(error.localizedDescription)")
  }

}

Delete a place or collection

To delete an item from a list, swipe left then tap Delete. To enable deletion within a UITableView, check the value of editingStyle, and perform the appropriate action if .delete is found. The following example shows enabling deletion in the UITableViewDelegate protocol extension in PlaceCollectionViewController:

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
  if editingStyle == .delete {
    DatabaseWrapper.sharedInstance.delete(collection.key, itemKey: items[indexPath.row].key)
  }
}

Send feedback about...

location_on
Google Places API for iOS