Skip to content

Instantly share code, notes, and snippets.

@rbresjer
Created July 13, 2017 12:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbresjer/43fb6e8639ef0b83e1b3a7cb20798612 to your computer and use it in GitHub Desktop.
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
//
// 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