【问题标题】:Updated Reverse Geolocation更新了反向地理位置
【发布时间】:2021-10-20 14:03:41
【问题描述】:

有许多示例展示了如何进行反向地理定位,但最近没有在 SwiftUI 中实现。我当前的代码使用 iPhone GPS 生成与地图一起使用的坐标以显示位置。我还想显示街道地址,因为没有指示位置的文字的地图不是很有帮助。

我的问题:

  1. 我是否拥有实现反向地理定位的所有相关代码?
  2. 我已经看到使用故事板和打印语句来显示位置的示例,但是如何使用 @escaping 闭包将位置返回到 Swiftui 视图?
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)
        }
    }
}

如果我在 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

如何调用 getPlace?

【问题讨论】:

  • 我有实现反向地理定位的所有相关代码吗?当你运行它时,你得到了你期望的结果吗? 我见过使用故事板和打印语句来显示位置的示例,但是如何将位置返回到带有 @escape 闭包的 Swiftui 视图?您的意思是 @escaping
  • SwiftUI 不会更改反向地理编码的实现。您从ObservableObject 获取的数据通常应该是@Published,以便在更新时通知您。因此,您将currentAddress 定义为@Published var currentAddress: String = "",并且可以在视图结构中将其声明为@ObservedObject var locationManager = LocationManager(),如果您在多个地方使用它,请考虑将其设为单例或@EnvironmentObject

标签: swiftui ios14.5


【解决方案1】:

要使用以下代码,您需要设置适当的权利和授权。 这是一个在 swiftui 中使用地理定位的工作示例,来自我的代码 几年前网上的一些消息来源。 这应该为您提供在 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"
        }
    }
}

【讨论】:

  • 感谢workingdog,我想尝试一下,但它在启动时崩溃:由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:'无效参数不令人满意:!stayUp || CLClientIsBackgroundable(内部->fClient) || _CFMZEnabled()' 和 -[CLLocationManager setAllowsBackgroundLocationUpdates:] 中的断言失败,CLLocationManager.m:934。我添加了标准的 2 个隐私位置密钥。这是编译器设置吗?
  • 我通过发送密钥 self lm.allowsBackgroundLocationUpdate = true 来运行它
  • 好消息。如果这回答了您的问题,请您将答案标记为正确。
猜你喜欢
  • 2017-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多