160 lines
6.2 KiB
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
|
|
}
|
|
}
|