使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

在地图上选择当前地点并显示详细信息

本教程介绍了如何构建 iOS 应用来执行以下操作:

  • 获取当前设备位置。
  • 获取设备可能所处的位置列表。
  • 提示用户输入最佳地点匹配项。
  • 在地图上显示标记。

请按照本教程中的说明操作,使用 Places SDK for iOSMaps SDK for iOSApple Core Location 框架构建 iOS 应用。

获取代码

从 GitHub 克隆或下载 Google Maps SDK for iOS

设置开发项目

请按照以下步骤安装 Places SDK for iOS 和 Maps SDK for iOS:

  1. 下载并安装 Xcode 13.0 版或更高版本。
  2. 如果您尚未安装 CocoaPods,请在 macOS 上从终端运行以下命令进行安装:
    sudo gem install cocoapods
  3. 在保存示例代码库的目录中(获取代码),导航到 tutorials/current-place-on-map 目录。
  4. 运行 pod install 命令。此操作会安装 Podfile 中指定的 API 及其可能具有的任何依赖项。
  5. 打开(双击)项目的 current-place-on-map.xcworkspace 以在 Xcode 中将其打开。您必须使用 .xcworkspace 文件打开项目。

如需了解详细的安装说明,请参阅使用入门(地图)使用入门(地点)

启用必要的 API 并获取 API 密钥

要完成本教程,您需要一个已获得 Maps SDK for iOSPlaces API 使用授权的 Google API 密钥。

  1. 请按照 Google Maps Platform 使用入门中的说明设置结算帐号,并为这两个项目启用项目。
  2. 按照获取 API 密钥中的说明,为您之前设置的开发项目创建 API 密钥。

向您的应用添加 API 密钥

Swift

按照以下方法向 AppDelegate.swift 添加 API 密钥:

  1. 请注意,以下 import 语句已添加到文件中:
    import GooglePlaces
    import GoogleMaps
  2. application(_:didFinishLaunchingWithOptions:) 方法中修改以下代码行,将 YOUR_API_KEY 替换为您的 API 密钥:
    GMSPlacesClient.provideAPIKey("YOUR_API_KEY")
    GMSServices.provideAPIKey("YOUR_API_KEY")

Objective-C

按照以下方法向 AppDelegate.m 添加 API 密钥:

  1. 请注意,以下 import 语句已添加到文件中:
    @import GooglePlaces;
    @import GoogleMaps;
  2. application(_:didFinishLaunchingWithOptions:) 方法中修改以下代码行,将 YOUR_API_KEY 替换为您的 API 密钥:
    [GMSPlacesClient provideAPIKey: @"YOUR_API_KEY"]
    [GMSServices provideAPIKey: @"YOUR_API_KEY"]

构建和运行您的应用

  1. 将 iOS 设备连接到计算机,或者从 Xcode 架构弹出式菜单中选择模拟器
  2. 如果您使用的是设备,请确保已启用位置信息服务。 如果您正在使用模拟器,请从 Features 菜单中选择一个位置。
  3. 在 Xcode 中,点击 Product/Run 菜单选项(或 play 按钮图标)。

Xcode 构建应用,然后在设备或模拟器上运行该应用。

您将看到一张地图,该地图会以您当前所在位置为中心显示若干标记,与本页上的图像类似。

问题排查:

  • 如果您没有看到地图,请检查您是否已获取 API 密钥,以及是否已将其添加到应用,如上文所述。查看 Xcode 的调试控制台,查看与 API 密钥有关的错误消息。
  • 如果您已按 iOS 软件包标识符限制了 API 密钥,请修改密钥以为应用添加软件包标识符:com.google.examples.current-place-on-map
  • 如果位置服务的权限请求遭拒,地图将无法正确显示。
    • 如果您使用的是设备,请转到设置/常规/隐私/位置信息服务,然后重新启用位置信息服务。
    • 如果您使用的是模拟器,请转到模拟器/重置内容和设置...
    下次运行该应用时,请务必接受位置信息服务提示。
  • 确保 WLAN 或 GPS 连接质量良好。
  • 如果应用启动后未显示地图,请确保您已为项目更新了 Info.plist,并提供了适当的位置权限。如需详细了解权限处理,请参阅下文中的在应用中请求位置权限指南。
  • 使用 Xcode 调试工具查看日志并调试应用。

了解代码

本部分教程介绍了当前地图位置应用中最重要的部分,以帮助您了解如何构建类似的应用。

current-place-on-map 应用具有两个视图控制器:一个用于显示地图,显示用户当前选择的地点,另一个用于向用户显示可供选择的地点列表。请注意,每个视图控制器都有相同的变量可用于跟踪可能的地点列表 (likelyPlaces),以及用于指示用户的选择 (selectedPlace)。视图之间的导航是通过使用 segues 完成的。

请求位置权限

您的应用必须提示用户同意使用位置信息服务。为此,请在应用的 Info.plist 文件中添加 NSLocationAlwaysUsageDescription 键,并将每个键的值设为一个字符串,用于描述应用打算如何使用位置数据。

设置营业地点管理器

使用 CLLocationManager 可查找设备的当前位置并请求定期更新(当设备移至新位置时)。本教程提供了获取设备位置信息所需的代码。如需了解详情,请参阅 Apple 开发者文档中的获取用户位置指南。

  1. 在类级别声明位置管理器、当前位置、地图视图、地点客户端和默认缩放级别。
  2. Swift

    var locationManager: CLLocationManager!
    var currentLocation: CLLocation?
    var mapView: GMSMapView!
    var placesClient: GMSPlacesClient!
    var preciseLocationZoomLevel: Float = 15.0
    var approximateLocationZoomLevel: Float = 10.0
          

    Objective-C

    CLLocationManager *locationManager;
    CLLocation * _Nullable currentLocation;
    GMSMapView *mapView;
    GMSPlacesClient *placesClient;
    float preciseLocationZoomLevel;
    float approximateLocationZoomLevel;
          
  3. 初始化 viewDidLoad() 中的营业地点管理器和 GMSPlacesClient
  4. Swift

    // Initialize the location manager.
    locationManager = CLLocationManager()
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.requestWhenInUseAuthorization()
    locationManager.distanceFilter = 50
    locationManager.startUpdatingLocation()
    locationManager.delegate = self
    
    placesClient = GMSPlacesClient.shared()
          

    Objective-C

    // Initialize the location manager.
    locationManager = [[CLLocationManager alloc] init];
    locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    [locationManager requestWhenInUseAuthorization];
    locationManager.distanceFilter = 50;
    [locationManager startUpdatingLocation];
    locationManager.delegate = self;
    
    placesClient = [GMSPlacesClient sharedClient];
          
  5. 声明变量以存储可能的地点列表以及用户的所选地点。
  6. Swift

    // An array to hold the list of likely places.
    var likelyPlaces: [GMSPlace] = []
    
    // The currently selected place.
    var selectedPlace: GMSPlace?
          

    Objective-C

    // An array to hold the list of likely places.
    NSMutableArray<GMSPlace *> *likelyPlaces;
    
    // The currently selected place.
    GMSPlace * _Nullable selectedPlace;
          
  7. 添加扩展,以使用扩展子句处理营业地点管理器的事件。
  8. Swift

    // Delegates to handle events for the location manager.
    extension MapViewController: CLLocationManagerDelegate {
    
      // Handle incoming location events.
      func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location: CLLocation = locations.last!
        print("Location: \(location)")
    
        let zoomLevel = locationManager.accuracyAuthorization == .fullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel
        let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
                                              longitude: location.coordinate.longitude,
                                              zoom: zoomLevel)
    
        if mapView.isHidden {
          mapView.isHidden = false
          mapView.camera = camera
        } else {
          mapView.animate(to: camera)
        }
    
        listLikelyPlaces()
      }
    
      // Handle authorization for the location manager.
      func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        // Check accuracy authorization
        let accuracy = manager.accuracyAuthorization
        switch accuracy {
        case .fullAccuracy:
            print("Location accuracy is precise.")
        case .reducedAccuracy:
            print("Location accuracy is not precise.")
        @unknown default:
          fatalError()
        }
    
        // Handle authorization status
        switch status {
        case .restricted:
          print("Location access was restricted.")
        case .denied:
          print("User denied access to location.")
          // Display the map using the default location.
          mapView.isHidden = false
        case .notDetermined:
          print("Location status not determined.")
        case .authorizedAlways: fallthrough
        case .authorizedWhenInUse:
          print("Location status is OK.")
        @unknown default:
          fatalError()
        }
      }
    
      // Handle location manager errors.
      func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        locationManager.stopUpdatingLocation()
        print("Error: \(error)")
      }
    }
          

    Objective-C

    // Delegates to handle events for the location manager.
    #pragma mark - CLLocationManagerDelegate
    
    // Handle incoming location events.
    - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
    {
      CLLocation *location = locations.lastObject;
      NSLog(@"Location: %@", location);
    
      float zoomLevel = locationManager.accuracyAuthorization == CLAccuracyAuthorizationFullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel;
      GMSCameraPosition * camera = [GMSCameraPosition cameraWithLatitude:location.coordinate.latitude
                                                               longitude:location.coordinate.longitude
                                                                    zoom:zoomLevel];
    
      if (mapView.isHidden) {
        mapView.hidden = NO;
        mapView.camera = camera;
      } else {
        [mapView animateToCameraPosition:camera];
      }
    
      [self listLikelyPlaces];
    }
    
    // Handle authorization for the location manager.
    - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
    {
      // Check accuracy authorization
      CLAccuracyAuthorization accuracy = manager.accuracyAuthorization;
      switch (accuracy) {
        case CLAccuracyAuthorizationFullAccuracy:
          NSLog(@"Location accuracy is precise.");
          break;
        case CLAccuracyAuthorizationReducedAccuracy:
          NSLog(@"Location accuracy is not precise.");
          break;
      }
    
      // Handle authorization status
      switch (status) {
        case kCLAuthorizationStatusRestricted:
          NSLog(@"Location access was restricted.");
          break;
        case kCLAuthorizationStatusDenied:
          NSLog(@"User denied access to location.");
          // Display the map using the default location.
          mapView.hidden = NO;
        case kCLAuthorizationStatusNotDetermined:
          NSLog(@"Location status not determined.");
        case kCLAuthorizationStatusAuthorizedAlways:
        case kCLAuthorizationStatusAuthorizedWhenInUse:
          NSLog(@"Location status is OK.");
      }
    }
    
    // Handle location manager errors.
    - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
    {
      [manager stopUpdatingLocation];
      NSLog(@"Error: %@", error.localizedDescription);
    }
          

添加地图

创建地图并将其添加到主视图控制器的 viewDidLoad() 视图中。在收到位置信息更新之前,地图将保持隐藏状态(位置信息更新在 CLLocationManagerDelegate 扩展中进行处理)。

Swift

// A default location to use when location permission is not granted.
let defaultLocation = CLLocation(latitude: -33.869405, longitude: 151.199)

// Create a map.
let zoomLevel = locationManager.accuracyAuthorization == .fullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude,
                                      longitude: defaultLocation.coordinate.longitude,
                                      zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.settings.myLocationButton = true
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true

// Add the map to the view, hide it until we've got a location update.
view.addSubview(mapView)
mapView.isHidden = true
      

Objective-C

// A default location to use when location permission is not granted.
CLLocationCoordinate2D defaultLocation = CLLocationCoordinate2DMake(-33.869405, 151.199);

// Create a map.
float zoomLevel = locationManager.accuracyAuthorization == CLAccuracyAuthorizationFullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel;
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:defaultLocation.latitude
                                                        longitude:defaultLocation.longitude
                                                             zoom:zoomLevel];
mapView = [GMSMapView mapWithFrame:self.view.bounds camera:camera];
mapView.settings.myLocationButton = YES;
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
mapView.myLocationEnabled = YES;

// Add the map to the view, hide it until we've got a location update.
[self.view addSubview:mapView];
mapView.hidden = YES;
      

提示用户选择当前地点

使用 Places SDK for iOS 获取用户当前位置的五大可能性,并以 UITableView 的形式显示列表。用户选择地点后,向地图添加标记。

  1. 获取用于填充 UITableView 的可能地点列表,用户可以从中选择他们当前所在的位置。
  2. Swift

    // Populate the array with the list of likely places.
    func listLikelyPlaces() {
      // Clean up from previous sessions.
      likelyPlaces.removeAll()
    
      let placeFields: GMSPlaceField = [.name, .coordinate]
      placesClient.findPlaceLikelihoodsFromCurrentLocation(withPlaceFields: placeFields) { (placeLikelihoods, error) in
        guard error == nil else {
          // TODO: Handle the error.
          print("Current Place error: \(error!.localizedDescription)")
          return
        }
    
        guard let placeLikelihoods = placeLikelihoods else {
          print("No places found.")
          return
        }
    
        // Get likely places and add to the list.
        for likelihood in placeLikelihoods {
          let place = likelihood.place
          self.likelyPlaces.append(place)
        }
      }
    }
          

    Objective-C

    // Populate the array with the list of likely places.
    - (void) listLikelyPlaces
    {
      // Clean up from previous sessions.
      likelyPlaces = [NSMutableArray array];
    
      GMSPlaceField placeFields = GMSPlaceFieldName | GMSPlaceFieldCoordinate;
      [placesClient findPlaceLikelihoodsFromCurrentLocationWithPlaceFields:placeFields callback:^(NSArray<GMSPlaceLikelihood *> * _Nullable likelihoods, NSError * _Nullable error) {
        if (error != nil) {
          // TODO: Handle the error.
          NSLog(@"Current Place error: %@", error.localizedDescription);
          return;
        }
    
        if (likelihoods == nil) {
          NSLog(@"No places found.");
          return;
        }
    
        for (GMSPlaceLikelihood *likelihood in likelihoods) {
          GMSPlace *place = likelihood.place;
          [likelyPlaces addObject:place];
        }
      }];
    }
          
  3. 打开新视图,向用户显示可能的地点。用户点按“获取地点”后,系统会跳转到一个新的视图,并向用户显示可供选择的地点列表。prepare 函数使用当前可能的地点列表更新 PlacesViewController,并在执行分段时自动调用该函数。
  4. Swift

    // Prepare the segue.
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
      if segue.identifier == "segueToSelect" {
        if let nextViewController = segue.destination as? PlacesViewController {
          nextViewController.likelyPlaces = likelyPlaces
        }
      }
    }
          

    Objective-C

    // Prepare the segue.
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
      if ([segue.identifier isEqualToString:@"segueToSelect"]) {
        if ([segue.destinationViewController isKindOfClass:[PlacesViewController class]]) {
          PlacesViewController *placesViewController = (PlacesViewController *)segue.destinationViewController;
          placesViewController.likelyPlaces = likelyPlaces;
        }
      }
    }
          
  5. PlacesViewController 中,使用 UITableViewDataSource 委托扩展程序,使用最有可能的地点列表填充表格。
  6. Swift

    // Populate the table with the list of most likely places.
    extension PlacesViewController: UITableViewDataSource {
      func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return likelyPlaces.count
      }
    
      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
        let collectionItem = likelyPlaces[indexPath.row]
    
        cell.textLabel?.text = collectionItem.name
    
        return cell
      }
    }
          

    Objective-C

    #pragma mark - UITableViewDataSource
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
      return self.likelyPlaces.count;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
      return [tableView dequeueReusableCellWithIdentifier:cellReuseIdentifier forIndexPath:indexPath];
    }
    @end
          
  7. 使用 UITableViewDelegate 委托扩展程序处理用户的选择。
  8. Swift

    class PlacesViewController: UIViewController {
    
      // ...
    
      // Pass the selected place to the new view controller.
      override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "unwindToMain" {
          if let nextViewController = segue.destination as? MapViewController {
            nextViewController.selectedPlace = selectedPlace
          }
        }
      }
    }
    
    // Respond when a user selects a place.
    extension PlacesViewController: UITableViewDelegate {
      func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedPlace = likelyPlaces[indexPath.row]
        performSegue(withIdentifier: "unwindToMain", sender: self)
      }
    
      // Adjust cell height to only show the first five items in the table
      // (scrolling is disabled in IB).
      func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return self.tableView.frame.size.height/5
      }
    
      // Make table rows display at proper height if there are less than 5 items.
      func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        if (section == tableView.numberOfSections - 1) {
          return 1
        }
        return 0
      }
    }
          

    Objective-C

    @interface PlacesViewController () <UITableViewDataSource, UITableViewDelegate>
    // ...
    
    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
    
    }
    
    #pragma mark - UITableViewDelegate
    
    // Respond when a user selects a place.
    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
      self.selectedPlace = [self.likelyPlaces objectAtIndex:indexPath.row];
      [self performSegueWithIdentifier:@"unwindToMain" sender:self];
    }
    
    // Adjust cell height to only show the first five items in the table
    // (scrolling is disabled in IB).
    -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
      return self.tableView.frame.size.height/5;
    }
    
    // Make table rows display at proper height if there are less than 5 items.
    -(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
    {
      if (section == tableView.numberOfSections - 1) {
        return 1;
      }
      return 0;
    }
          

向地图添加标记

在用户做出选择后,使用展开细分返回上一个视图,然后将标记添加到地图中。系统会在返回主视图控制器时自动调用 unwindToMain IBAction。

Swift

// Update the map once the user has made their selection.
@IBAction func unwindToMain(segue: UIStoryboardSegue) {
  // Clear the map.
  mapView.clear()

  // Add a marker to the map.
  if let place = selectedPlace {
    let marker = GMSMarker(position: place.coordinate)
    marker.title = selectedPlace?.name
    marker.snippet = selectedPlace?.formattedAddress
    marker.map = mapView
  }

  listLikelyPlaces()
}
      

Objective-C

// Update the map once the user has made their selection.
- (void) unwindToMain:(UIStoryboardSegue *)segue
{
  // Clear the map.
  [mapView clear];

  // Add a marker to the map.
  if (selectedPlace != nil) {
    GMSMarker *marker = [GMSMarker markerWithPosition:selectedPlace.coordinate];
    marker.title = selectedPlace.name;
    marker.snippet = selectedPlace.formattedAddress;
    marker.map = mapView;
  }

  [self listLikelyPlaces];
}
      

恭喜!您构建了一个 iOS 应用,可让用户选择其当前地点,并在 Google 地图上显示结果。在学习本课程的过程中,您学习了如何使用 Places SDK for iOSMaps SDK for iOSApple Core Location 框架