简体   繁体   中英

Updated Reverse Geolocation

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:

  1. Do I have all the relevant code to implement reverse geolocation?
  2. I have seen examples using storyboards and print statements to display the location, but how do I return the location to a Swiftui view with an @escaping closure?
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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM