What would be the proper way to avoid Task {} inside a .onReceive() closure and still be able to run async code?
If I add a Task {} everything runs as it should but shouldn't I replace all Task {} blocks in swiftui? Whenever possible I'm using .task directly on the view but in this case I need to run an URLSession from a publisher onReceive closure.
You can have the onReceive
alter a variable that serves as an id
for task
.
struct TestURLSessionOnReceive: View {
//Object from the publisher/task id
@State private var id: UUID?
//Result from async task
@State private var progress: String?
var body: some View {
VStack{
Button("post notification") { //Mocking a publisher trigger
NotificationCenter.default.post(name: .someNotification, object: UUID.init().uuidString)
}
if let progress { //Mocking a result from an async task
ProgressView {
Text(progress)
}
}
}
//Listen for the publisher
.onReceive(NotificationCenter.default.publisher(for: .someNotification), perform: { notification in
guard let uuidString = notification.object as? String, let uuid = UUID(uuidString: uuidString) else {
return
}
self.id = uuid
})
//Complete a task based on a publisher
.task(id: id) {
guard let id else {return} //Ignore the "onAppear" call
progress = "Working on\nid = \(id)"
//Do any async await code
//let (data, response) = try await URLSession.shared.data(from: <#T##URL#>)
try? await Task.sleep(for: .seconds(2)) //Mocking async task
progress = nil
}
}
}
extension Notification.Name {
static let someNotification = Notification.Name("someNotification")
}