Reputation: 6862
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.
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
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