Інтеграція Google Maps SDK в мобільну програму
Google Maps SDK для Android та iOS — зрілий інструмент, але в нього є нюанси, які регулярно ломають перші інтеграції: неправильна ініціалізація API key, несумісність версій з Gradle / Swift Package Manager, та відображення сірої карти без зрозумілого сообщення про помилку у логах.
Перший запуск: API key та обмеження
API key створюється у Google Cloud Console, проект повинен включати Maps SDK for Android та Maps SDK for iOS як окремі продукти. Ключ потрібно обмежити: для Android — по applicationId (SHA-1 fingerprint + package name), для iOS — по Bundle ID. Без обмежень ключ можна витягнути з APK декомпілятором за 5 хвилин.
На Android ключ прописується у AndroidManifest.xml:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${MAPS_API_KEY}" />
Значення MAPS_API_KEY задається у local.properties та підставляється через buildConfigField у build.gradle — не хардкодьте строку прямо в маніфест.
На iOS — у AppDelegate або через GMSServices.provideAPIKey() до створення будь-якого GMSMapView:
import GoogleMaps
@main
struct AppEntry: App {
init() {
GMSServices.provideAPIKey("YOUR_API_KEY")
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
Сіра карта — що перевірити
- API key не має прав на потрібний продукт (Maps SDK vs Maps JavaScript API — різні продукти)
- Billing не підключен до проекту у GCP (Maps SDK потребує активного біллінга, навіть у рамках бесплатного ліміту)
- На Android —
minSdkVersionнижче 21 або немає залежностіcom.google.android.gms:play-services-maps - На iOS — файл
GoogleMaps.xcframeworkне додано доFrameworks, Libraries, and Embedded Content
Android: базова настройка MapView
// build.gradle (app)
implementation("com.google.android.gms:play-services-maps:18.2.0")
// Fragment
class MapFragment : Fragment(), OnMapReadyCallback {
private lateinit var map: GoogleMap
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val mapFragment = childFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
map.uiSettings.isZoomControlsEnabled = true
map.moveCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(55.7558, 37.6173), // Москва
12f
)
)
}
}
SupportMapFragment переважніше MapView — він сам управляє lifecycle (onCreate, onResume, onPause, onDestroy). Якщо використовуєте MapView напрями — кожен lifecycle-метод потрібно пробрасувати вручну, і забутий mapView.onDestroy() приводить до утечи пам'яти.
iOS: SwiftUI та UIKit
У UIKit — GMSMapView додається як звичайний UIView. У SwiftUI — оберніть через UIViewRepresentable:
struct GoogleMapView: UIViewRepresentable {
let coordinate: CLLocationCoordinate2D
let zoom: Float
func makeUIView(context: Context) -> GMSMapView {
let camera = GMSCameraPosition(target: coordinate, zoom: zoom)
let mapView = GMSMapView(frame: .zero, camera: camera)
mapView.isMyLocationEnabled = true
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Context) {
let camera = GMSCameraPosition(target: coordinate, zoom: zoom)
mapView.animate(to: camera)
}
}
Кастомні стилі карти
Google Maps підтримує JSON-стилі через GMSMapStyle (iOS) та MapStyleOptions (Android). Стилі генеруються у Google Maps Platform Styling Wizard. Застосовуються однією строком:
mapView.mapStyle = try? GMSMapStyle(jsonString: mapStyleJSON)
Типові проблеми при інтеграції
Android: ClassNotFoundException: com.google.android.gms.maps.MapFragment — застарілий MapFragment замість SupportMapFragment. У сучасних проектах з Navigation Component завжди використовуйте SupportMapFragment.
iOS: крах GMSServices.provideAPIKey викликаний двічі — якщо AppDelegate та SceneDelegate обидва ініціалізують SDK. Винесіть ініціалізацію в одне місце.
Квоти: бесплатний ліміт Google Maps — 28 500 завантажень карти на місяць. Кожний getMapAsync або створення GMSMapView — це одна завантаження. Якщо карта пересоздається при кожному відкритті екрана (неправильний lifecycle), квота тає швидко.
Часові рамки
1–3 дні. Базова карта з маркерами — 1 день. Повна інтеграція з кастомним стилем, інфо-вікнами та маршрутами — 2–3 дні. Вартість розраховується індивідуально.







