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

iOS-位置更改时SwiftUI更新文本,ios,swift,swiftui,core-location,cllocationmanager,Ios,Swift,Swiftui,Core Location,Cllocationmanager,我与SwiftUI和CLLocationManager合作。这是我的位置模型: class LocationViewModel: NSObject, ObservableObject{ @Published var userLatitude: Double = 0 @Published var userLongitude: Double = 0 @Published var userTown: String = "" var objectWillCh

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

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

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

如果您只需直接在
文本
视图中访问视图模型属性,它将按照您想要的方式工作

最后的问题与反向地理编码有关。首先,反向地理编码请求异步完成。这意味着您不能
返回城镇
。同样,您可以直接更新
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
,我们也可以变得更聪明一点-在
getTown
中,将一个位置拆分为纬度/经度来创建
CLLocation
,是没有意义的。)


如果使用的是
self.objectWillChange.send()
,则希望视图仅在特定更改时刷新,而不是在每次更新时刷新。对于当前模型,您每次都在进行更新,因为属性也被标记为
@published
,我认为这不是必需的,另一件事是您不需要显式定义此依赖项
var objectWillChange=observeObjectPublisher()
。您可以在getTown函数中直接使用
self.objectWillChange.send()
。实际问题是lat和long正在更新吗?这很好,我可以在getTown()中看到城镇正在打印(在位置管理器中),但当我尝试在文本标签的主视图中打印它时,它似乎是空的。你知道那里发生了什么吗?你的
getTown
函数将始终返回空字符串,因为你在该函数中有
async
调用,在点击
CLGeocoder()后调用。reverseGeocodeLocation(location)
行将直接返回调用,并返回空字符串。因此,您的userTown始终是
。您可以直接在闭包块中使用
userTown
变量,而不是
Town
变量。

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