Все готово!

Прежде чем приступить к разработке, ознакомьтесь с документацией для разработчиков.

Активация Google Places API for iOS

Чтобы помочь вам освоиться, мы покажем, как выполнить некоторые необходимые действия в консоли разработчика Google:

  1. Создание или выбор проекта
  2. Активация Google Places API for iOS
  3. Создание соответствующих ключей

Подсказки мест

Служба подсказки мест в Google Places API for iOS возвращает список подсказок мест в ответ на пользовательские поисковые запросы. Когда пользователь набирает текст, служба подсказки мест выдает предполагаемые варианты для различных мест, таких как организации, адреса и достопримечательности.

Для добавления подсказок мест в приложение можно использовать следующие методы.

Добавление элемента управления пользовательского интерфейса службы подсказки мест

Элемент управления пользовательского интерфейса службы подсказки мест – это окно поиска с функцией подсказки мест. По мере того, как пользователь вводит поисковый запрос, этот элемент предлагает список предполагаемых мест, из которого можно выбрать подходящее. Когда пользователь делает выбор, возвращается экземпляр GMSPlace, который затем может быть использован в вашем приложении для получения подробной информации о выбранном месте.

Чтобы добавить элемент управления пользовательского интерфейса службы подсказки мест в приложение, можно использовать следующие методы:

Добавление элемента управления полноэкранного режима

Элемент управления полноэкранного режима можно использовать, если вам необходим модальный контекст, когда интерфейс службы подсказки мест временно заменяет пользовательский интерфейс вашего приложения, пока пользователь не сделает свой выбор. Эта функциональная возможность обеспечивается благодаря классу GMSAutocompleteViewController. Когда пользователь выбирает то или иное место, ваше приложение получает обратный вызов.

Чтобы добавить элемент управления полноэкранного режима в приложение, выполните следующие действия.

  1. Создайте элемент пользовательского интерфейса в своем основном приложении, который будет запускать элемент управления пользовательского интерфейса службы подсказки мест, например, обработчик касаний UIButton.
  2. Реализуйте протокол GMSAutocompleteViewControllerDelegate в родительском контроллере представлений.
  3. Создайте экземпляр GMSAutocompleteViewController и назначьте родительский контроллер представлений в качестве делегированного свойства.
  4. Представьте GMSAutocompleteViewController, используя [self presentViewController...].
  5. Обработайте элемент, выбранный пользователем, в делегированном методе didAutocompleteWithPlace.
  6. Деактивируйте контроллер в делегированных методах didAutocompleteWithPlace, didFailAutocompleteWithError и wasCancelled.

В следующем примере демонстрируется один возможный способ запустить GMSAutocompleteViewController в ответ на нажатие кнопки пользователем.

Swift

import UIKit
import GooglePlaces

class ViewController: UIViewController {

  // Present the Autocomplete view controller when the button is pressed.
  @IBAction func autocompleteClicked(_ sender: UIButton) {
    let autocompleteController = GMSAutocompleteViewController()
    autocompleteController.delegate = self
    present(autocompleteController, animated: true, completion: nil)
  }
}

extension ViewController: GMSAutocompleteViewControllerDelegate {

  // Handle the user's selection.
  func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
    print("Place name: \(place.name)")
    print("Place address: \(place.formattedAddress)")
    print("Place attributions: \(place.attributions)")
    dismiss(animated: true, completion: nil)
  }

  func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
    // TODO: handle the error.
    print("Error: ", error.localizedDescription)
  }

  // User canceled the operation.
  func wasCancelled(_ viewController: GMSAutocompleteViewController) {
    dismiss(animated: true, completion: nil)
  }

  // Turn the network activity indicator on and off again.
  func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
    UIApplication.shared.isNetworkActivityIndicatorVisible = true
  }

  func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
    UIApplication.shared.isNetworkActivityIndicatorVisible = false
  }

}

Objective-C

#import "ViewController.h"
#import <GooglePlaces/GooglePlaces.h>

@interface ViewController () <GMSAutocompleteViewControllerDelegate>

@end

@implementation ViewController

...

// Present the autocomplete view controller when the button is pressed.
- (IBAction)onLaunchClicked:(id)sender {
  GMSAutocompleteViewController *acController = [[GMSAutocompleteViewController alloc] init];
  acController.delegate = self;
  [self presentViewController:acController animated:YES completion:nil];
}

// Handle the user's selection.
- (void)viewController:(GMSAutocompleteViewController *)viewController
didAutocompleteWithPlace:(GMSPlace *)place {
  [self dismissViewControllerAnimated:YES completion:nil];
  // Do something with the selected place.
  NSLog(@"Place name %@", place.name);
  NSLog(@"Place address %@", place.formattedAddress);
  NSLog(@"Place attributions %@", place.attributions.string);
}

- (void)viewController:(GMSAutocompleteViewController *)viewController
didFailAutocompleteWithError:(NSError *)error {
  [self dismissViewControllerAnimated:YES completion:nil];
  // TODO: handle the error.
  NSLog(@"Error: %@", [error description]);
}

// User canceled the operation.
- (void)wasCancelled:(GMSAutocompleteViewController *)viewController {
  [self dismissViewControllerAnimated:YES completion:nil];
}

// Turn the network activity indicator on and off again.
- (void)didRequestAutocompletePredictions:(GMSAutocompleteViewController *)viewController {
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)didUpdateAutocompletePredictions:(GMSAutocompleteViewController *)viewController {
  [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

@end

Добавление контроллера результатов

Контроллер результатов можно использовать, если вам необходим дополнительный контроль пользовательского интерфейса, используемого для ввода текста. Контроллер результатов динамически переключает отображение списка результатов в зависимости от фокуса пользовательского интерфейса, используемого для ввода.

Чтобы добавить контроллер результатов в свое приложение, выполните следующие действия.

1.Создайте GMSAutocompleteResultsViewController. 1. Реализуйте протокол GMSAutocompleteResultsViewControllerDelegate в родительском контроллере представлений и назначьте этот контроллер в качестве делегированного свойства. 1. Создайте объект UISearchController, передающий GMSAutocompleteResultsViewController в виде аргумента контроллера результатов. 1. Укажите GMSAutocompleteResultsViewController как свойство searchResultsUpdater для UISearchController. 1. Добавьте searchBar для UISearchController в пользовательский интерфейс своего приложения. 1. Обработайте элемент, выбранный пользователем, в делегированном методе didAutocompleteWithPlace.

Существует несколько способов добавить панель поиска UISearchController в интерфейс вашего приложения, а именно:

Добавление панели поиска на панель навигации

В следующем примере кода показано, как добавлять контроллер результатов, добавлять searchBar на панель навигации, а также обрабатывать элемент, выбранный пользователем.

Swift

class ViewController: UIViewController {

  var resultsViewController: GMSAutocompleteResultsViewController?
  var searchController: UISearchController?
  var resultView: UITextView?

  override func viewDidLoad() {
    super.viewDidLoad()

    resultsViewController = GMSAutocompleteResultsViewController()
    resultsViewController?.delegate = self

    searchController = UISearchController(searchResultsController: resultsViewController)
    searchController?.searchResultsUpdater = resultsViewController

    // Put the search bar in the navigation bar.
    searchController?.searchBar.sizeToFit()
    navigationItem.titleView = searchController?.searchBar

    // When UISearchController presents the results view, present it in
    // this view controller, not one further up the chain.
    definesPresentationContext = true

    // Prevent the navigation bar from being hidden when searching.
    searchController?.hidesNavigationBarDuringPresentation = false
  }
}

// Handle the user's selection.
extension ViewController: GMSAutocompleteResultsViewControllerDelegate {
  func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
                         didAutocompleteWith place: GMSPlace) {
    searchController?.isActive = false
    // Do something with the selected place.
    print("Place name: \(place.name)")
    print("Place address: \(place.formattedAddress)")
    print("Place attributions: \(place.attributions)")
  }

  func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
                         didFailAutocompleteWithError error: Error){
    // TODO: handle the error.
    print("Error: ", error.localizedDescription)
  }

  // Turn the network activity indicator on and off again.
  func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
    UIApplication.shared.isNetworkActivityIndicatorVisible = true
  }

  func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
    UIApplication.shared.isNetworkActivityIndicatorVisible = false
  }
}

Objective-C

- (void)viewDidLoad {
  _resultsViewController = [[GMSAutocompleteResultsViewController alloc] init];
  _resultsViewController.delegate = self;

  _searchController = [[UISearchController alloc]
                       initWithSearchResultsController:_resultsViewController];
  _searchController.searchResultsUpdater = _resultsViewController;

  // Put the search bar in the navigation bar.
  [_searchController.searchBar sizeToFit];
  self.navigationItem.titleView = _searchController.searchBar;

  // When UISearchController presents the results view, present it in
  // this view controller, not one further up the chain.
  self.definesPresentationContext = YES;

  // Prevent the navigation bar from being hidden when searching.
  _searchController.hidesNavigationBarDuringPresentation = NO;
}

// Handle the user's selection.
- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController
  didAutocompleteWithPlace:(GMSPlace *)place {
    _searchController.active = NO;
    // Do something with the selected place.
    NSLog(@"Place name %@", place.name);
    NSLog(@"Place address %@", place.formattedAddress);
    NSLog(@"Place attributions %@", place.attributions.string);
}

- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController
didFailAutocompleteWithError:(NSError *)error {
  [self dismissViewControllerAnimated:YES completion:nil];
  // TODO: handle the error.
  NSLog(@"Error: %@", [error description]);
}

// Turn the network activity indicator on and off again.
- (void)didRequestAutocompletePredictionsForResultsController:
    (GMSAutocompleteResultsViewController *)resultsController {
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)didUpdateAutocompletePredictionsForResultsController:
    (GMSAutocompleteResultsViewController *)resultsController {
  [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

Примечание. Для правильного отображения панели поиска контроллер представлений вашего приложения должен быть включен в UINavigationController.

Добавление панели поиска в верхнюю часть представления

В следующем примере кода показано, как добавить searchBar в верхнюю часть представления.

Swift

import UIKit
import GooglePlaces

class ViewController: UIViewController {

  var resultsViewController: GMSAutocompleteResultsViewController?
  var searchController: UISearchController?
  var resultView: UITextView?

  override func viewDidLoad() {
    super.viewDidLoad()

    resultsViewController = GMSAutocompleteResultsViewController()
    resultsViewController?.delegate = self

    searchController = UISearchController(searchResultsController: resultsViewController)
    searchController?.searchResultsUpdater = resultsViewController

    let subView = UIView(frame: CGRect(x: 0, y: 65.0, width: 350.0, height: 45.0))

    subView.addSubview((searchController?.searchBar)!)
    view.addSubview(subView)
    searchController?.searchBar.sizeToFit()
    searchController?.hidesNavigationBarDuringPresentation = false

    // When UISearchController presents the results view, present it in
    // this view controller, not one further up the chain.
    definesPresentationContext = true
  }
}

// Handle the user's selection.
extension ViewController: GMSAutocompleteResultsViewControllerDelegate {
  func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
                         didAutocompleteWith place: GMSPlace) {
    searchController?.isActive = false
    // Do something with the selected place.
    print("Place name: \(place.name)")
    print("Place address: \(place.formattedAddress)")
    print("Place attributions: \(place.attributions)")
  }

  func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
                         didFailAutocompleteWithError error: Error){
    // TODO: handle the error.
    print("Error: ", error.localizedDescription)
  }

  // Turn the network activity indicator on and off again.
  func didRequestAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
    UIApplication.shared.isNetworkActivityIndicatorVisible = true
  }

  func didUpdateAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
    UIApplication.shared.isNetworkActivityIndicatorVisible = false
  }
}

Objective-C

- (void)viewDidLoad {
    [super viewDidLoad];

    _resultsViewController = [[GMSAutocompleteResultsViewController alloc] init];
    _resultsViewController.delegate = self;

    _searchController = [[UISearchController alloc]
                             initWithSearchResultsController:_resultsViewController];
    _searchController.searchResultsUpdater = _resultsViewController;

    UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 65.0, 250, 50)];

    [subView addSubview:_searchController.searchBar];
    [_searchController.searchBar sizeToFit];
    [self.view addSubview:subView];

    // When UISearchController presents the results view, present it in
    // this view controller, not one further up the chain.
    self.definesPresentationContext = YES;
}

// Handle the user's selection.
- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController
didAutocompleteWithPlace:(GMSPlace *)place {
  [self dismissViewControllerAnimated:YES completion:nil];
  // Do something with the selected place.
  NSLog(@"Place name %@", place.name);
  NSLog(@"Place address %@", place.formattedAddress);
  NSLog(@"Place attributions %@", place.attributions.string);
}

- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController
didFailAutocompleteWithError:(NSError *)error {
  [self dismissViewControllerAnimated:YES completion:nil];
  // TODO: handle the error.
  NSLog(@"Error: %@", [error description]);
}

// Turn the network activity indicator on and off again.
- (void)didRequestAutocompletePredictionsForResultsController:
    (GMSAutocompleteResultsViewController *)resultsController {
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)didUpdateAutocompletePredictionsForResultsController:
    (GMSAutocompleteResultsViewController *)resultsController {
  [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

По умолчанию UISearchController при представлении скрывает панель навигации (это действие можно отключить). В тех случаях, когда панель навигации отображается и непрозрачна, UISearchController не выполняет размещение правильно. Для решения этой проблемы используйте следующий код:

Swift

navigationController?.navigationBar.translucent = false
searchController?.hidesNavigationBarDuringPresentation = false

// This makes the view area include the nav bar even though it is opaque.
// Adjust the view placement down.
self.extendedLayoutIncludesOpaqueBars = true
self.edgesForExtendedLayout = .top

Objective-C

self.navigationController.navigationBar.translucent = NO;
_searchController.hidesNavigationBarDuringPresentation = NO;

// This makes the view area include the nav bar even though it is opaque.
// Adjust the view placement down.
self.extendedLayoutIncludesOpaqueBars = YES;
self.edgesForExtendedLayout = UIRectEdgeTop;

Добавление панели поиска с отображением результатов во всплывающем окне

В следующем примере кода показано, как разместить панель поиска справа от панели навигации и отобразить результаты во всплывающем окне.

Swift

import UIKit
import GooglePlaces

class ViewController: UIViewController {

  var resultsViewController: GMSAutocompleteResultsViewController?
  var searchController: UISearchController?
  var resultView: UITextView?

  override func viewDidLoad() {
    super.viewDidLoad()

    resultsViewController = GMSAutocompleteResultsViewController()
    resultsViewController?.delegate = self

    searchController = UISearchController(searchResultsController: resultsViewController)
    searchController?.searchResultsUpdater = resultsViewController

    // Add the search bar to the right of the nav bar,
    // use a popover to display the results.
    // Set an explicit size as we don't want to use the entire nav bar.
    searchController?.searchBar.frame = (CGRect(x: 0, y: 0, width: 250.0, height: 44.0))
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: (searchController?.searchBar)!)

    // When UISearchController presents the results view, present it in
    // this view controller, not one further up the chain.
    definesPresentationContext = true

    // Keep the navigation bar visible.
    searchController?.hidesNavigationBarDuringPresentation = false
    searchController?.modalPresentationStyle = .popover
  }
}
// Handle the user's selection.
extension ViewController: GMSAutocompleteResultsViewControllerDelegate {
  func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
                         didAutocompleteWith place: GMSPlace) {
    searchController?.isActive = false
    // Do something with the selected place.
    print("Place name: \(place.name)")
    print("Place address: \(place.formattedAddress)")
    print("Place attributions: \(place.attributions)")
  }

  func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
                         didFailAutocompleteWithError error: Error){
    // TODO: handle the error.
    print("Error: ", error.localizedDescription)
  }

  // Turn the network activity indicator on and off again.
  func didRequestAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
    UIApplication.shared.isNetworkActivityIndicatorVisible = true
  }

  func didUpdateAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
    UIApplication.shared.isNetworkActivityIndicatorVisible = false
  }
}

Objective-C

- (void)viewDidLoad {
  [super viewDidLoad];

  _resultsViewController = [[GMSAutocompleteResultsViewController alloc] init];
  _resultsViewController.delegate = self;

  _searchController = [[UISearchController alloc]
                           initWithSearchResultsController:_resultsViewController];
  _searchController.searchResultsUpdater = _resultsViewController;

  // Add the search bar to the right of the nav bar,
  // use a popover to display the results.
  // Set an explicit size as we don't want to use the entire nav bar.
  _searchController.searchBar.frame = CGRectMake(0, 0, 250.0f, 44.0f);
  self.navigationItem.rightBarButtonItem =
  [[UIBarButtonItem alloc] initWithCustomView:_searchController.searchBar];

  // When UISearchController presents the results view, present it in
  // this view controller, not one further up the chain.
  self.definesPresentationContext = YES;

  // Keep the navigation bar visible.
  _searchController.hidesNavigationBarDuringPresentation = NO;

  _searchController.modalPresentationStyle = UIModalPresentationPopover;
}

// Handle the user's selection.
- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController
didAutocompleteWithPlace:(GMSPlace *)place {
  [self dismissViewControllerAnimated:YES completion:nil];
  NSLog(@"Place name %@", place.name);
  NSLog(@"Place address %@", place.formattedAddress);
  NSLog(@"Place attributions %@", place.attributions.string);
}

- (void)resultsController:(GMSAutocompleteResultsViewController *)resultsController
didFailAutocompleteWithError:(NSError *)error {
  [self dismissViewControllerAnimated:YES completion:nil];
  // TODO: handle the error.
  NSLog(@"Error: %@", [error description]);
}

// Turn the network activity indicator on and off again.
- (void)didRequestAutocompletePredictionsForResultsController:
    (GMSAutocompleteResultsViewController *)resultsController {
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)didUpdateAutocompletePredictionsForResultsController:
    (GMSAutocompleteResultsViewController *)resultsController {
  [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

Примечание. Для правильного отображения панели поиска контроллер представлений вашего приложения должен быть включен в UINavigationController.

Использование источника данных таблицы

Если ваше приложение должно поддерживать iOS 7, используйте класс GMSAutocompleteTableDataSource, чтобы задействовать представление таблицы UISearchDisplayController.

Чтобы использовать GMSAutocompleteTableDataSource для отображения контроллера поиска, выполните следующие действия.

  1. Реализуйте протоколы GMSAutocompleteTableDataSourceDelegate и UISearchDisplayDelegate в родительском контроллере представлений.
  2. Создайте экземпляр GMSAutocompleteTableDataSource и назначьте родительский контроллер представлений в качестве делегированного свойства.
  3. Создайте экземпляр UISearchDisplayController.
  4. Добавьте searchBar для UISearchController в пользовательский интерфейс своего приложения.
  5. Обработайте элемент, выбранный пользователем, в делегированном методе didAutocompleteWithPlace.
  6. Деактивируйте контроллер в делегированных методах didAutocompleteWithPlace, didFailAutocompleteWithError и wasCancelled.

В следующем примере кода показано, как использовать класс GMSAutocompleteTableDataSource, чтобы задействовать представление таблицы UISearchDisplayController.

Swift

import UIKit
import GooglePlaces

class ViewController: UIViewController, UISearchDisplayDelegate {

  var searchBar: UISearchBar?
  var tableDataSource: GMSAutocompleteTableDataSource?
  var searchDisplayController: UISearchDisplayController?

  override func viewDidLoad() {
    super.viewDidLoad()

    searchBar = UISearchBar(CGRect(x: 0, y: 0, width: 250.0, height: 44.0))

    tableDataSource = GMSAutocompleteTableDataSource()
    tableDataSource?.delegate = self

    searchDisplayController = UISearchDisplayController(searchBar: searchBar!, contentsController: self)
    searchDisplayController?.searchResultsDataSource = tableDataSource
    searchDisplayController?.searchResultsDelegate = tableDataSource
    searchDisplayController?.delegate = self

    view.addSubview(searchBar!)
  }

  func didUpdateAutocompletePredictionsForTableDataSource(tableDataSource: GMSAutocompleteTableDataSource) {
    // Turn the network activity indicator off.
    UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    // Reload table data.
    searchDisplayController?.searchResultsTableView.reloadData()
  }

  func didRequestAutocompletePredictionsForTableDataSource(tableDataSource: GMSAutocompleteTableDataSource) {
    // Turn the network activity indicator on.
    UIApplication.sharedApplication().networkActivityIndicatorVisible = true
    // Reload table data.
    searchDisplayController?.searchResultsTableView.reloadData()
  }

}

extension ViewController: GMSAutocompleteTableDataSourceDelegate {
  func tableDataSource(tableDataSource: GMSAutocompleteTableDataSource, didAutocompleteWithPlace place: GMSPlace) {
    searchDisplayController?.active = false
    // Do something with the selected place.
    print("Place name: \(place.name)")
    print("Place address: \(place.formattedAddress)")
    print("Place attributions: \(place.attributions)")
  }

  func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String?) -> Bool {
    tableDataSource?.sourceTextHasChanged(searchString)
    return false
  }

  func tableDataSource(tableDataSource: GMSAutocompleteTableDataSource, didFailAutocompleteWithError error: NSError) {
    // TODO: Handle the error.
    print("Error: \(error.description)")
  }

  func tableDataSource(tableDataSource: GMSAutocompleteTableDataSource, didSelectPrediction prediction: GMSAutocompletePrediction) -> Bool {
    return true
  }
}

Objective-C

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

  _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 20, 250.0f, 44.0f)];

  _tableDataSource = [[GMSAutocompleteTableDataSource alloc] init];
  _tableDataSource.delegate = self;

  _searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar
                                                               contentsController:self];
  _searchDisplayController.searchResultsDataSource = _tableDataSource;
  _searchDisplayController.searchResultsDelegate = _tableDataSource;
  _searchDisplayController.delegate = self;

  [self.view addSubview:_searchBar];
}

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller
shouldReloadTableForSearchString:(NSString *)searchString
{
  [_tableDataSource sourceTextHasChanged:searchString];
  return NO;
}

// Handle the user's selection.
- (void)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource
  didAutocompleteWithPlace:(GMSPlace *)place {
  [_searchDisplayController setActive:NO animated:YES];
  // Do something with the selected place.
  NSLog(@"Place name %@", place.name);
  NSLog(@"Place address %@", place.formattedAddress);
  NSLog(@"Place attributions %@", place.attributions.string);
}

- (void)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource
  didFailAutocompleteWithError:(NSError *)error {
  [_searchDisplayController setActive:NO animated:YES];
  // TODO: handle the error.
  NSLog(@"Error: %@", [error description]);
}

- (void)didUpdateAutocompletePredictionsForTableDataSource:
  (GMSAutocompleteTableDataSource *)tableDataSource {
  // Turn the network activity indicator off.
  [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  // Reload table data.
  [_searchDisplayController.searchResultsTableView reloadData];
}

- (void)didRequestAutocompletePredictionsForTableDataSource:
  (GMSAutocompleteTableDataSource *)tableDataSource {
  // Turn the network activity indicator on.
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  // Reload table data.
  [_searchDisplayController.searchResultsTableView reloadData];
}

Примечание. UISearchDisplayController API не поддерживает концепцию асинхронного обновления данных, поэтому обновление таблицы необходимо выполнить принудительно путем перезагрузки данных таблицы в методах didUpdateAutocompletePredictions и didRequestAutocompletePredictions протокола GMSAutoCompleteResultsDelegate.

Настройка цвета текста и фона

В элементе управления пользовательского интерфейса службы подсказки мест можно настроить цвет текста и фона, чтобы обеспечить более точное визуальное соответствие виджета вашему приложению. Для настройки цвета элемента управления пользовательского интерфейса существуют два способа:

  • Использование собственного протокола UIAppearance системы iOS для общего изменения стиля элементов пользовательского интерфейса там, где это возможно. Эти изменения затронут многие, но не все элементы управления пользовательского интерфейса.
  • Использование методов пакета SDK в классах виджета для настройки свойств, которые не поддерживаются протоколом UIAppearance.

Как правило, ваше приложение будет использовать некую комбинацию возможностей протокола UIAppearance и методов пакета SDK. На приведенной ниже диаграмме показано, для каких элементов можно изменить стиль.

Цвета элементов управления пользовательского интерфейса службы подсказки мест

В следующей таблице перечислены все элементы пользовательского интерфейса и указан способ изменения стиля для каждого (протокол UIAppearance или метод пакета SDK).

Элемент интерфейса Метод Способ изменения стиля
Тонирование панели навигации (фон) Протокол UIAppearance Вызвать setBarTintColor для прокси-объекта UINavigationBar.
Цвет тонирования панели навигации (для текстового курсора панели поиска и кнопки "Cancel") Протокол UIAppearance Вызвать setTintColor для прокси-объектаUINavigationBar.
Цвет текста панели поиска Протокол UIAppearance Установить NSForegroundColorAttributeName в searchBarTextAttributes.
Цвет тонирования панели поиска Н/Д Панель поиска полупрозрачна и будет отображаться как затененный вариант панели навигации.
Цвет текста заполнителя панели поиска (текст поиска по умолчанию) Протокол UIAppearance Установить NSForegroundColorAttributeName в placeholderAttributes.
Основной текст (также применяется к тексту уведомлений об ошибках и сообщений) Метод пакета SDK Вызвать primaryTextColor.
Выделение основного текста Метод пакета SDK Вызвать primaryTextHighlightColor.
Второстепенный текст Метод пакета SDK Вызвать secondaryTextColor.
Текст уведомлений об ошибках и сообщений Метод пакета SDK Вызвать primaryTextColor.
Фон ячейки таблицы Метод пакета SDK Вызвать tableCellBackgroundColor.
Цвет разделителя ячейки таблицы Метод пакета SDK Вызвать tableCellSeparatorColor.
Кнопка "Try Again" Метод пакета SDK Вызвать tintColor.
Индикатор операции (индикатор выполнения) Протокол UIAppearance Вызвать setColor для прокси-объектаUIActivityIndicatorView.
Логотип "Powered by Google", изображение грустного облака Н/Д Белый или серый вариант выбирается автоматически в зависимости от контрастности фона.
Значки лупы и удаления текста в текстовом поле панели поиска Н/Д Для изменения стиля замените изображения по умолчанию на изображения необходимого цвета.

Примечание. Эти свойства влияют только на параметры цвета элементов пользовательского интерфейса. Изменение размера элементов пользовательского интерфейса или шрифта, используемого в ячейках таблицы, не поддерживается.

Использование протокола UIAppearance

Вы можете использовать протокол UIAppearance, чтобы получить прокси-объект внешнего вида для определенного элемента пользовательского интерфейса, который затем можно использовать для настройки цвета элемента пользовательского интерфейса. После внесения изменения оно применяется ко всем экземплярам элемента пользовательского интерфейса. Например, в следующем примере показаны изменения цвета всего текста классов UITextField на зеленый, когда они содержатся в UISearchBar:

[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil]
    setDefaultTextAttributes:@{NSForegroundColorAttributeName:[UIColor greenColor]}];

Дополнительную информацию об установке значений цвета см. в документе Ссылка класса UIColor.

В следующих фрагментах кода представлены все команды Proxy, которые вам необходимы, чтобы полностью изменить стиль в элементе управления пользовательского интерфейса службы подсказки мест. Добавьте этот код в метод didFinishLaunchingWithOptions в Appdelegate.m:

// Define some colors.
UIColor *darkGray = [UIColor darkGrayColor];
UIColor *lightGray = [UIColor lightGrayColor];

// Navigation bar background.
[[UINavigationBar appearance] setBarTintColor:darkGray];
[[UINavigationBar appearance] setTintColor:lightGray];

// Color of typed text in the search bar.
NSDictionary *searchBarTextAttributes = @{
                                          NSForegroundColorAttributeName: lightGray,
                                          NSFontAttributeName : [UIFont systemFontOfSize:[UIFont systemFontSize]]
                                          };
[UITextField appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]]
    .defaultTextAttributes = searchBarTextAttributes;

// Color of the placeholder text in the search bar prior to text entry.
NSDictionary *placeholderAttributes = @{
                                        NSForegroundColorAttributeName: lightGray,
                                        NSFontAttributeName : [UIFont systemFontOfSize:[UIFont systemFontSize]]
                                        };

// Color of the default search text.
// NOTE: In a production scenario, "Search" would be a localized string.
NSAttributedString *attributedPlaceholder =
[[NSAttributedString alloc] initWithString:@"Search"
                                attributes:placeholderAttributes];
[UITextField appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]]
    .attributedPlaceholder = attributedPlaceholder;

// Color of the in-progress spinner.
[[UIActivityIndicatorView appearance] setColor:lightGray];

// To style the two image icons in the search bar (the magnifying glass
// icon and the 'clear text' icon), replace them with different images.
[[UISearchBar appearance] setImage:[UIImage imageNamed:@"custom_clear_x_high"]
                  forSearchBarIcon:UISearchBarIconClear
                            state:UIControlStateHighlighted];
[[UISearchBar appearance] setImage:[UIImage imageNamed:@"custom_clear_x"]
                  forSearchBarIcon:UISearchBarIconClear
                            state:UIControlStateNormal];
[[UISearchBar appearance] setImage:[UIImage imageNamed:@"custom_search"]
                    forSearchBarIcon:UISearchBarIconSearch
                            state:UIControlStateNormal];

// Color of selected table cells.
UIView *selectedBackgroundView = [[UIView alloc] init];
selectedBackgroundView.backgroundColor = [UIColor lightGrayColor];
[UITableViewCell appearanceWhenContainedIn:[GMSAutocompleteViewController class], nil]
    .selectedBackgroundView = selectedBackgroundView;

Настройка свойств стиля элемента управления пользовательского интерфейса

У набора элементов управления пользовательского интерфейса есть свойства, которые не управляются протоколом UIAppearance, поэтому их необходимо настроить напрямую. В следующем примере показано, как установить цвета переднего и заднего планов и применить их к экземпляру элемента управления пользовательского интерфейса с именем acController. Добавьте этот код в метод onLaunchClicked в ViewController.m:

UIColor <em>darkGray = [UIColor darkGrayColor];
UIColor </em>lightGray = [UIColor lightGrayColor];</p>
<p>acController.secondaryTextColor = [UIColor colorWithWhite:1.0f alpha:0.5f];
acController.primaryTextColor = lightGray;
acController.primaryTextHighlightColor = [UIColor grayColor];
acController.tableCellBackgroundColor = darkGray;
acController.tableCellSeparatorColor = lightGray;
acController.tintColor = lightGray;

Примечание. Для установки этих значений в GMSAutocompleteViewController и GMSAutocompleteResultsViewController можно использовать инструмент Interface Builder.

Получение подсказок мест программным способом

Вы можете создать собственный пользовательский интерфейс поиска в дополнение к тому, который предоставляется виджетом службы подсказки мест. Для этого ваше приложение должно получать подсказки мест программным способом. Чтобы получить список названий предполагаемых мест или адресов, приложение может:

Вызов GMSPlacesClient

Чтобы получить список предполагаемых мест или адресов, вызовите метод GMSPlacesClient autocompleteQuery:bounds:filter:callback:, передав в него следующие параметры.

  • Строку autocompleteQuery с текстом, введенным пользователем.
  • Объект GMSCoordinateBounds, выделяющий предпочтительные результаты поиска для определенной области, которая задается значениями широты и долготы.
  • Объект GMSAutocompleteFilter, ограничивающий результаты поиска определенным типом мест. Для получения всех типов мест следует указать нулевое значение. Фильтр поддерживает следующие типы мест.

    • kGMSPlacesAutocompleteTypeFilterNoFilter – пустой фильтр; возвращаются все результаты.
    • kGMSPlacesAutocompleteTypeFilterGeocode – возвращает в результатах поиска только геокоды, а не организации. Этот запрос следует использовать, когда существуют места с похожими адресами и требуется уточнить результаты.
    • kGMSPlacesAutocompleteTypeFilterAddress – ограничивает результаты подсказки мест точными адресами. Этот тип следует использовать, когда известно, что пользователь выполняет поиск полностью указанного адреса.
    • kGMSPlacesAutocompleteTypeFilterEstablishment – возвращает только организации.
    • kGMSPlacesAutocompleteTypeFilterRegion – возвращает только адреса, которые соответствуют одному из следующих типов:

      • locality
      • sublocality
      • postal_code
      • country
      • administrative_area_level_1
      • administrative_area_level_2
    • kGMSPlacesAutocompleteTypeFilterCity – возвращает только результаты, соответствующие locality или administrative_area_level_3.

Дополнительную информацию см. в описании типов мест.

  • Метод обратного вызова для обработки полученных подсказок.

В примере ниже показан упрощенный вызов метода autocompleteQuery:bounds:filter:callback:.

Swift

func placeAutocomplete() {
  let filter = GMSAutocompleteFilter()
  filter.type = .establishment
  placesClient.autocompleteQuery("Sydney Oper", bounds: nil, filter: filter, callback: {(results, error) -> Void in
    if let error = error {
      print("Autocomplete error \(error)")
      return
    }
    if let results = results {
      for result in results {
        print("Result \(result.attributedFullText) with placeID \(result.placeID)")
      }
    }
  })
}

Objective-C

- (void)placeAutocomplete {

GMSAutocompleteFilter *filter = [[GMSAutocompleteFilter alloc] init];
filter.type = kGMSPlacesAutocompleteTypeFilterEstablishment;

[_placesClient autocompleteQuery:@"Sydney Oper"
                          bounds:nil
                          filter:filter
                        callback:^(NSArray *results, NSError *error) {
                          if (error != nil) {
                            NSLog(@"Autocomplete error %@", [error localizedDescription]);
                            return;
                          }

                          for (GMSAutocompletePrediction* result in results) {
                            NSLog(@"Result '%@' with placeID %@", result.attributedFullText.string, result.placeID);
                          }
                        }];
}

API вызывает указанный метод обратного вызова, передавая в него массив объектов GMSAutocompletePrediction.

Каждый объект GMSAutocompletePrediction содержит следующую информацию.

  • attributedFullText – полный текст подсказки в виде строки NSAttributedString. Например, "Мариинский театр, Санкт-Петербург, Россия". Каждый фрагмент текста, соответствующий пользовательскому вводу, имеет атрибут kGMSAutocompleteMatchAttribute. Этот атрибут можно использовать для выделения соответствующих слов в запросе пользователя, например, как показано ниже.
  • placeID – идентификатор подсказанного места. Идентификатор места – это текстовый идентификатор, который однозначно указывает на какое-то место. Подробные сведения об идентификаторах мест см. в обзоре идентификаторов мест.

В следующем примере кода показано, как выделить полужирным шрифтом части полученного текста, соответствующие запросу пользователя, применяя атрибут enumerateAttribute:

Swift

let regularFont = UIFont.systemFont(ofSize: UIFont.labelFontSize)
let boldFont = UIFont.boldSystemFont(ofSize: UIFont.labelFontSize)

let bolded = prediction.attributedFullText.mutableCopy() as! NSMutableAttributedString
bolded.enumerateAttribute(kGMSAutocompleteMatchAttribute, in: NSMakeRange(0, bolded.length), options: []) {
  (value, range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
    let font = (value == nil) ? regularFont : boldFont
    bolded.addAttribute(NSFontAttributeName, value: font, range: range)
}

label.attributedText = bolded
    

Objective-C

UIFont *regularFont = [UIFont systemFontOfSize:[UIFont labelFontSize]];
UIFont *boldFont = [UIFont boldSystemFontOfSize:[UIFont labelFontSize]];

NSMutableAttributedString *bolded = [prediction.attributedFullText mutableCopy];
[bolded enumerateAttribute:kGMSAutocompleteMatchAttribute
                   inRange:NSMakeRange(0, bolded.length)
                   options:0
                usingBlock:^(id value, NSRange range, BOOL *stop) {
                  UIFont *font = (value == nil) ? regularFont : boldFont;
                  [bolded addAttribute:NSFontAttributeName value:font range:range];
                }];

label.attributedText = bolded;
    

Использование инструмента выбора

Если вы хотите заново создать собственный элемент управления службы подсказки мест, можете использовать GMSAutocompleteFetcher, который инкапсулирует метод autocompleteQuery для GMSPlacesClient. Инструмент выбора обрабатывает запросы и возвращает только те результаты, которые соответствуют тексту поиска, введенному последним. Он не предоставляет элементы пользовательского интерфейса.

Чтобы реализовать GMSAutocompleteFetcher, выполните следующие действия.

  1. Реализуйте протокол GMSAutocompleteFetcherDelegate.
  2. Создайте объект GMSAutocompleteFetcher.
  3. Вызовите sourceTextHasChanged для инструмента выбора в виде пользовательских типов.
  4. Выполните обработку подсказок и ошибок с использованием методов протокола didAutcompleteWithPredictions и didFailAutocompleteWithError.

В следующем примере кода показано, как использовать инструмент выбора для обработки текста, вводимого пользователем, и отобразить совпадения мест в текстовом представлении. Функциональные возможности для выбора мест были опущены. FetcherSampleViewController наследуется из UIViewController в FetcherSampleViewController.h.

Swift

import UIKit
import GooglePlaces

class ViewController: UIViewController {

  var textField: UITextField?
  var resultText: UITextView?
  var fetcher: GMSAutocompleteFetcher?

  override func viewDidLoad() {
    super.viewDidLoad()

    view.backgroundColor = .white
    edgesForExtendedLayout = []

    // Set bounds to inner-west Sydney Australia.
    let neBoundsCorner = CLLocationCoordinate2D(latitude: -33.843366,
                                                longitude: 151.134002)
    let swBoundsCorner = CLLocationCoordinate2D(latitude: -33.875725,
                                                longitude: 151.200349)
    let bounds = GMSCoordinateBounds(coordinate: neBoundsCorner,
                                     coordinate: swBoundsCorner)

    // Set up the autocomplete filter.
    let filter = GMSAutocompleteFilter()
    filter.type = .establishment

    // Create the fetcher.
    fetcher = GMSAutocompleteFetcher(bounds: bounds, filter: filter)
    fetcher?.delegate = self

    textField = UITextField(frame: CGRect(x: 5.0, y: 10.0,
                                          width: view.bounds.size.width - 5.0,
                                          height: 44.0))
    textField?.autoresizingMask = .flexibleWidth
    textField?.addTarget(self, action: #selector(textFieldDidChange(textField:)),
                         for: .editingChanged)

    resultText = UITextView(frame: CGRect(x: 0, y: 45.0,
                                          width: view.bounds.size.width,
                                          height: view.bounds.size.height - 45.0))
    resultText?.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
    resultText?.text = "No Results"
    resultText?.isEditable = false

    view.addSubview(textField!)
    view.addSubview(resultText!)
  }

  func textFieldDidChange(textField: UITextField) {
    fetcher?.sourceTextHasChanged(textField.text!)
  }

}

extension ViewController: GMSAutocompleteFetcherDelegate {
  func didAutocomplete(with predictions: [GMSAutocompletePrediction]) {
    let resultsStr = NSMutableString()
    for prediction in predictions {
      resultsStr.appendFormat("%@\n", prediction.attributedPrimaryText)
    }

    resultText?.text = resultsStr as String
  }

  func didFailAutocompleteWithError(_ error: Error) {
    resultText?.text = error.localizedDescription
  }

}

Objective-C

#import "FetcherSampleViewController.h"
#import <GooglePlaces/GooglePlaces.h>

@interface FetcherSampleViewController () <GMSAutocompleteFetcherDelegate>

@end

@implementation FetcherSampleViewController {
  UITextField *_textField;
  UITextView *_resultText;
  GMSAutocompleteFetcher* _fetcher;
}

- (void)viewDidLoad {
  [super viewDidLoad];

  self.view.backgroundColor = [UIColor whiteColor];
  self.edgesForExtendedLayout = UIRectEdgeNone;

  // Set bounds to inner-west Sydney Australia.
  CLLocationCoordinate2D neBoundsCorner = CLLocationCoordinate2DMake(-33.843366, 151.134002);
  CLLocationCoordinate2D swBoundsCorner = CLLocationCoordinate2DMake(-33.875725, 151.200349);
  GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:neBoundsCorner
                                                                     coordinate:swBoundsCorner];

  // Set up the autocomplete filter.
  GMSAutocompleteFilter *filter = [[GMSAutocompleteFilter alloc] init];
  filter.type = kGMSPlacesAutocompleteTypeFilterEstablishment;

  // Create the fetcher.
  _fetcher = [[GMSAutocompleteFetcher alloc] initWithBounds:bounds
                                                     filter:filter];
  _fetcher.delegate = self;

  // Set up the UITextField and UITextView.
  _textField = [[UITextField alloc] initWithFrame:CGRectMake(5.0f,
                                                             0,
                                                             self.view.bounds.size.width - 5.0f,
                                                             44.0f)];
  _textField.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  [_textField addTarget:self
                 action:@selector(textFieldDidChange:)
       forControlEvents:UIControlEventEditingChanged];
  _resultText =[[UITextView alloc] initWithFrame:CGRectMake(0,
                                                            45.0f,
                                                            self.view.bounds.size.width,
                                                            self.view.bounds.size.height - 45.0f)];
  _resultText.backgroundColor = [UIColor colorWithWhite:0.95f alpha:1.0f];
  _resultText.text = @"No Results";
  _resultText.editable = NO;
  [self.view addSubview:_textField];
  [self.view addSubview:_resultText];
}

- (void)textFieldDidChange:(UITextField *)textField {
  NSLog(@"%@", textField.text);
  [_fetcher sourceTextHasChanged:textField.text];
}

#pragma mark - GMSAutocompleteFetcherDelegate
- (void)didAutocompleteWithPredictions:(NSArray *)predictions {
  NSMutableString *resultsStr = [NSMutableString string];
  for (GMSAutocompletePrediction *prediction in predictions) {
      [resultsStr appendFormat:@"%@\n", [prediction.attributedPrimaryText string]];
  }
  _resultText.text = resultsStr;
}

- (void)didFailAutocompleteWithError:(NSError *)error {
  _resultText.text = [NSString stringWithFormat:@"%@", error.localizedDescription];
}

@end

Настройка параметра GMSCoordinateBounds подсказки мест

Вы можете указать параметр GMSCoordinateBounds для использования с полученными подсказками. Например, если в вашем контроллере представлений уже есть карта Google, вы можете использовать границы текущей области просмотра для определения предпочтительных результатов подсказки мест.

Swift

func placeAutocomplete() {
  let visibleRegion = mapView.projection.visibleRegion()
  let bounds = GMSCoordinateBounds(coordinate: visibleRegion.farLeft, coordinate: visibleRegion.nearRight)

  let filter = GMSAutocompleteFilter()
  filter.type = .establishment
  placesClient.autocompleteQuery("Sydney Oper", bounds: bounds, filter: filter, callback: {
    (results, error) -> Void in
    guard error == nil else {
      print("Autocomplete error \(error)")
      return
    }
    if let results = results {
      for result in results {
        print("Result \(result.attributedFullText) with placeID \(result.placeID)")
      }
    }
  })
}

Objective-C

- (void)placeAutocomplete {

GMSVisibleRegion visibleRegion = self.mapView.projection.visibleRegion;
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:visibleRegion.farLeft
                                                                   coordinate:visibleRegion.nearRight];
GMSAutocompleteFilter *filter = [[GMSAutocompleteFilter alloc] init];
filter.type = kGMSPlacesAutocompleteTypeFilterEstablishment;

[_placesClient autocompleteQuery:@"Sydney Oper"
                          bounds:bounds
                          filter:filter
                        callback:^(NSArray *results, NSError *error) {
                          if (error != nil) {
                            NSLog(@"Autocomplete error %@", [error localizedDescription]);
                            return;
                          }

                          for (GMSAutocompletePrediction* result in results) {
                            NSLog(@"Result '%@' with placeID %@", result.attributedFullText.string, result.placeID);
                          }
                        }];
}

Ограничения на использование

Указание авторства в приложении

  • Если в вашем приложении служба подсказки мест используется программным способом, пользовательский интерфейс должен либо содержать информацию об авторстве "Powered by Google", либо отображаться на карте с логотипом Google.
  • Если в вашем приложении используется элемент управления пользовательского интерфейса службы подсказки мест, дополнительные действия не требуются (необходимая информация об авторстве отображается по умолчанию).
  • При получении и отображении дополнительных сведений о месте после выполнения поиска места по идентификатору также необходимо показывать пользователям информацию об авторстве третьих сторон.

Дополнительную информацию см. в документации по указанию авторства.

Управление индикатором сетевой активности

Для управления индикатором сетевой активности в строке состояния приложения необходимо реализовать соответствующие дополнительные делегированные методы для используемого класса подсказки мест, а также самостоятельно включать и отключать этот сетевой индикатор.

  • Для GMSAutocompleteViewController необходимо реализовать делегированные методы didRequestAutocompletePredictions: и didUpdateAutocompletePredictions:.
  • Для GMSAutocompleteResultsViewController необходимо реализовать делегированные методы didRequestAutocompletePredictionsForResultsController: и didUpdateAutocompletePredictionsForResultsController:.
  • Для GMSAutocompleteTableDataSource необходимо реализовать делегированные методы didRequestAutocompletePredictionsForTableDataSource: и didUpdateAutocompletePredictionsForTableDataSource:.

После реализации этих методов и установки для [UIApplication sharedApplication].networkActivityIndicatorVisible значений соответственно YES и NO, строка состояния будет правильно соответствовать пользовательскому интерфейсу службы подсказки мест.

Устранение неполадок

Несмотря на то, что во время работы приложения могут иметь место самые разные проблемы, большинство из них, как правило, связанно либо с ошибками конфигурации (например, использованием неверного ключа API или его неправильной настройкой), либо с ошибками квоты (превышением установленной квоты вашим приложением). Дополнительную информацию о квотах см. в документации по ограничениям на использование.

Ошибки, которые возникают при использовании элементов управления службы подсказки мест, возвращаются в методе didFailAutocompleteWithError() разных делегированных протоколов. Для свойства code предоставленного объекта NSError установлено одно из значений нумерации GMSPlacesErrorCode.

Оставить отзыв о...

Текущей странице
location_on
Google Places API for iOS