Memilih Tempat Saat Ini dan Menampilkan Detail pada Peta

Tutorial ini menunjukkan cara mem-build aplikasi iOS untuk:

  • Mendapatkan lokasi perangkat saat ini.
  • Mendapatkan daftar tempat perangkat kemungkinan berada.
  • Minta pengguna untuk menemukan tempat terbaik.
  • Tampilkan penanda di peta.

Ikuti tutorial ini untuk membuat aplikasi iOS menggunakan Places SDK for iOS, Maps SDK for iOS, dan framework Apple Core Location.

Mendapatkan kode

Clone atau download Google Maps SDK for iOS dari GitHub.

Menyiapkan project pengembangan

Ikuti langkah-langkah berikut untuk menginstal Places SDK for iOS dan Maps SDK for iOS:

  1. Download dan instal Xcode versi 13.0 atau yang lebih baru.
  2. Jika Anda belum memiliki CocoaPods, instal di macOS dengan menjalankan perintah berikut dari terminal:
    sudo gem install cocoapods
  3. Di direktori tempat Anda menyimpan repositori contoh (Dapatkan kode), buka direktori tutorials/current-place-on-map.
  4. Jalankan perintah pod install. Tindakan ini akan menginstal API yang ditentukan dalam Podfile, beserta dependensi yang mungkin dimilikinya.
  5. Jalankan pod outdated untuk membandingkan versi pod yang diinstal dengan update baru. Jika versi baru terdeteksi, jalankan pod update untuk mengupdate Podfile dan instal SDK terbaru. Untuk mengetahui detail selengkapnya, lihat Panduan CocoaPods.
  6. Buka (klik dua kali) current-place-on-map.xcworkspace project untuk membukanya di Xcode. Anda harus menggunakan file .xcworkspace untuk membuka project.

Untuk mendapatkan petunjuk penginstalan yang mendetail, lihat Memulai (Maps), dan Memulai (Places).

Mengaktifkan API yang diperlukan dan mendapatkan kunci API

Untuk menyelesaikan tutorial ini, Anda memerlukan kunci Google API yang telah diberi otorisasi untuk menggunakan Maps SDK for iOS dan Places API.

  1. Ikuti petunjuk di Memulai Google Maps Platform untuk menyiapkan akun penagihan dan project yang diaktifkan dengan kedua produk ini.
  2. Ikuti petunjuk tentang Mendapatkan Kunci API untuk membuat kunci API untuk project pengembangan yang Anda siapkan sebelumnya.

Menambahkan kunci API ke aplikasi

Tambahkan kunci API ke AppDelegate.swift sebagai berikut:

  1. Perhatikan bahwa pernyataan impor berikut telah ditambahkan ke file:
    import GooglePlaces
    import GoogleMaps
  2. Edit baris berikut dalam metode application(_:didFinishLaunchingWithOptions:) Anda, yang menggantikan YOUR_API_KEY dengan kunci API Anda:
    GMSPlacesClient.provideAPIKey("YOUR_API_KEY")
    GMSServices.provideAPIKey("YOUR_API_KEY")

Mem-build dan menjalankan aplikasi

  1. Hubungkan perangkat iOS ke komputer Anda, atau pilih simulator dari menu pop-up skema Xcode.
  2. Jika Anda menggunakan perangkat, pastikan layanan lokasi diaktifkan. Jika Anda menggunakan simulator, pilih lokasi dari menu Features.
  3. Di Xcode, klik opsi menu Product/Run (atau ikon tombol putar).
    • Xcode mem-build aplikasi, lalu menjalankan aplikasi di perangkat atau di simulator.
    • Anda akan melihat peta dengan sejumlah penanda yang dipusatkan di sekitar lokasi Anda saat ini.

Pemecahan masalah:

  • Jika Anda tidak melihat peta, pastikan bahwa Anda telah mendapatkan kunci API dan menambahkannya ke aplikasi, seperti yang dijelaskan di atas. Periksa konsol proses debug Xcode untuk pesan error tentang kunci API.
  • Jika Anda telah membatasi kunci API berdasarkan ID paket iOS, edit kunci untuk menambahkan ID paket untuk aplikasi: com.google.examples.current-place-on-map.
  • Peta tidak akan ditampilkan dengan benar jika permintaan izin untuk layanan lokasi ditolak.
    • Jika Anda menggunakan perangkat, buka Settings/General/Privacy/Location Services, dan aktifkan kembali layanan lokasi.
    • Jika Anda menggunakan simulator, buka Simulator/Reset Content and Settings....
    Saat berikutnya aplikasi dijalankan, pastikan untuk menerima permintaan layanan lokasi.
  • Pastikan Anda memiliki koneksi Wi-Fi atau GPS yang bagus.
  • Jika aplikasi diluncurkan, tetapi tidak ada peta yang ditampilkan, pastikan Anda telah memperbarui Info.plist untuk project dengan izin akses lokasi yang sesuai. Untuk informasi selengkapnya tentang penanganan izin, lihat panduan untuk meminta izin akses lokasi di aplikasi Anda di bawah.
  • Gunakan alat proses debug Xcode untuk melihat log dan melakukan debug pada aplikasi.

Memahami kode

Bagian tutorial ini menjelaskan bagian yang paling signifikan dari aplikasi current-place-on-map untuk membantu Anda memahami cara membuat aplikasi serupa.

Aplikasi current-place-on-map menampilkan dua pengontrol tampilan: Satu untuk menampilkan peta yang menunjukkan tempat yang saat ini dipilih pengguna, dan satu untuk menampilkan daftar tempat yang mungkin dapat dipilih pengguna. Perhatikan, setiap pengontrol tampilan memiliki variabel yang sama untuk melacak daftar kemungkinan tempat (likelyPlaces), dan untuk menunjukkan pilihan pengguna (selectedPlace). Navigasi antartampilan diselesaikan menggunakan segues.

Meminta izin akses lokasi

Aplikasi Anda harus meminta izin kepada pengguna untuk menggunakan layanan lokasi. Untuk melakukannya, sertakan kunci NSLocationAlwaysUsageDescription dalam file Info.plist untuk aplikasi, dan tetapkan nilai setiap kunci ke string yang menjelaskan cara aplikasi menggunakan data lokasi.

Menyiapkan pengelola lokasi

Gunakan CLLocationManager untuk menemukan lokasi perangkat saat ini dan meminta update reguler saat perangkat berpindah ke lokasi baru. Tutorial ini menyediakan kode yang Anda butuhkan untuk mendapatkan lokasi perangkat. Untuk mengetahui detail selengkapnya, baca panduan untuk Mendapatkan Lokasi Pengguna di Dokumentasi Developer Apple.

  1. Deklarasikan pengelola lokasi, lokasi saat ini, tampilan peta, klien tempat, dan tingkat zoom default di tingkat class.
  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. Lakukan inisialisasi pengelola lokasi dan GMSPlacesClient di viewDidLoad().
  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. Deklarasikan variabel untuk menyimpan daftar kemungkinan tempat, dan tempat yang dipilih pengguna.
  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. Tambahkan delegasi untuk menangani peristiwa untuk pengelola lokasi, dengan menggunakan klausa ekstensi.
  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);
    }
          

Menambahkan peta

Buat peta dan tambahkan ke tampilan di viewDidLoad() di pengontrol tampilan utama. Peta akan tetap tersembunyi sampai pembaruan lokasi diterima (pembaruan lokasi ditangani di ekstensi 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;
      

Meminta pengguna memilih tempat saat ini

Gunakan Places SDK for iOS untuk mendapatkan lima kemungkinan tempat teratas berdasarkan lokasi pengguna saat ini, dan tampilkan daftar dalam UITableView. Saat pengguna memilih tempat, tambahkan penanda ke peta.

  1. Mendapatkan daftar kemungkinan tempat untuk mengisi UITableView, tempat pengguna dapat memilih tempat mereka berada saat ini.
  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. Buka tampilan baru untuk menyajikan kemungkinan tempat kepada pengguna. Saat pengguna mengetuk "Get Place", kita beralih ke tampilan baru, dan menampilkan daftar kemungkinan tempat untuk dipilih pengguna. Fungsi prepare memperbarui PlacesViewController dengan daftar kemungkinan tempat saat ini, dan secara otomatis dipanggil saat segue dilakukan.
  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. Di PlacesViewController, isi tabel menggunakan daftar kemungkinan tempat, menggunakan ekstensi delegasi 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. Tangani pilihan pengguna menggunakan ekstensi delegasi 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;
    }
          

Menambahkan penanda ke peta

Saat pengguna membuat pilihan, gunakan segue escape untuk kembali ke tampilan sebelumnya, dan tambahkan penanda ke peta. PMA unwindToMain dipanggil secara otomatis setelah kembali ke pengontrol tampilan utama.

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];
}
      

Selamat! Anda telah membuat aplikasi iOS yang memungkinkan pengguna memilih tempat mereka saat ini, dan menampilkan hasilnya di peta Google. Setelah melakukannya, Anda telah mempelajari cara menggunakan Places SDK for iOS, Maps SDK for iOS, dan framework Apple Core Location.