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

问题描述

我正在使用 SwiftUI 和 CLLocationManager。这是我的 LocationModel:

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 函数完成关闭时将更新的变量传递到视图中。

解决方法

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

您没有在视图中看到更新的原因是因为您尝试使用计算属性来访问您的模型;这行不通。没有什么可以使用已发布的对模型属性的更改,也没有什么可以告诉视图它应该刷新。

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

您最后的问题与反向地理编码有关。首先,反向地理编码请求异步完成。这意味着您不能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)")
        }
    }
}

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

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

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


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
            }
        }
        
    }

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...