Реализация отправки геолокации в чате мобильного приложения
Кнопка «Поделиться местоположением» в чате выглядит простой фичей, пока не встречаешься с тем, что iOS 17 разграничивает разовое местоположение (requestLocation) и постоянный мониторинг (startUpdatingLocation), а Android 10+ требует отдельного разрешения ACCESS_BACKGROUND_LOCATION для фоновых обновлений. Кроме того, живая геолокация (когда собеседник видит, куда ты едешь в реальном времени) — это принципиально другая архитектура по сравнению с одиночным снимком координат.
Разовое местоположение vs живое отслеживание
Для разового «где я сейчас» достаточно запросить координаты один раз, сформировать сообщение со статической картой и отправить его в чат как attachment. Собеседник видит превью карты с маркером.
Живое отслеживание — это отдельный тип сообщения в чате (например, type: "live_location") с временем жизни. Google Maps Messenger и WhatsApp ограничивают трансляцию 15-60 минутами. По истечении времени бекенд автоматически закрывает сессию, и сообщение переходит в «статическое» состояние.
Разовое на iOS
import CoreLocation
class LocationManager: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
var onLocation: ((CLLocation) -> Void)?
func requestOnce() {
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
manager.requestWhenInUseAuthorization()
manager.requestLocation() // однократный запрос
}
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
onLocation?(location)
}
}
requestLocation() даёт ровно одно обновление и останавливается. Используйте kCLLocationAccuracyHundredMeters — для чата точность до метра не нужна, а батарею экономит.
Живая геолокация: архитектура
Трансляция текущего положения требует трёх слоёв:
- Мобильный клиент-отправитель периодически пишет координаты на сервер
- Бекенд хранит последние координаты и рассылает обновления подписчикам (WebSocket / SSE)
- Мобильный клиент-получатель принимает обновления и двигает маркер на карте
На Android обновления координат в фоне — через FusedLocationProviderClient с WorkManager или ForegroundService. С WorkManager одним не обойтись: PeriodicWorkRequest имеет минимальный интервал 15 минут, что для живой геолокации бесполезно. Нужен ForegroundService с уведомлением в статусбаре — пользователь должен видеть, что приложение активно использует GPS.
class LocationTrackingService : Service() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let { location ->
sendLocationToServer(location.latitude, location.longitude)
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIFICATION_ID, buildNotification())
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000L)
.setMinUpdateIntervalMillis(3000L)
.build()
fusedLocationClient.requestLocationUpdates(request, locationCallback, mainLooper)
return START_STICKY
}
}
На iOS живая трансляция в фоне работает через startUpdatingLocation с allowsBackgroundLocationUpdates = true и ключом UIBackgroundModes: location в Info.plist. Без этого ключа — краш ещё в разработке, а не на ревью.
Отображение на карте у получателя
Получатель видит маркер собеседника поверх собственного местоположения. Анимация перемещения — обязательна, иначе маркер «прыгает».
Сообщение в чате с живой геолокацией содержит session_id. Получатель подписывается на WebSocket-канал этой сессии:
ws://api.example.com/location-sessions/{session_id}
Каждые N секунд сервер публикует {lat, lng, bearing, accuracy}. Bearing нужен для поворота иконки в направлении движения.
Статическое превью карты
Для разового местоположения в пузыре чата рендерим статическое изображение через Google Static Maps API или MapKit Snapshot:
// iOS MapKit Snapshot
let options = MKMapSnapshotter.Options()
options.region = MKCoordinateRegion(
center: coordinate,
latitudinalMeters: 500,
longitudinalMeters: 500
)
options.size = CGSize(width: 240, height: 160)
MKMapSnapshotter(options: options).start { snapshot, _ in
guard let snapshot = snapshot else { return }
let image = snapshot.image
// отображаем в ячейке чата
}
Снимок рендерится асинхронно — не блокирует UI при быстром скролле.
Сроки
2–3 дня для разового местоположения со статическим превью. Живая трансляция с ForegroundService / background mode — 4–6 дней. Стоимость рассчитывается индивидуально.







