Ron Srebro
Ron Srebro

Reputation: 6862

Task in SwiftUI runs but view that is inside a sheet is not updated

I have a simple view that is using a class to generate a link for the user to share.

This link is generated asynchronously so is run by using the .task modifier.

class SomeClass : ObservableObject {

func getLinkURL() async -> URL {
    
    try? await Task.sleep(for: .seconds(1))
    return URL(string:"https://www.apple.com")!
  }
}

struct ContentView: View {

@State var showSheet = false
@State var link : URL?
@StateObject var someClass = SomeClass()

var body: some View {
    VStack {
        Button ("Show Sheet") {
            showSheet.toggle()
        }
    }
    .padding()
    .sheet(isPresented: $showSheet) {
        if let link = link {
            ShareLink(item: link)
        } else {
            HStack {
                ProgressView()
                Text("Generating Link")
            }
            
        }
    }.task {
        let link = await someClass.getLinkURL()
        print ("I got the link",link)
        await MainActor.run {
            self.link = link
        }
    }
    
   }
}

I've simplified my actual code to this example which still displays the same behavior. The task is properly executed when the view appears, and I see the debug print for the link. But when pressing the button to present the sheet the link is nil.

enter image description here

The workaround I found for this is to move the .task modifier to be inside the sheet, but that doesn't make sense to me nor do I understand why that works.

Is this a bug, or am I missing something?

Upvotes: 1

Views: 820

Answers (1)

malhal
malhal

Reputation: 30582

It's because the sheet's closure is created before it is shown and it has captured the old value of the link which was nil. To make it have the latest value, i.e. have a new closure created that uses the new value, then just add it to the capture list, e.g.

.sheet(isPresented: $showSheet) { [link] in

You can learn more about this problem in this answer to a different question. Also, someone who submitted a bug report on this was told by Apple to use the capture list.

By the way, .task is designed to remove the need for state objects for doing async work tied to view lifecycle. Also, you don't need MainActor.run.

Upvotes: 4

Related Questions