Hi Eric I saw your blog post linked in iOS Dev Weekly - Issue 595. I had a play around with the project, trying all the different versions of the download and thought it might help to send a few comments along.
When async/await was added to SwiftUI and we got the .task and .task(id:) modifiers, those are instead of Task { } because as you've noticed, that is fire and forget. With .task(id:) it saves you from having to hold onto a task handle in a @State, like you are doing in CancelTaskView fileDownloadTask. To use these modifiers all you need to do is set a bool in the button press , e.g. isDownloading, to bounce it over to task(id:isDownloading) which is an async context with lifetime tied to the View. What happens is when the View appears the task is started, it's cancelled when it disappears and also if the id param changes, it is cancelled and restarted, so it would look like this:
Button("Download") {
isDownloading.toggle()
}
.task(id:isDownloading) {
if (!isDownloading) {
return
}
do {
result = try await downloadFile()
}
catch {
errorMessage = error.description
}
}
Since now we have an async context tied to the view on screen, there is now no need to use a reference type to achieve the same thing, i.e. an ObservableObject. We can simply set our results or errors on an @State. The actual async function can be anywhere you like, some like to put it as a static func on the downloaded model type, so it can be tested independently.
(And by the way I noticed a serious mistake at in line 16 of CancelTaskMultipleView if you init an object in a View struct like as an @ObservedObject instead of @StateObject it is lost and reinit every time the View struct is recreated. It's best to never init any objects in a View's init or in body, even things like NSFormatters or NSPredicates.
Hi Eric I saw your blog post linked in iOS Dev Weekly - Issue 595. I had a play around with the project, trying all the different versions of the download and thought it might help to send a few comments along.
When async/await was added to SwiftUI and we got the
.taskand.task(id:)modifiers, those are instead ofTask { }because as you've noticed, that is fire and forget. With.task(id:)it saves you from having to hold onto a task handle in a@State, like you are doing inCancelTaskViewfileDownloadTask. To use these modifiers all you need to do is set aboolin the button press , e.g.isDownloading, to bounce it over totask(id:isDownloading)which is an async context with lifetime tied to the View. What happens is when the View appears the task is started, it's cancelled when it disappears and also if theidparam changes, it is cancelled and restarted, so it would look like this:Since now we have an async context tied to the view on screen, there is now no need to use a reference type to achieve the same thing, i.e. an
ObservableObject. We can simply set our results or errors on an@State. The actual async function can be anywhere you like, some like to put it as astatic funcon the downloaded model type, so it can be tested independently.(And by the way I noticed a serious mistake at in line 16 of
CancelTaskMultipleViewif you init an object in aViewstruct like as an@ObservedObjectinstead of@StateObjectit is lost and reinit every time theViewstruct is recreated. It's best to never init any objects in a View'sinitor inbody, even things likeNSFormatters orNSPredicates.