In the code below I ask the server for the popuplation rate for the city the user is current in via HTTP request.
Everything works as expected except that I'm getting a purple warning when I save lastSearchedCity
and lastSearchedPopulationRate
to UserDefaults
inside the http synchronous function call via @AppStorage
. Again, I get the right info from the server and everything seem to be saving to UserDefaults, the only issue is the purple warning.
Purple Warning:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
I tried wraping self.lastSearchedCity = city
and self.lastSearchedPopulationRate = pRate
inside DispatchQueue.main.async {}
but I'm afraid this is more then that since the compiler suggest using the receive(on:)
operator but I'm not sure how to implement it.
if let pRate = populationRate{
self.lastSearchedCity = city // purple warning points to this line
self.lastSearchedPopulationRate = pRate // purple warning points to this line
}
What would be the right way to solve this warning?
Core Location
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
@AppStorage("kLastSearchedCity")private var lastSearchedCity = ""
@AppStorage("kLastSearchedPopulationRate")private var lastSearchedPopulationRate = ""
@Published var locationStatus: CLAuthorizationStatus?
var hasFoundOnePlacemark:Bool = false
let httpRequestor = HttpPopulationRateRequestor()
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
var statusString: String {
guard let status = locationStatus else {
return "unknown"
}
switch status {
case .notDetermined: return "notDetermined"
case .authorizedWhenInUse: return "authorizedWhenInUse"
case .authorizedAlways: return "authorizedAlways"
case .restricted: return "restricted"
case .denied: return "denied"
default: return "unknown"
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
locationStatus = status
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
hasFoundOnePlacemark = false
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)-> Void in
if error != nil {
self.locationManager.stopUpdatingLocation()
if placemarks!.count > 0 {
if !self.hasFoundOnePlacemark{
self.hasFoundOnePlacemark = true
let placemark = placemarks![0]
let city:String = placemark.locality ?? ""
let zipCode:String = placemark.postalCode ?? ""
// make request
if city != self.lastSearchedCity{
// asynchronous function call
self.httpRequestor.populationRateForCurrentLocation(zipCode: zipCode) { (populationRate) in
if let pRate = populationRate{
self.lastSearchedCity = city // purple warning points to this line
self.lastSearchedPopulationRate = pRate // purple warning points to this line
}
}
}
}
self.locationManager.stopUpdatingLocation()
}else{
print("No placemarks found.")
}
})
}
}
SwiftUI - For reference only
struct ContentView: View {
@StateObject var locationManager = LocationManager()
@AppStorage("kLastSearchedCity")private var lastSearchedCity = ""
@AppStorage("kLastSearchedPopulationRate")private var lastSearchedPopulationRate = ""
var body: some View {
VStack {
Text("Location Status:")
.font(.callout)
Text("Location Status: \(locationManager.statusString)")
.padding(.bottom)
Text("Population Rate:")
.font(.callout)
HStack {
Text("\(lastSearchedCity)")
.font(.title2)
Text(" \(lastSearchedPopulationRate)")
.font(.title2)
}
}
}
}
HTTP Request class
class HttpPopulationRateRequestor{
let customKeyValue = "ryHGehesdorut$=jfdfjd"
let customKeyName = "some-key"
func populationRateForCurrentLocation(zipCode: String, completion:@escaping(_ populationRate:String?) -> () ){
print("HTTP Request: Asking server for population rate for current location...")
let siteLink = "http://example.com/some-folder/" + zipCode
let url = URL(string: siteLink)
var request = URLRequest(url: url!)
request.setValue(customKeyValue, forHTTPHeaderField: customKeyName)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
print("ERROR: \(error!)")
completion(nil)
return
}
guard let data = data else {
print("Data is empty")
completion(nil)
return
}
let json = try! JSONSerialization.jsonObject(with: data, options: [])
guard let jsonArray = json as? [[String: String]] else {
return
}
if jsonArray.isEmpty{
print("Array is empty...")
return
}else{
let rate = jsonArray[0]["EstimatedRate"]!
let rateAsDouble = Double(rate)! * 100
completion(String(rateAsDouble))
}
}
task.resume()
}
}