Created
July 13, 2017 12:44
-
-
Save rbresjer/43fb6e8639ef0b83e1b3a7cb20798612 to your computer and use it in GitHub Desktop.
Get the current user location with a desired accuracy, or the best possible within a set time-out
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// LocationService.swift | |
// | |
// Created by Rutger Bresjer on 13/07/2017. | |
// Copyright © 2017 Woost. All rights reserved. | |
// | |
/* | |
Dependencies: | |
- RxSwift | |
- RxCocoa | |
- https://github.com/ReactiveX/RxSwift/blob/master/RxExample/Extensions/CLLocationManager%2BRx.swift | |
- https://github.com/ReactiveX/RxSwift/blob/master/RxExample/Extensions/RxCLLocationManagerDelegateProxy.swift | |
*/ | |
import Foundation | |
import RxSwift | |
import RxCocoa | |
import CoreLocation | |
/// Enum with errors emitted by the LocationService | |
/// | |
/// - accessDenied: User denied access to location (now or before) | |
/// - cannotDetermine: Unable to determine the user location | |
/// - timeOut: Determining user location timed out | |
enum LocationError: Error { | |
case accessDenied, cannotDetermine, timeOut | |
} | |
class LocationService { | |
private static let locationManager = CLLocationManager() | |
/// Get the current user location which either has the desired accuracy, or the best possible within the time-out. | |
/// | |
/// This method first checks if the app has authorization to access the user location, and if not, requests that first. | |
/// | |
/// - Parameters: | |
/// - accuracy: Desired accuracy in meters | |
/// - timeOut: Time-out in seconds | |
/// - Returns: Observable<CLLocation> containing the user location | |
static func bestLocation(accuracy: Double, timeOut: TimeInterval) -> Observable<CLLocation> { | |
switch CLLocationManager.authorizationStatus() { | |
case .authorizedWhenInUse, .authorizedAlways: | |
return _bestLocation(accuracy: accuracy, timeOut: timeOut) | |
case .restricted, .denied: | |
return Observable.error(LocationError.accessDenied) | |
case .notDetermined: | |
return locationManager.rx.didChangeAuthorizationStatus | |
.filter { $0 != .notDetermined } | |
.do( | |
onSubscribed: { locationManager.requestWhenInUseAuthorization() } | |
).flatMap { status -> Observable<CLLocation> in | |
switch status { | |
case .authorizedAlways, .authorizedWhenInUse: | |
return _bestLocation(accuracy: accuracy, timeOut: timeOut) | |
case .restricted, .denied, .notDetermined: | |
return Observable.error(LocationError.accessDenied) | |
} | |
} | |
} | |
} | |
/// Get the current user location which either has the desired accuracy, or the best possible within the time-out. | |
/// | |
/// - Parameters: | |
/// - accuracy: Desired accuracy in meters | |
/// - timeOut: Time-out in seconds | |
/// - Returns: Observable<CLLocation> containing the user location | |
private static func _bestLocation(accuracy: Double, timeOut: TimeInterval) -> Observable<CLLocation> { | |
let locations = locationManager.rx | |
.didUpdateLocations | |
.do( | |
onSubscribed: { locationManager.startUpdatingLocation() }, | |
onDispose: { locationManager.stopUpdatingLocation() } | |
).flatMap { Observable.from($0) } | |
.shareReplayLatestWhileConnected() | |
let timeOutLocation = Observable<Int> | |
.timer(timeOut, scheduler: MainScheduler.instance) | |
.withLatestFrom(locations) | |
.take(1) | |
let bestLocation = locations | |
.filter { $0.horizontalAccuracy <= accuracy } | |
.take(1) | |
return Observable | |
.merge(timeOutLocation, bestLocation) | |
.take(1) | |
.timeout(timeOut + 1, scheduler: MainScheduler.instance) // Add 1 so this timer will fire later than the 'best possible' timer | |
.catchError { error in | |
if let rxError = error as? RxError, case .timeout = rxError { | |
return Observable.error(LocationError.timeOut) | |
} else { | |
return Observable.error(LocationError.cannotDetermine) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment