今回はGoogle Mapをモバイル開発で使う流れをまとめようと思います。
AndroidとiOS両方説明しようと思います。
AndroidとiOSでできることにはほとんど差がないのでコードの書き方が少し違うだけだと思います。
iOSでGoogle Mapの対応を行う
最初にマップを表示させるところから始めましょう!
事前準備
まずは、マップを使えるようにするために次の手順を行う必要があります。(順不同)
- APIキーを取得してプロジェクトに導入
- ライブラリを使えるようにする
- 位置情報の権限追加
まずはAPIキーを取得します。 どのアプリケーションからGoogle Mapを使うかを判定、リクエストを行うためにAPIキーを取得してプロジェクトで設定する必要があります。
APIキーを取得は次の公式ドキュメントを確認してください。 対象のアプリでGoogle Mapの使用するかでONを設定すると認証情報の欄でAPI Keyが確認できます。
そして、AppDelegateで下記のようにAPIキーを設定します。
GMSServices.provideAPIKey("取得したAPIキーを設定")
次にライブラリを使えるようにpodファイルにGoogle Mapのライブラリを追加してpod install
を行います。
pod 'GoogleMaps'
そして、位置情報の権限追加はinfo.plistに位置情報の権限を追加します。 細かい記載は今回割愛します。
マップを表示
事前準備を行ったら実際にマップを表示させます。
class MapViewController: UIViewController { private var locationManager: CLLocationManager? private var mapView: GMSMapView? deinit { if let manager = locationManager { manager.stopUpdatingLocation() locationManager = nil } } override func viewDidLoad() { super.viewDidLoad() setupLocationManager() } private func setupLocationManager() { let locationManager = CLLocationManager() locationManager.delegate = self // 位置情報の取得精度を指定 locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters // 位置情報の権限確認 let status = CLLocationManager.authorizationStatus() switch status { case .notDetermined: locationManager.requestWhenInUseAuthorization() case .restricted, .denied: setupGoogleMaps() case .authorizedAlways, .authorizedWhenInUse: locationManager.startUpdatingLocation() @unknown default: setupGoogleMaps() } self.locationManager = locationManager } } extension MapViewController { private func setupGoogleMaps() { if mapView != nil { return } var location: CLLocation! if let lastKnownLocation = locationManager?.location { location = lastKnownLocation } else { location = CLLocation(latitude: 0, longitude: 0) } // 現在地とマップがどれくらいズームするかを設定する let camera = GMSCameraPosition.camera( withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: 12.5 ) // GMSMapViewを初期化する let mapView = GMSMapView.map( withFrame: CGRect(x: 0, y: 0, width: view.frame.width,height: view.frame.height), camera: camera ) // 現在値を中心にするボタンを有効にする設定 mapView.settings.myLocationButton = true // AutoLayoutで定義したUIViewにaddSubViewする mapBaseView.addSubview(mapView) mapBaseView.sendSubviewToBack(mapView) self.mapView = mapView } } // MARK: - CLLocationManagerDelegate extension MapViewController: CLLocationManagerDelegate { // 権限が変更されたときの処理 func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { switch status { case .notDetermined: manager.requestWhenInUseAuthorization() case .restricted, .denied: setupGoogleMaps() case .authorizedAlways, .authorizedWhenInUse: manager.startUpdatingLocation() @unknown default: setupGoogleMaps() } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { manager.stopUpdatingLocation() setupGoogleMaps() } }
上記は表示させるためのコードです。
setupLocationManager()
で位置情報を許諾しているかの確認を行い、まだ1度も設定していない場合には確認のダイアログを表示させます。
許可している場合にはstartUpdatingLocation()
で現在地を取得してマップに適用させます。
移動する場合にもCLLocationManagerDelegate
のfunc locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
で現在地を定期的に更新できます。
また、マップにはGMSMapView
クラスを使います。
GMSMapViewは、地図に関するすべてのメソッドの入り口で、表示するMapのクラスです。
setupGoogleMaps()
でGoogleMapの表示設定を行いました。
大まかには初期表示の位置やどれくらいズームするかを設定したGMSMapViewを初期化して、AutoLayoutで定義したUIViewにaddSubViewすると表示できます。
それが上記のコードになります。
ピンを表示
次に特定の位置にピン(アイコン)を表示させる方法です。
ピンにはGMSMarker
クラスを使います。
先ほどの例のsetupGoogleMaps()の後にcreateMarker()メソッドを追加します。
class MapViewController: UIViewController { private var locationManager: CLLocationManager? private var mapView: GMSMapView? } extension MapViewController { private func setupGoogleMaps() { // 先ほどの例の部分は割愛 createMarker() } private func createMarker() { let marker = GMSMarker() //ピンの色変更したい場合に設定 marker.icon = GMSMarker.markerImage(with: .black) marker.position = CLLocationCoordinate2D(latitude: 34.077875549971, longitude: 134.56156512254) marker.title = "タイトル" //ピンの画像を変更したい場合に設定 marker.icon = UIImage(named: "camera_1")! marker.zIndex = 2 marker.map = mapView mapView.delegate = self } } extension MapViewController: GMSMapViewDelegate { // ピンをタップした時の処理 func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool { return true } // マップをタップした時の処理 func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) { } func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) { } }
今回はposition(表示させる緯度・経度の情報)と titleを含むGMSMarkerオブジェクトを作成し、GMSMapViewに設定します。
titleはピンをタップすると情報ウィンドウに設定した文字列が表示されます。
iconはアイコンの情報を設定することができ、色や画像の変更を行えます。
GMSMapViewDelegate
を設定しているのはアイコンやマップをタップしたときに処理を行う場合には必要です。
また、zIndex
を指定していますが、これはピンが重なった時にzIndexの値が大きい方が前に表示されます。
意外と簡単だと思った方が多いと思いますが、ここまでで最低限のマップアプリは作れると思います。
AndroidでGoogle Mapの対応を行う
AndroidもiOSと同様に進めます。
iOSと大きな違いがあるわけではないのでそこまで難しいことはないと思います。
マップを表示
まずはiOSと同様にマップを表示させるところから始めましょう!
最初にAPI Keyを設定します。
AndroidManifest.xmlにAPI Keyの設定を追加します。
<meta-data android:name="com.google.android.geo.API_KEY" android:value="取得したAPI Keyを設定" />
そして、ライブラリをbuild.gradleに追加します。
implementation 'com.google.gms:google-services:4.3.14' implementation 'com.google.android.gms:play-services-location:21.0.1' implementation 'com.google.android.gms:play-services-maps:18.1.0' implementation 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1'
Mapを表示させるにはGoogleMap
クラスを使用します。
LocationServices.getFusedLocationProviderClient
で位置情報を取得します。
Mapを表示させるための一例は次のコードです。
class MapFragment : Fragment(), OnMapReadyCallback { private var mapView: GoogleMap? = null private var fusedLocationClient: FusedLocationProviderClient? = null private var _binding: FragmentMapBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentMapBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { setupData() } override fun onResume() { super.onResume() } private fun setupData() { checkPermission() fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext()) val mapFragment = childFragmentManager.findFragmentById(R.id.mapView) as SupportMapFragment mapFragment.getMapAsync(this) } override fun onMapReady(p0: GoogleMap) { this.mapView = p0 fetchLocation() } private fun fetchLocation() { mapView?.uiSettings?.isMyLocationButtonEnabled = false if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return } else { mapView?.isMyLocationEnabled = true val task = fusedLocationClient?.lastLocation task?.addOnSuccessListener { location -> if (location != null) { // Mapの中心の緯度・経度を設定して表示させる mapView?.moveCamera( CameraUpdateFactory.newLatLngZoom( LatLng(location.latitude, location.longitude), 10.0f ) ) } else { val request = LocationRequest.create() .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) .setInterval(500) .setFastestInterval(300) fusedLocationClient?.requestLocationUpdates( request, object : LocationCallback() { override fun onLocationResult(result: LocationResult) { mapView?.moveCamera( CameraUpdateFactory.newLatLngZoom( LatLng( result.lastLocation!!.latitude, result.lastLocation!!.longitude ), 12.5f ) ) fusedLocationClient?.removeLocationUpdates(this) } }, Looper.getMainLooper() ) } } } }
今回はFragmentでGoogle Mapを表示させていますが、Activityで表示させる場合にも基本的には同じです。
FragmentManager.findFragmentById() を呼び出して、地図フラグメントに対するハンドルを取得します。
次に、getMapAsync()を使用して、GoogleMapインスタンス生成完了時に呼ばれる地図コールバックを登録します。
OnMapReadyCallback
インターフェースを実装し、GoogleMap オブジェクトが使用可能な場合に地図を設定するようonMapReady()
をオーバーライドします。
ピンを表示
次に特定の位置にピン(アイコン)を表示させる方法です。
iOSではピンにはGMSMarker
クラスを使いましたが、AndroidではMarker
クラスを使用します。
Mapが表示した後に下記の処理を行います。(onMapReady()や初期設定後)
private fun createOption(latLng: LatLng): MarkerOptions { val options = MarkerOptions().position(latLng) // アイコンを指定 options.icon(BitmapDescriptorFactory.fromResource(R.drawable.pin)) options.zIndex(2.0F) // mapViewはGoogle Mapクラスのインスタンス mapView?.addMarker(options) }
上記の処理を行うと指定した緯度・経度にマーカーが表示されます。 削除する場合にはremove()を使用します。
ダークモード対応
作成したコードをダークモードで実行してもライトモードのままだと思います。 ダークモード対応は追加で別で実装が必要となるので、最後に補足します。
iOS
次のメソッドをマップの初期化や画面復帰した際の処理に追加してください。
private func setMapStyle(_ mapView: GMSMapView?) { guard let mapView = mapView else { return } if UITraitCollection.current.userInterfaceStyle == .dark { // ダークモードのjsonを読み込ませる if let styleURL = Bundle.main.url(forResource: "map_dark", withExtension: "json") { do { mapView.mapStyle = try GMSMapStyle(contentsOfFileURL: styleURL) } catch { return } } } else { // ライトモードのjsonを読み込ませる if let styleURL = Bundle.main.url(forResource: "map_light", withExtension: "json") { do { mapView.mapStyle = try GMSMapStyle(contentsOfFileURL: styleURL) } catch { return } } }
何をやっているかというと、GMSMapViewのmapStyle
にjsonで定義した色などの情報を適用させています。
背景色やラベルの色、道路の色など設定できます。
下記に色を設定してjsonファイルを作成してくれるサイトがあるので、作成は楽だと思います。
https://mapstyle.withgoogle.com
Android
AndroidもiOSと同様にjsonで定義した色などの情報を適用させます。 他にも方法はあるかもしれませんが、iOSと実装方法を共通化できる点でjsonを使うのがおすすめです。
private fun setMapStyle(mapView: GoogleMap?) { mapView?.let { view -> val nightModeFlags = requireContext().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { view.setMapStyle( MapStyleOptions.loadRawResourceStyle( requireContext(), R.raw.map_dark ) ) } else { view.setMapStyle( MapStyleOptions.loadRawResourceStyle( requireContext(), R.raw.map ) ) } } }
最後に
両OSともにシンプルなコードでGoogle Mapを表示できたと思います。 もう少し突っ込んだことを実装したい場合には公式ドキュメントが細かく記載されているため確認してみるといいと思います。