waterkit-location 0.1.4

Cross-platform location and GPS services
Documentation
import Foundation
import CoreLocation

private let locationRequestTimeout: TimeInterval = 10.0

private final class LocationRequest: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()
    private var callback: ((LocationResult) -> Void)?
    private var timeout: DispatchWorkItem?
    private var keepAlive: LocationRequest?

    init(callback: @escaping (LocationResult) -> Void) {
        self.callback = callback
    }

    func start() {
        keepAlive = self
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            let servicesEnabled = CLLocationManager.locationServicesEnabled()
            DispatchQueue.main.async { [weak self] in
                self?.startOnMain(servicesEnabled: servicesEnabled)
            }
        }
    }

    private func startOnMain(servicesEnabled: Bool) {
        guard servicesEnabled else {
            finish(.ServiceDisabled)
            return
        }
        let status = manager.authorizationStatus
        guard status != .denied && status != .restricted else {
            finish(.PermissionDenied)
            return
        }

        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        let timeout = DispatchWorkItem { [weak self] in
            self?.finish(.Timeout)
        }
        self.timeout = timeout
        DispatchQueue.main.asyncAfter(
            deadline: .now() + locationRequestTimeout,
            execute: timeout
        )
        manager.requestLocation()
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else {
            finish(.NotAvailable)
            return
        }
        let timestampMs = Int64(location.timestamp.timeIntervalSince1970 * 1000)
        finish(.Success(LocationData(
            latitude: location.coordinate.latitude,
            longitude: location.coordinate.longitude,
            altitude: location.altitude,
            horizontal_accuracy: location.horizontalAccuracy,
            vertical_accuracy: location.verticalAccuracy,
            timestamp_ms: timestampMs
        )))
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        guard let code = (error as? CLError)?.code else {
            finish(.NotAvailable)
            return
        }
        switch code {
        case .denied:
            finish(.PermissionDenied)
        case .locationUnknown:
            break
        default:
            finish(.NotAvailable)
        }
    }

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        let status = manager.authorizationStatus
        if status == .denied || status == .restricted {
            finish(.PermissionDenied)
        }
    }

    private func finish(_ result: LocationResult) {
        guard let callback else {
            return
        }
        self.callback = nil
        timeout?.cancel()
        timeout = nil
        manager.delegate = nil
        callback(result)
        keepAlive = nil
    }
}

func get_current_location(callback: @escaping (LocationResult) -> Void) {
    DispatchQueue.main.async {
        LocationRequest(callback: callback).start()
    }
}