Instantly share code, notes, and snippets.
Last active
June 21, 2022 07:37
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
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