I am working with a view that displays a list of locations. When a user taps on a location, a didSet
block containing a Task
is triggered in a separate class wrapped with the @ObservedObject
property:
struct LocationSearch: View {
@StateObject var locationService = LocationService()
@ObservedObject var networking: Networking
var savedLocations = SavedLocations()
var body: some View {
ForEach(locationService.searchResults, id: \.self) { location in
Button(location.title) {
getCoordinate(addressString: location.title) { coordinates, error in
if error == nil {
networking.lastLocation = CLLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
// wait for networking.locationString to update
// this smells wrong
// how to better await Task completion in didSet?
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
savedLocations.all.append(networking.locationString!.locality!)
UserDefaults.standard.set(savedLocations.all, forKey: "savedLocations")
dismiss()
}
}
}
}
}
}
}
The Task
that gets triggered after networking.lastLocation
is set is as follows:
class Networking: NSObject, ObservableObject, CLLocationManagerDelegate {
@Published public var lastLocation: CLLocation? {
didSet {
Task {
getLocationString() { placemark in
self.locationString = placemark
}
getMainWeather(self.lastLocation?.coordinate.latitude ?? 0, self.lastLocation?.coordinate.longitude ?? 0)
getAQI(self.lastLocation?.coordinate.latitude ?? 0, self.lastLocation?.coordinate.longitude ?? 0)
locationManager.stopUpdatingLocation()
}
}
}
What is a better way to ensure that the task has had time to complete and that the new string will be saved to UserDefaults
versus freezing my application's UI for one second?
In case it isn't clear, if I don't wait for one second, instead of the new value of locationString
being saved to UserDefaults
, the former value is saved instead because the logic in the didSet
block hasn't had time to complete.
The first thing I think is odd about your code is that you have a class called "Networking" which is storing a location. It seems that you are conflating storing location information with making network requests which is strange to me.
That aside, the way you synchronize work using Async/Await is by using a single task that can put the work in order.
I think you want to coordinate getting some information from the network and then storing that information in user defaults. In general the task structure you are looking for is something like:
Task {
let netData = await makeNetworkRequest(/* parameters /*)
saveNetworkDataInUserDefaults()
}
So instead of waiting for a second, or something like that, you create a task that waits just long enough to get info back from the network then stores the data away.
To do that, you're going to need an asynchronous context in which to coordinate that work, and you don't have one in didSet
. You'll likely want to pull that functionality out into a function that can be made async. Without a lot more detail about what your code means, however, it's difficult to give more specific advice.