Skip to content

Instantly share code, notes, and snippets.

@gromwel
Last active March 4, 2021 16:33
Show Gist options
  • Save gromwel/6e7d9f464c1ad93009d18feb305c7373 to your computer and use it in GitHub Desktop.
Save gromwel/6e7d9f464c1ad93009d18feb305c7373 to your computer and use it in GitHub Desktop.
Generic Location Manager [ReactiveSwift]
import Foundation
import ReactiveSwift
import CoreLocation
final class LocationManager {
// MARK: - Интерфейс
// Синглтон
static let shared = LocationManager()
// Локация
lazy var location = self.locationPipe.output
private let locationPipe = Signal<CLLocationCoordinate2D, Never>.pipe()
// Запрос локации
func requestLocation() {
switch self.locationManager.authorizationStatus {
case .notDetermined:
// Не решил - спросим
self.requestAuthorizationAction.apply().start()
case .restricted:
// Не разрешено - отваливаемся
break
case .denied:
// Отказал - отваливаемся
break
case .authorizedAlways:
// Разрешил - запросим локацию
self.requestLocationAction.apply().start()
case .authorizedWhenInUse:
// Разрешил - запросим локацию
self.requestLocationAction.apply().start()
@unknown default:
// Новый вариант - отваливаемся
break
}
}
// MARK: - Инициализация
// Приватная что бы нельзя было просто так взять и инициализировать класс. Надо использовать .shared
private init() {
// Следим за разрешением
self.requestAuthorizationAction
.values
.observeValues { [weak self] allowed in
// Разворачиваем
guard let self = self else { return }
if allowed {
// Разрешили - запрашиваем локацию
self.requestLocationAction.apply().start()
} else {
// Запретили - отваливаемся
}
}
// Следим за локацией
self.requestLocationAction
.values
.observeValues { [weak self] coordinate in
// Разворачиваем
guard let self = self else { return }
// Получили локацию - радуемся
self.locationPipe.input.send(value: coordinate)
}
// Следим за ошибкой локации
self.requestLocationAction
.errors
.observeValues { error in
// Получили ошибку - отваливаемся
}
}
// MARK: - Свойства
// Менеджер
private lazy var locationManager: CLLocationManager = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
manager.delegate = self.locationDelegate
return manager
}()
// Делегат
private lazy var locationDelegate = LocationDelegate()
// MARK: - Экшены
// Экшн нужен для того что бы получение локации было вызвано один раз
private lazy var requestLocationAction = Action<Void, CLLocationCoordinate2D, Swift.Error> { [weak self] in
SignalProducer { observer, _ in
guard let self = self else { observer.sendCompleted(); return }
// Следим за получением локации
self.locationDelegate.serviceLocationPipe.output
.take(first: 1)
.observeResult { [weak self] result in
// Завершим при выходе
defer { observer.sendCompleted() }
// Разворачиваем
guard let self = self else { return }
// Тормозим обновление
self.locationManager.stopUpdatingLocation()
// Смотрим результат
switch result {
case .success(let coordinate):
observer.send(value: coordinate)
case .failure(let error):
observer.send(error: error)
}
}
// Запускаем обновление локации
self.locationManager.startUpdatingLocation()
}
}
// Экшн нужен для того что бы запрос авторизации был вызван один раз
private lazy var requestAuthorizationAction = Action<Void, Bool, Never> { [weak self] in
SignalProducer { observer, _ in
// Разворачиваем
guard let self = self else { observer.sendCompleted(); return }
// Подписываемся на изменение статуса авторизации
self.locationDelegate.serviceAuthorizationPipe.output
.map { $0.isAllowed }
.observeValues { allowed in
// Возвращаем результаты аторизации
observer.send(value: allowed)
}
// Запрос авторизации
self.locationManager.requestAlwaysAuthorization()
}
}
}
// Делегат отдельным классом иначе методы CLLocationManagerDelegate видны извне LocationManager
fileprivate class LocationDelegate: NSObject, CLLocationManagerDelegate {
// MARK: - Сигналы
// Труба для локации
let serviceLocationPipe = Signal<CLLocationCoordinate2D, Swift.Error>.pipe()
// Труба для статуса авторизации
let serviceAuthorizationPipe = Signal<CLAuthorizationStatus, Never>.pipe()
// MARK: - CLLocationManagerDelegate
// Меняется статус доступа
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
self.serviceAuthorizationPipe.input.send(value: manager.authorizationStatus)
}
// Новое локация
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
self.serviceLocationPipe.input.send(value: location.coordinate)
}
// Ошибка при получении локации
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
self.serviceLocationPipe.input.send(error: error)
}
}
fileprivate extension CLAuthorizationStatus {
var isAllowed: Bool {
switch self {
case .notDetermined: return false
case .restricted: return false
case .denied: return false
case .authorizedAlways: return true
case .authorizedWhenInUse: return true
@unknown default: return false
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment