简体   繁体   中英

Awaiting Task Completion in SwiftUI View

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM