iOS - 位置更改时 SwiftUI 更新文本
Posted
技术标签:
【中文标题】iOS - 位置更改时 SwiftUI 更新文本【英文标题】:iOS - SwiftUI update text when location changes 【发布时间】:2021-03-08 09:59:26 【问题描述】:我正在使用 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 函数完成关闭时将更新后的变量传递到视图中。
【问题讨论】:
如果您使用self.objectWillChange.send()
,您希望视图仅在特定更改时刷新,而不是在每次更新时刷新。使用当前模型,您每次都在进行更新,因为属性也标记为@published
,我认为这不是必需的,另一件事是您不需要显式定义此依赖关系var objectWillChange = ObservableObjectPublisher()
。您可以在 getTown 函数中直接使用self.objectWillChange.send()
。实际问题是经纬度更新?
这一切都很好,我可以在 getTown() (在位置管理器内部)中看到正在打印的城镇,但是当我尝试在文本标签的主视图中打印它时,它就出现了为空。你知道那里发生了什么吗?
您的getTown
函数将始终返回空字符串,因为您在该函数内部有async
调用,在点击CLGeocoder().reverseGeocodeLocation(location)
行后调用将直接返回调用,并返回空字符串。所以你的 userTown 总是“”
。您可以直接在闭包块内使用userTown
变量,而不是Town
变量。
【参考方案1】:
你让事情变得比他们需要的更复杂。您不需要在模型中显式发布更改;属性标记为@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
更聪明一点 - 将位置拆分为纬度/经度只是为了在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
【讨论】:
以上是关于iOS - 位置更改时 SwiftUI 更新文本的主要内容,如果未能解决你的问题,请参考以下文章
@ObservedObject 更改后 SwiftUI 视图未更新
UITextField 未更新以更改 ObservableObject (SwiftUI)