Skip to content

Instantly share code, notes, and snippets.

@notoroid
Last active June 21, 2022 07:37
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 notoroid/98144cbea71421c599dbfd76e6656237 to your computer and use it in GitHub Desktop.
Save notoroid/98144cbea71421c599dbfd76e6656237 to your computer and use it in GitHub Desktop.
import SwiftUI
import Foundation
import CoreLocation
import Combine
class LocationManager: ObservableObject {
enum LocationManagerError: Error, LocalizedError {
case deniedLocation; case restrictedLocation; case unknown
var errorDescription: String? {
switch self {
case .deniedLocation: return "Location information is not allowed. Please allow Settings - Privacy to retrieve the location of your app."
case .restrictedLocation: return "Location information is not allowed by the constraints specified on the device."
case .unknown: return "An unknown error has occurred."
}
}
}
typealias AuthorizationStatusContinuation = CheckedContinuation<CLAuthorizationStatus, Never> // Continuation for asymc/await
fileprivate class DelegateAdaptorForAuthorization: NSObject, CLLocationManagerDelegate {
var continuation: AuthorizationStatusContinuation?
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
continuation?.resume(returning: manager.authorizationStatus)
}
}
fileprivate class DelegateAdaptorForLocation: NSObject, CLLocationManagerDelegate {
var currentLocation: PassthroughSubject<CurrentLocationStatus, Never> = .init()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let newLocation = locations.last else { return }
currentLocation.send(CurrentLocationStatus(active: true, location: newLocation))
}
}
fileprivate let delegateAdaptorForLocation: DelegateAdaptorForLocation = .init()
fileprivate let locationManager: CLLocationManager = .init()
@Published var currentLocation: CurrentLocationStatus = .init(active: false, location: .init())
struct CurrentLocationStatus: Equatable {
var active: Bool; var location: CLLocation
}
var anyCancelableCollection = Set<AnyCancellable>()
func startCurrentLocation() async throws {
let authorizationStatus: CLAuthorizationStatus
if locationManager.authorizationStatus == .notDetermined {
let delegateAdaptor = DelegateAdaptorForAuthorization()
locationManager.delegate = delegateAdaptor
authorizationStatus = await withCheckedContinuation { (continuation: AuthorizationStatusContinuation) in
delegateAdaptor.continuation = continuation
locationManager.requestWhenInUseAuthorization()
}
} else {
authorizationStatus = locationManager.authorizationStatus
}
locationManager.delegate = delegateAdaptorForLocation
delegateAdaptorForLocation.currentLocation.sink { [unowned self] location in
if self.currentLocation.location.distance(from: location.location) < 5 /*distance 5metre over*/ {
return
}
self.currentLocation = location
}.store(in: &anyCancelableCollection)
locationManager.startUpdatingLocation()
switch authorizationStatus {
case .notDetermined: break
case .denied: throw LocationManagerError.deniedLocation
case .authorizedAlways, .authorizedWhenInUse: break
case .restricted: throw LocationManagerError.restrictedLocation
default: throw LocationManagerError.unknown
}
}
}
@main
struct SimpleLocationAuthorizationApp: App {
@StateObject var locationManager: LocationManager = .init()
var body: some Scene {
WindowGroup {
ContentView(locationManager: locationManager)
}
}
}
struct ContentView: View {
@ObservedObject var locationManager: LocationManager
@State var errorMessage: String = ""
@State var showErrorAlert: Bool = false
var body: some View {
VStack {
Image(systemName: "location.circle.fill").imageScale(.large).foregroundColor(.accentColor)
Text("Obtaining location information...")
}.task {
do {
try await locationManager.startCurrentLocation()
} catch let error {
errorMessage = error.localizedDescription
showErrorAlert = true
}
}.alert(errorMessage, isPresented: $showErrorAlert) {
Button("OK", role: .cancel, action: { errorMessage = ""})
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment