AutoCat/AutoCatCore/Utils/Location.swift

160 lines
6.2 KiB
Swift

import Foundation
import RxSwift
import RxCocoa
import CoreLocation
class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate {
private let generalErrors: [CLError.Code] = [.locationUnknown, .denied, .network, .headingFailure, .rangingUnavailable, .rangingFailure]
private let geocodingErrors: [CLError.Code] = [.geocodeCanceled, .geocodeFoundNoResult, .geocodeFoundPartialResult]
private(set) var authSubject = PublishSubject<CLAuthorizationStatus>()
private(set) var locationSubject = PublishSubject<CLLocation>()
init(locationManager: ParentObject) {
super.init(parentObject: locationManager, delegateProxy: RxLocationManagerDelegateProxy.self)
}
deinit {
print("deinit")
}
// MARK: - DelegateProxyType
static func registerKnownImplementations() {
self.register { RxLocationManagerDelegateProxy(locationManager: $0) }
}
static func currentDelegate(for object: CLLocationManager) -> CLLocationManagerDelegate? {
return object.delegate
}
static func setCurrentDelegate(_ delegate: CLLocationManagerDelegate?, to object: CLLocationManager) {
object.delegate = delegate
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.authSubject.onNext(status)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
self.locationSubject.onNext(location)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
guard let err = error as? CLError else { return }
if self.generalErrors.contains(err.code) {
// Pass general errors to all existing subjects
self.authSubject.onError(error)
self.locationSubject.onError(error)
} else if self.geocodingErrors.contains(err.code) {
// TODO: pass error to geocoding subject
} else {
print("Unexpected CoreLocation error: \(error)")
}
}
func getNewLocationSubject() -> PublishSubject<CLLocation> {
self.locationSubject = PublishSubject<CLLocation>()
return self.locationSubject
}
}
public class RxLocationManager {
private static let manager: CLLocationManager = {
let mgr = CLLocationManager()
mgr.desiredAccuracy = kCLLocationAccuracyBest
return mgr
}()
private static let bag = DisposeBag()
private static var eventObservable: Single<VehicleEvent>?
public private(set) static var lastEvent: VehicleEvent?
private static func checkPermissions() -> Single<Void> {
return Single<Void>.create { observer in
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse, .authorizedAlways:
observer(.success(()))
break
case .notDetermined:
self.manager.requestAlwaysAuthorization()
let proxy = RxLocationManagerDelegateProxy.proxy(for: self.manager)
_ = proxy.authSubject.filter{ $0 != .notDetermined }.first().subscribe(onSuccess: { result in
if let status = result, [.authorizedWhenInUse, .authorizedAlways].contains(status) {
observer(.success(()))
} else {
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"])))
}
}, onError: { observer(.error($0)) })
case .denied:
observer(.error(CLError(.denied)))
break
default:
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"])))
break
}
return Disposables.create()
}
}
private static func requestLocation() -> Single<VehicleEvent> {
let proxy = RxLocationManagerDelegateProxy.proxy(for: self.manager)
let single = proxy.getNewLocationSubject().take(1).asSingle().map { location -> VehicleEvent in
let event = VehicleEvent(lat: location.coordinate.latitude, lon: location.coordinate.longitude, speed: location.speed, dir: location.course)
self.lastEvent = event
return event
}
self.manager.requestLocation()
return single
}
public static func requestCurrentLocation() -> Single<VehicleEvent> {
if let result = self.eventObservable {
return result
} else {
self.eventObservable = self.checkPermissions().flatMap(self.requestLocation).do(onError: { _ in
self.eventObservable = nil
}, onDispose: {
self.eventObservable = nil
self.manager.stopUpdatingLocation()
})
return self.eventObservable!
}
}
public static func locationRequestInProgress() -> Bool {
return self.eventObservable != nil
}
public static func getAddressForLocation(latitude: Double, longitude: Double) -> Single<String> {
return Single.create { observer in
let geocoder = CLGeocoder()
let location = CLLocation(latitude: latitude, longitude: longitude)
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let error = error {
observer(.error(error))
} else if let placemark = placemarks?.first, let name = placemark.name {
observer(.success(name))
} else {
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Reverse geolocation error"])))
}
}
return Disposables.create {
geocoder.cancelGeocode()
}
}
}
public static func resetLastEvent() {
self.lastEvent = nil
}
}