Avatar ·

Как получить текущие координаты пользователя на Swift (CLLocationManager)?

Всем привет! Я новичок в iOS разработке и никак не могу разобраться с получением текущего местоположения пользователя. Мне нужно получить широту и долготу, чтобы потом отправить их на сервер. Я подключил CoreLocation, добавил ключи в Info.plist (NSLocationWhenInUseUsageDescription), написал код, но координаты всегда приходят nil или nil. Вот мой код: ```swift import UIKit import CoreLocation class ViewController: UIViewController, CLLocationManagerDelegate { let locationManager = CLLocationManager() override func viewDidLoad() { super.viewDidLoad() locationManager.delegate = self locationManager.requestWhenInUseAuthorization() locationManager.startUpdatingLocation() if let location = locationManager.location { print("Lat: \(location.coordinate.latitude)") print("Lon: \(location.coordinate.longitude)") } else { print("Location is nil") } } } ``` Я запускаю симулятор, выбираю кастомную локацию (например, Apple), но в консоль всегда выводится "Location is nil". В чем проблема? Нужно ли ждать, пока определится местоположение? Или я неправильно настроил делегат? Подскажите, пожалуйста, как правильно получить координаты. Заранее спасибо! <<>>

Проблема: асинхронность и отсутствие обработки делегата

Ваш код не работает, потому что locationManager.location — это последнее известное местоположение, которое может быть nil, если менеджер еще не успел получить данные. Вызов startUpdatingLocation() запускает асинхронный процесс, и результат приходит в метод делегата didUpdateLocations. Вы пытаетесь прочитать координаты сразу после запуска, но система еще не успела их определить.

Правильное решение (пошагово)

  1. Запросить разрешение — вызываем requestWhenInUseAuthorization().
  2. Реализовать делегат — подписаться на протокол CLLocationManagerDelegate.
  3. Обработать результат — в методе didUpdateLocations получить массив объектов CLLocation.
  4. Остановить обновления — после получения координат вызвать stopUpdatingLocation(), чтобы сберечь батарею.

Исправленный код

import UIKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {
    
    let locationManager = CLLocationManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
    }
    
    // MARK: - CLLocationManagerDelegate
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        
        // Координаты получены
        let latitude = location.coordinate.latitude
        let longitude = location.coordinate.longitude
        print("Latitude: \(latitude), Longitude: \(longitude)")
        
        // Останавливаем обновления, чтобы не тратить заряд
        manager.stopUpdatingLocation()
        
        // Теперь можно отправить на сервер или использовать в UI
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("Ошибка получения локации: \(error.localizedDescription)")
    }
}

Важные пояснения

  • desiredAccuracy — задает точность. kCLLocationAccuracyBest — максимальная, но требует больше времени и энергии. Для большинства задач хватит kCLLocationAccuracyNearestTenMeters.
  • locations.last — массив содержит несколько точек, последняя — самая свежая.
  • didFailWithError — обязательно реализуйте этот метод, иначе не узнаете об ошибках (например, отключен GPS).
  • Симулятор — не забудьте задать кастомную локацию через меню Features → Location. Если не задать, координаты будут nil.

Дополнительно: проверка авторизации

Иногда пользователь запрещает доступ. Проверьте статус:

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    switch status {
    case .authorizedWhenInUse, .authorizedAlways:
        manager.startUpdatingLocation()
    case .denied, .restricted:
        print("Доступ к геолокации запрещен")
    default:
        break
    }
}

Итог

Главная ошибка — синхронное чтение locationManager.location. Всегда используйте делегат didUpdateLocations для получения координат. Не забывайте про stopUpdatingLocation() после получения результата.

<<>>

Альтернативный подход: использование одноразового запроса

Если вам нужно получить координаты только один раз (например, при нажатии кнопки), можно использовать метод requestLocation() вместо startUpdatingLocation(). Он автоматически останавливает обновления после получения результата и лучше подходит для простых сценариев.

import CoreLocation

class LocationManager: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()
    var completion: ((CLLocation?, Error?) -> Void)?
    
    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
    }
    
    func fetchLocation(completion: @escaping (CLLocation?, Error?) -> Void) {
        self.completion = completion
        manager.requestWhenInUseAuthorization()
        manager.requestLocation()
    }
    
    // MARK: - Delegate
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        completion?(locations.last, nil)
        completion = nil
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        completion?(nil, error)
        completion = nil
    }
}

Плюсы:

  • Не нужно вручную вызывать stopUpdatingLocation().
  • Однократный запрос — экономит батарею.
  • Удобно для кнопки "Показать мое местоположение".

Минусы:

  • Не подходит для отслеживания движения (например, навигация).
  • В симуляторе может вернуть ошибку, если не задана локация.

Используйте этот способ, если вам нужно "разово" узнать, где находится пользователь.