컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

지도에서 현재 장소 선택 및 세부정보 표시하기

이 튜토리얼에서는 다음 작업을 위해 iOS 앱을 빌드하는 방법을 보여줍니다.

  • 현재 기기 위치를 가져옵니다.
  • 기기가 있을 가능성이 높은 위치의 목록을 가져옵니다.
  • 가장 일치하는 장소가 있는지 묻는 메시지를 표시합니다.
  • 지도에 마커를 표시합니다.

이 튜토리얼에 따라 iOS용 Places SDK, iOS용 Maps SDKApple Core Location 프레임워크를 사용하여 iOS 앱을 빌드할 수 있습니다.

코드 가져오기

GitHub에서 iOS용 Google Maps SDK를 클론하거나 다운로드합니다.

개발 프로젝트 설정

iOS용 Places SDK와 iOS용 Maps SDK를 설치하려면 다음 단계를 따르세요.

  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 키 가져오기

이 튜토리얼을 완료하려면 iOS용 Maps SDKPlaces API를 사용할 수 있도록 승인된 Google API 키가 필요합니다.

  1. Google Maps Platform 시작하기의 안내에 따라 두 제품 모두 결제 계정 및 프로젝트를 사용 설정합니다.
  2. API 키 가져오기의 안내에 따라 이전에 설정한 개발 프로젝트를 위한 API 키를 만듭니다.

애플리케이션에 API 키 추가

Swift

다음과 같이 API 키를 AppDelegate.swift에 추가합니다.

  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

다음과 같이 API 키를 AppDelegate.m에 추가합니다.

  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. 기기를 사용 중인 경우 위치 서비스가 사용 설정되어 있는지 확인합니다. 시뮬레이터를 사용하는 경우 기능 메뉴에서 위치를 선택합니다.
  3. Xcode에서 Product/Run 메뉴 옵션 (또는 재생 버튼 아이콘)을 클릭합니다.

Xcode가 앱을 빌드한 다음 기기 또는 시뮬레이터에서 앱을 실행합니다.

이 페이지의 이미지와 비슷하게, 현재 위치를 중심으로 한 여러 개의 마커가 있는 지도가 표시됩니다.

문제 해결:

  • 지도가 표시되지 않으면 위에서 설명한 대로 API 키를 가져와 앱에 추가했는지 확인하세요. Xcode의 디버깅 콘솔에서 API 키에 대한 오류 메시지를 확인합니다.
  • iOS 번들 식별자로 API 키를 제한했다면 키를 수정하여 앱의 번들 식별자를 추가합니다. com.google.examples.current-place-on-map
  • 위치 서비스 권한 요청이 거부되면 지도가 제대로 표시되지 않습니다.
    • 기기를 사용 중인 경우 설정/일반/개인 정보 보호/위치 서비스로 이동하여 위치 서비스를 다시 사용 설정합니다.
    • 시뮬레이터를 사용하는 경우 시뮬레이터/콘텐츠 및 설정 초기화...로 이동합니다.
    다음에 앱이 실행될 때 위치 서비스 프롬프트를 수락해야 합니다.
  • Wi-Fi 또는 GPS 연결이 양호한지 확인합니다.
  • 앱이 실행되지만 지도가 표시되지 않으면 프로젝트의 Info.plist를 적절한 위치 권한으로 업데이트했는지 확인합니다. 권한 처리에 관한 자세한 내용은 아래의 앱에서 위치 정보 액세스 권한 요청 가이드를 참고하세요.
  • Xcode 디버깅 도구를 사용하여 로그를 확인하고 앱을 디버깅하세요.

코드 이해

여기에서는 유사한 앱을 빌드하는 방법을 이해할 수 있도록 current-place-on-map 앱의 가장 중요한 부분을 설명합니다.

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;
      

사용자에게 현재 장소를 선택하라는 메시지 표시

iOS용 Places SDK를 사용하면 사용자의 현재 위치를 토대로 상위 5개 장소의 가능성을 확인하고 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];
}
      

수고하셨습니다. 사용자가 현재 장소를 선택하고 Google 지도에 결과를 표시할 수 있는 iOS 앱을 빌드했습니다. 이 과정에서 iOS용 Places SDK, iOS용 Maps SDK, Apple Core Location 프레임워크를 사용하는 방법을 알아보았습니다.