There are a number of examples showing how to do reverse geolocation, but nothing recent on implementation in SwiftUI. My current code uses the iPhone GPS to generate coordinates that are used with maps to show the location. I would also like to display the street address since a map without text indicating the location isn't very helpful.
My Questions:
import Foundation
import CoreLocation
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
@Published var currentAddress: String = ""
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.distanceFilter = 10 // distance before update (meters)
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.startUpdatingLocation()
}
func startLocationServices() {
if locationManager.authorizationStatus == .authorizedAlways || locationManager.authorizationStatus == .authorizedWhenInUse {
locationManager.startUpdatingLocation()
} else {
locationManager.requestWhenInUseAuthorization()
}
}
func getLocationCoordinates() -> (Double, Double) {
let coordinate = self.locationManager.location != nil ? self.locationManager.location!.coordinate : CLLocationCoordinate2D()
print("location = \(coordinate.latitude), \(coordinate.longitude)")
return (Double(coordinate.latitude), Double(coordinate.longitude))
}
// Using closure
func getAddress(handler: @escaping (String) -> Void)
{
self.currentAddress = ""
let coordinate = self.locationManager.location != nil ? self.locationManager.location!.coordinate : CLLocationCoordinate2D()
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark?
placeMark = placemarks?[0]
guard let placemark = placemarks?.first else { return }
if let streetNumber = placemark.subThoroughfare,
let street = placemark.subThoroughfare,
let city = placemark.locality,
let state = placemark.administrativeArea {
DispatchQueue.main.async {
self.currentAddress = "\(streetNumber) \(street) \(city) \(state)"
}
} else if let city = placemark.locality, let state = placemark.administrativeArea {
DispatchQueue.main.async {
self.currentAddress = "\(city) \(state)"
}
} else {
DispatchQueue.main.async {
self.currentAddress = "Address Unknown"
}
}
}
)
print( self.currentAddress)
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if locationManager.authorizationStatus == .authorizedAlways || locationManager.authorizationStatus == .authorizedWhenInUse {
locationManager.startUpdatingLocation()
}
}
// Get Placemark
func getPlace(for location: CLLocation,
completion: @escaping (CLPlacemark?) -> Void) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemarks, error in
guard error == nil else {
print("*** Error in \(#function): \(error!.localizedDescription)")
completion(nil)
return
}
guard let placemark = placemarks?[0] else {
print("*** Error in \(#function): placemark is nil")
completion(nil)
return
}
completion(placemark)
}
}
}
If I add the follow code say in ContentView:
@State private var entryLat: Double = 0.0
@State private var entryLong: Double = 0.0
let result = lm.getLocationCoordinates()
entryLat = result.0
entryLong = result.1
How would I call getPlace?
To use the following code you need to setup the appropriate entitlements and authorizations. Here is a working example of using geolocation in swiftui, from code I got from a number of sources on the.net years ago. This should give you a base to do reverse geolocation in swiftui:
import Foundation
import CoreLocation
import SwiftUI
import Combine
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
let locationProvider = LocationProvider()
@State var currentAddress = ""
var body: some View {
Text(currentAddress)
.onAppear {
getAddress()
}
}
func getAddress() {
// for testing Tokyo
let location = CLLocation(latitude: 35.684602, longitude: 139.751992)
locationProvider.getPlace(for: location) { plsmark in
guard let placemark = plsmark else { return }
if let streetNumber = placemark.subThoroughfare,
let street = placemark.subThoroughfare,
let city = placemark.locality,
let state = placemark.administrativeArea {
self.currentAddress = "\(streetNumber) \(street) \(city) \(state)"
} else if let city = placemark.locality, let state = placemark.administrativeArea {
self.currentAddress = "\(city) \(state)"
} else {
self.currentAddress = "Address Unknown"
}
}
}
}
/**
A Combine-based CoreLocation provider.
On every update of the device location from a wrapped `CLLocationManager`,
it provides the latest location as a published `CLLocation` object and
via a `PassthroughSubject<CLLocation, Never>` called `locationWillChange`.
*/
public class LocationProvider: NSObject, ObservableObject {
private let lm = CLLocationManager()
/// Is emitted when the `location` property changes.
public let locationWillChange = PassthroughSubject<CLLocation, Never>()
/**
The latest location provided by the `CLLocationManager`.
Updates of its value trigger both the `objectWillChange` and the `locationWillChange` PassthroughSubjects.
*/
@Published public private(set) var location: CLLocation? {
willSet {
locationWillChange.send(newValue ?? CLLocation())
}
}
/// The authorization status for CoreLocation.
@Published public var authorizationStatus: CLAuthorizationStatus?
/// A function that is executed when the `CLAuthorizationStatus` changes to `Denied`.
public var onAuthorizationStatusDenied : ()->Void = {presentLocationSettingsAlert()}
/// The LocationProvider intializer.
///
/// Creates a CLLocationManager delegate and sets the CLLocationManager properties.
public override init() {
super.init()
self.lm.delegate = self
self.lm.desiredAccuracy = kCLLocationAccuracyBest
self.lm.activityType = .fitness
self.lm.distanceFilter = 10
self.lm.allowsBackgroundLocationUpdates = true
self.lm.pausesLocationUpdatesAutomatically = false
self.lm.showsBackgroundLocationIndicator = true
}
/**
Request location access from user.
In case, the access has already been denied, execute the `onAuthorizationDenied` closure.
The default behavior is to present an alert that suggests going to the settings page.
*/
public func requestAuthorization() -> Void {
if self.authorizationStatus == CLAuthorizationStatus.denied {
onAuthorizationStatusDenied()
}
else {
self.lm.requestWhenInUseAuthorization()
}
}
/// Start the Location Provider.
public func start() throws -> Void {
self.requestAuthorization()
if let status = self.authorizationStatus {
guard status == .authorizedWhenInUse || status == .authorizedAlways else {
throw LocationProviderError.noAuthorization
}
}
else {
/// no authorization set by delegate yet
#if DEBUG
print(#function, "No location authorization status set by delegate yet. Try to start updates anyhow.")
#endif
/// In principle, this should throw an error.
/// However, this would prevent start() from running directly after the LocationProvider is initialized.
/// This is because the delegate method `didChangeAuthorization`,
/// setting `authorizationStatus` runs only after a brief delay after initialization.
//throw LocationProviderError.noAuthorization
}
self.lm.startUpdatingLocation()
}
/// Stop the Location Provider.
public func stop() -> Void {
self.lm.stopUpdatingLocation()
}
// todo deal with errors
public func getPlace(for location: CLLocation, completion: @escaping (CLPlacemark?) -> Void) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemarks, error in
guard error == nil else {
print("=====> Error \(error!.localizedDescription)")
completion(nil)
return
}
guard let placemark = placemarks?.first else {
print("=====> Error placemark is nil")
completion(nil)
return
}
completion(placemark)
}
}
}
/// Present an alert that suggests to go to the app settings screen.
public func presentLocationSettingsAlert(alertText : String? = nil) -> Void {
let alertController = UIAlertController (title: "Enable Location Access", message: alertText ?? "The location access for this app is set to 'never'. Enable location access in the application settings. Go to Settings now?", preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let settingsUrl = URL(string:UIApplication.openSettingsURLString) else {
return
}
UIApplication.shared.open(settingsUrl)
}
alertController.addAction(settingsAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alertController.addAction(cancelAction)
UIApplication.shared.windows[0].rootViewController?.present(alertController, animated: true, completion: nil)
}
/// Error which is thrown for lacking localization authorization.
public enum LocationProviderError: Error {
case noAuthorization
}
extension LocationProvider: CLLocationManagerDelegate {
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.authorizationStatus = status
#if DEBUG
print(#function, status.name)
#endif
//print()
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
self.location = location
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
if let clErr = error as? CLError {
switch clErr {
case CLError.denied : do {
print(#function, "Location access denied by user.")
self.stop()
self.requestAuthorization()
}
case CLError.locationUnknown : print(#function, "Location manager is unable to retrieve a location.")
default: print(#function, "Location manager failed with unknown CoreLocation error.")
}
}
else {
print(#function, "Location manager failed with unknown error", error.localizedDescription)
}
}
}
extension CLAuthorizationStatus {
/// String representation of the CLAuthorizationStatus
var name: String {
switch self {
case .notDetermined: return "notDetermined"
case .authorizedWhenInUse: return "authorizedWhenInUse"
case .authorizedAlways: return "authorizedAlways"
case .restricted: return "restricted"
case .denied: return "denied"
default: return "unknown"
}
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.