繁体   English   中英

iOS - SwiftUI 在位置更改时更新文本

[英]iOS - SwiftUI update text when location changes

我正在使用 SwiftUI 和 CLLocationManager。 这是我的位置模型:

class LocationViewModel: NSObject, ObservableObject{
  
  @Published var userLatitude: Double = 0
  @Published var userLongitude: Double = 0
  @Published var userTown: String = ""
    var objectWillChange = ObservableObjectPublisher()
  
  private let locationManager = CLLocationManager()
  
  override init() {
    super.init()
    self.locationManager.delegate = self
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
    self.locationManager.requestWhenInUseAuthorization()
    self.locationManager.startUpdatingLocation()
  }
}

extension LocationViewModel: CLLocationManagerDelegate {
    
    struct ReversedGeoLocation {
        let name: String            // eg. Apple Inc.
        let streetName: String      // eg. Infinite Loop
        let streetNumber: String    // eg. 1
        let city: String            // eg. Cupertino
        let state: String           // eg. CA
        let zipCode: String         // eg. 95014
        let country: String         // eg. United States
        let isoCountryCode: String  // eg. US

        var formattedAddress: String {
            return """
            \(name),
            \(streetNumber) \(streetName),
            \(city), \(state) \(zipCode)
            \(country)
            """
        }

        // Handle optionals as needed
        init(with placemark: CLPlacemark) {
            self.name           = placemark.name ?? ""
            self.streetName     = placemark.thoroughfare ?? ""
            self.streetNumber   = placemark.subThoroughfare ?? ""
            self.city           = placemark.locality ?? ""
            self.state          = placemark.administrativeArea ?? ""
            self.zipCode        = placemark.postalCode ?? ""
            self.country        = placemark.country ?? ""
            self.isoCountryCode = placemark.isoCountryCode ?? ""
        }
    }
  
  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.last else { return }
    userLatitude = location.coordinate.latitude
    userLongitude = location.coordinate.longitude
    userTown = getTown(lat: CLLocationDegrees.init(userLatitude), long: CLLocationDegrees.init(userLongitude))

    print(location)
  }
    
    func getTown(lat: CLLocationDegrees, long: CLLocationDegrees) -> String
    {
        var town = ""
        let location = CLLocation.init(latitude: lat, longitude: long)
        CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in

            guard let placemark = placemarks?.first else {
                let errorString = error?.localizedDescription ?? "Unexpected Error"
                print("Unable to reverse geocode the given location. Error: \(errorString)")
                return
            }

            let reversedGeoLocation = ReversedGeoLocation(with: placemark)
            print(reversedGeoLocation.city)
            
            town = reversedGeoLocation.city
            self.objectWillChange.send()
        }
        
        return town
    }
}

现在我想显示当前坐标和城市,但只是没有显示城市,似乎变量没有正确更新。 怎么做? 这是我的看法:

@ObservedObject var locationViewModel = LocationViewModel()

    var latitude: Double  { return(locationViewModel.userLatitude ) }
    var longitude: Double { return(locationViewModel.userLongitude ) }
    var town: String { return(locationViewModel.userTown) }

    var body: some View {
        VStack {
            Text("Town: \(town)")
            Text("Latitude: \(latitude)")
            Text("Longitude: \(longitude)")
        }
    }

我不完全了解如何在位置更改或 getTown 函数完成时将更新的变量传递到视图中。

你让事情变得比他们需要的更复杂。 您无需在 model 中显式发布更改; 属性标记为@Published ,因此更改它们将自动触发属性更改。

您没有在视图中看到更新的原因是您尝试使用计算属性来访问您的 model; 这行不通。 没有任何东西可以消耗你的 model 属性的已发布更改,也没有任何东西可以告诉视图它应该刷新。

如果您只是直接在您的Text视图中访问视图 model 属性,它将按照您想要的方式工作。

您的最终问题与反向地理编码有关。 首先,反向地理编码请求异步完成。 这意味着你不能return town 同样,您可以简单地直接更新userTown属性,将其分派到主队列,因为您不能保证将在主队列上调用反向地理编码处理程序,并且必须在主队列上执行所有 UI 更新。

把所有这些放在一起得到

class LocationViewModel: NSObject, ObservableObject{
  
  @Published var userLatitude: Double = 0
  @Published var userLongitude: Double = 0
  @Published var userTown: String = ""
  
  private let locationManager = CLLocationManager()
  
  override init() {
    super.init()
    self.locationManager.delegate = self
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
    self.locationManager.requestWhenInUseAuthorization()
    self.locationManager.startUpdatingLocation()
  }
}

extension LocationViewModel: CLLocationManagerDelegate {
    
    struct ReversedGeoLocation {
        let name: String            // eg. Apple Inc.
        let streetName: String      // eg. Infinite Loop
        let streetNumber: String    // eg. 1
        let city: String            // eg. Cupertino
        let state: String           // eg. CA
        let zipCode: String         // eg. 95014
        let country: String         // eg. United States
        let isoCountryCode: String  // eg. US

        var formattedAddress: String {
            return """
            \(name),
            \(streetNumber) \(streetName),
            \(city), \(state) \(zipCode)
            \(country)
            """
        }

        // Handle optionals as needed
        init(with placemark: CLPlacemark) {
            self.name           = placemark.name ?? ""
            self.streetName     = placemark.thoroughfare ?? ""
            self.streetNumber   = placemark.subThoroughfare ?? ""
            self.city           = placemark.locality ?? ""
            self.state          = placemark.administrativeArea ?? ""
            self.zipCode        = placemark.postalCode ?? ""
            self.country        = placemark.country ?? ""
            self.isoCountryCode = placemark.isoCountryCode ?? ""
        }
    }
  
  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.last else { return }
    userLatitude = location.coordinate.latitude
    userLongitude = location.coordinate.longitude
    getTown(lat: CLLocationDegrees.init(userLatitude), long: CLLocationDegrees.init(userLongitude))

    print(location)
  }
    
    func getTown(lat: CLLocationDegrees, long: CLLocationDegrees) -> Void
    {
        let location = CLLocation.init(latitude: lat, longitude: long)
        CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in

            guard let placemark = placemarks?.first else {
                let errorString = error?.localizedDescription ?? "Unexpected Error"
                print("Unable to reverse geocode the given location. Error: \(errorString)")
                return
            }

            let reversedGeoLocation = ReversedGeoLocation(with: placemark)
            print(reversedGeoLocation.city)
            DispatchQueue.main.async {
                self.userTown = reversedGeoLocation.city
            }
        }

    }
}

struct ContentView: View {
    @ObservedObject var locationViewModel = LocationViewModel()
    
    var body: some View {
        VStack {
            Text("Town: \(locationViewModel.userTown)")
            Text("Latitude: \(locationViewModel.userLatitude)")
            Text("Longitude: \(locationViewModel.userLongitude)")
        }
    }
}

反向地理编码的最后一个问题是它受到速率限制。 在开始出现错误之前,您只能在一段时间内调用它多次。 位置更新大约每秒到达一次,即使您没有移动也是如此。 大多数情况下,您将不必要地查找相同或几乎相同的位置。

一种方法是检查自上次反向位置查找以来经过的距离,并且仅在超过某个阈值(例如 500m)时才执行新查找

(我们也可以使用getTown更聪明一些 - 将位置拆分为纬度/经度只是为了在getTown中创建一个CLLocation是没有意义的)


private var lastTownLocation: CLLocation? = nil

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        userLatitude = location.coordinate.latitude
        userLongitude = location.coordinate.longitude
        
        if self.lastTownLocation == nil || self.lastTownLocation!.distance(from: location) > 500 {
            getTown(location)
        }
    }
    
    func getTown(_ location: CLLocation) -> Void
    {
        CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
            
            guard let placemark = placemarks?.first else {
                let errorString = error?.localizedDescription ?? "Unexpected Error"
                print("Unable to reverse geocode the given location. Error: \(errorString)")
                return
            }
            self.lastTownLocation = location
            let reversedGeoLocation = ReversedGeoLocation(with: placemark)
            print(reversedGeoLocation.city)
            DispatchQueue.main.async {
                self.userTown = reversedGeoLocation.city
            }
        }
        
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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