import Foundation import RxSwift import RxCocoa import CoreLocation class RxLocationManagerDelegateProxy: DelegateProxy, 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() private(set) var locationSubject = PublishSubject() 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 { self.locationSubject = PublishSubject() 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? public private(set) static var lastEvent: VehicleEvent? private static func checkPermissions() -> Single { return Single.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(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"]))) } }, onFailure: { observer(.failure($0)) }) case .denied: observer(.failure(CLError(.denied))) break default: observer(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"]))) break } return Disposables.create() } } private static func requestLocation() -> Single { 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) self.lastEvent = event return event } self.manager.requestLocation() return single } public static func requestCurrentLocation() -> Single { 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 { 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(.failure(error)) } else if let placemark = placemarks?.first, let name = placemark.name { observer(.success(name)) } else { observer(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Reverse geolocation error"]))) } } return Disposables.create { geocoder.cancelGeocode() } }.timeout(.seconds(20), scheduler: MainScheduler.instance) } public static func resetLastEvent() { self.lastEvent = nil } }