How to get CLPlacemark from Location in SwiftLocation 5.1.0?

428 views Asked by At

This is a library that doesn't have a very great documentation, In the past, I had problems migrating from a previous version to a modern version, and now, I have exactly the same problem again. In SwiftLocation 4 this is the way to get the placemark from a location, passing it the coordinates of that location:

SwiftLocation.LocationManager.shared.locateFromCoordinates(location.coordinate) { result in
    switch result {
    case .success(let places):
        guard let receivedPlacemark = places.first?.placemark else {
            return
        }
        logger.debug("Retrieved placemark: (receivedPlacemark.locality ?? "-")")
        self?.currentPlacemark = receivedPlacemark

        NotificationCenter.default.post(name: Constants.Notifications.placemarkUpdateNotification, object: nil)
    case .failure(let error):
        logger.error(error.localizedDescription)
        NotificationCenter.default.post(name: Constants.Notifications.placemarkUpdateNotification, object: nil)
    }
}

Now, after upgrading to 5.1.0 version of SwiftLocation I simply can't find in GitHub or the cocoapods documentation of the library how to do the same with the actual version. SwiftLocation.LocationManager.shared.locateFromCoordinates doesn't exists and can't find anything similar.

How can I do that with 5.1.0?

2

There are 2 answers

9
ferus On BEST ANSWER

The process name for what you are trying to achieve is Geocoding.

From what I can see the location API changed a bit, so the code you are looking for is (from documentation):

let service = // insert your service here
SwiftLocation.geocodeWith(service).then { result in
    // You will get an array of suggested [GeoLocation] objects with data and coordinates
    print(result.data)
}

where service should equal to your service of choosing, but if we want to use Apple engine to get data for given coordinates, you can try:

let service = Geocoder.Apple(coordinates: location) //location must be CLLocationCoordinate2D
0
iOSDevZF On

Had a similar problem and this is what worked for me:

Inside your LocationManager create an optional CLPlacemark object:

@Published var placemark: CLPlacemark?

Then, in your didUpdateLocations func (still inside LocationManager) create a CLGeoCoder that uses your users location like so:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  guard let location = locations.last else { return }
  self.userLocation = location
                    
  let geoCoder = CLGeocoder()
  geoCoder.reverseGeocodeLocation(userLocation!) { placemarks, error in
    self.placemark = placemarks?.last
  }
}

LocationManager Code in full:

import CoreLocation
import Foundation
        
class LocationManager: NSObject, ObservableObject {
  static let shared = LocationManager()
            
  private let manager = CLLocationManager()
            
  @Published var userLocation: CLLocation?
  @Published var authorizationState: CLAuthorizationStatus?
            
  @Published var placemark: CLPlacemark?
        
  override init() {
    super.init()
    manager.delegate = self
    manager.desiredAccuracy = kCLLocationAccuracyBest
  }
            
  func requestLocation() {
    manager.requestWhenInUseAuthorization()
  }
}
        
extension LocationManager: CLLocationManagerDelegate {
  func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
    self.authorizationState = manager.authorizationStatus
                
    if manager.authorizationStatus == .authorizedAlways || manager.authorizationStatus == .authorizedWhenInUse {
      manager.startUpdatingLocation()
    } else if manager.authorizationStatus == .denied {
      print("Permission to location was denied")
    }
  }
  
  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.last else { return }
    self.userLocation = location
                    
    let geoCoder = CLGeocoder()
    geoCoder.reverseGeocodeLocation(userLocation!) { placemarks, error in
      self.placemark = placemarks?.last
    }
  }
}

You can use the above to pass in your UI like so:

struct ContentView: View {
  @ObservedObject var locationManager = LocationManager.shared
        
  var body: some View {
    VStack {
      Text(locationManager.placemark?.locality ?? "Unknown Location")
        .font(.system(size: 40, design: .rounded)).bold()
    }
  }
}