지도에서 현재 장소 선택 및 세부정보 표시하기
이 튜토리얼에서는 다음 작업을 위해 iOS 앱을 빌드하는 방법을 보여줍니다.
- 현재 기기 위치를 가져옵니다.
- 기기가 있을 가능성이 높은 장소의 목록을 가져옵니다.
- 사용자에게 최적의 장소 일치를 요청하는 메시지를 표시합니다.
- 지도에 마커를 표시합니다.
이 튜토리얼에 따라 iOS용 Places SDK, iOS용 Maps SDK, Apple Core 위치 프레임워크를 사용하여 iOS 앱을 빌드하세요.
코드 가져오기
GitHub에서 iOS용 Google 지도 SDK를 클론하거나 다운로드합니다.
개발 프로젝트 설정
iOS용 Places SDK 및 iOS용 Maps SDK를 설치하려면 다음 단계를 따르세요.
- Xcode 버전 14.0 이상을 다운로드하여 설치합니다.
- CocoaPods를 아직 설치하지 않은 경우 터미널에서 다음 명령어를 실행하여 macOS에 설치합니다.
sudo gem install cocoapods
- 샘플 저장소를 저장한 디렉터리 (코드 가져오기)에서
tutorials/current-place-on-map
디렉터리로 이동합니다. pod install
명령어를 실행합니다. 이렇게 하면Podfile
에 지정된 API가 설치될 수 있는 종속 항목과 함께 설치됩니다.pod outdated
를 실행하여 설치된 포드 버전을 새 업데이트와 비교합니다. 새 버전이 감지되면pod update
를 실행하여Podfile
를 업데이트하고 최신 SDK를 설치합니다. 자세한 내용은 CocoaPods 가이드를 참고하세요.- 프로젝트의 current-place-on-map.xcworkspace를 열고 더블클릭하여 Xcode에서 엽니다.
.xcworkspace
파일을 사용하여 프로젝트를 열어야 합니다.
자세한 설치 안내는 시작하기 (지도) 및 시작하기 (장소)를 참고하세요.
필요한 API 사용 설정 및 API 키 가져오기
이 튜토리얼을 완료하려면 iOS용 Maps SDK 및 Places API를 사용할 권한이 있는 Google API 키가 필요합니다.
- Google Maps Platform 시작하기의 안내에 따라 두 계정 모두에서 결제 계정과 프로젝트를 사용 설정합니다.
- API 키 가져오기의 안내에 따라 이전에 설정한 개발 프로젝트의 API 키를 만듭니다.
애플리케이션에 API 키 추가
다음과 같이 API 키를 AppDelegate.swift
에 추가합니다.
- 다음 import 문이 파일에 추가되었습니다.
import GooglePlaces import GoogleMaps
application(_:didFinishLaunchingWithOptions:)
메서드에서 다음 줄을 수정하여 YOUR_API_KEY를 API 키로 바꿉니다.GMSPlacesClient.provideAPIKey("YOUR_API_KEY") GMSServices.provideAPIKey("YOUR_API_KEY")
앱 빌드 및 실행
- iOS 기기를 컴퓨터에 연결하거나 Xcode 스키마 팝업 메뉴에서 시뮬레이터를 선택합니다.
- 기기를 사용 중인 경우 위치 서비스가 사용 설정되어 있는지 확인합니다. 시뮬레이터를 사용하는 경우 기능 메뉴에서 위치를 선택합니다.
- 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 앱에는 두 가지 뷰 컨트롤러가 있습니다. 하나는 사용자가 현재 선택한 장소를 보여주는 지도를 표시하고 다른 하나는 사용자에게 선택 가능한 장소 목록을 표시합니다. 각 뷰 컨트롤러에는 가능한 장소 목록 (likelyPlaces
)을 추적하고 사용자의 선택 (selectedPlace
)을 지정하기 위한 동일한 변수가 있습니다. 뷰 간 탐색은 segues를 사용하여 실행됩니다.
위치 정보 액세스 권한 요청
앱에서 사용자에게 위치 서비스 사용에 대한 동의를 요청해야 합니다. 이렇게 하려면 앱의 Info.plist
파일에 NSLocationAlwaysUsageDescription
키를 포함하고 각 키의 값을 앱이 위치 데이터를 사용하는 방식을 설명하는 문자열로 설정합니다.
위치 관리자 설정
CLLocationManager를 사용하여 기기의 현재 위치를 찾고 기기가 새 위치로 이동할 때 정기 업데이트를 요청합니다. 이 튜토리얼에서는 기기 위치를 가져오는 데 필요한 코드를 제공합니다. 자세한 내용은 Apple 개발자 문서의 사용자 위치 가져오기 가이드를 참조하세요.
- 위치 관리자, 현재 위치, 지도뷰, 장소 클라이언트, 기본 확대/축소 수준을 클래스 수준에서 선언합니다.
viewDidLoad()
에서 위치 관리자 및GMSPlacesClient
를 초기화합니다.- 예상 장소 목록과 사용자가 선택한 장소를 보유할 변수를 선언합니다.
- 대리자를 추가하여 확장 절을 사용하여 위치 관리자의 이벤트를 처리합니다.
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;
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];
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;
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
에 목록을 표시합니다. 사용자가 장소를 선택하면 지도에 마커를 추가합니다.
UITableView
를 채울 가능성이 높은 장소 목록을 가져옵니다. 여기에서 사용자는 현재 위치한 장소를 선택할 수 있습니다.- 새 뷰를 열어 사용자에게 표시될 가능성이 높은 장소를 표시합니다. 사용자가 '장소 가져오기'를 탭하면 새로운 뷰로 표시되며 사용자에게 선택 가능한 장소 목록이 표시됩니다.
prepare
함수는 현재 예상되는 장소 목록으로PlacesViewController
를 업데이트하며, 세그를 실행할 때 자동으로 호출됩니다. PlacesViewController
에서UITableViewDataSource
위임 확장 프로그램을 사용하여 가장 가능성이 높은 장소 목록을 사용하여 테이블을 채웁니다.UITableViewDelegate
위임 확장 프로그램을 사용하여 사용자의 선택을 처리합니다.
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]; } }]; }
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; } } }
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
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 위치 프레임워크를 사용하는 방법을 알아보았습니다.