1

I've created a list using LazyVStack and inside of the list there are ListItemViews.

What I'm trying to do is, when a ListItemView gets tapped, I want to make an async call depending on the selected item, and then navigate to the view with the data I fetched.

I've tried to do so by placing a Task inside of destination parameter of NavigationLink but I think this is not permissible. I get Type '() -> Task<some View, Never>' cannot conform to 'View' error.

Here is the list that I'm implementing

ScrollView {
    LazyVStack {
        ForEach(items) { item in
            NavigationLink(destination: {
                Task {
                    await detailView(from: item.id) // <- Type '() -> Task<some View, Never>' cannot conform to 'View'
                }
            }, label: {
                ListItemView(item: item)
            })
            .padding(.horizontal, 8)
        } //: ForEach
    } //: LazyVStack
} //: ScrollView

And here is the method that I use to generate destination view.

private func detailView(from id: Int) async -> some View {
    guard let product = await viewModel.getProduct(with: id) else { return Text("Error").eraseToAnyView() }
    selectedProduct = product
    return ProductDetailView(product: $selectedProduct).eraseToAnyView()
}

Any ideas how can I implement this?

thedp
  • 8,350
  • 16
  • 53
  • 95
Burak Akyalçın
  • 302
  • 5
  • 26
  • You can't asynchronously return a `View`. You should pass the `item` to the view and have the view start the data retrieval. The view should show placeholder data until the data fetch is complete. – Paulw11 Mar 09 '23 at 03:54
  • @Paulw11 it's possible to achieve this effect, see my answer below. – thedp Mar 09 '23 at 09:34
  • It's possible, but pretty awful – Paulw11 Mar 09 '23 at 09:35
  • 1
    @Paulw11 The whole SwiftUI is awful imo. Many stuff I consider basic with UIKit require jumping though hoops to achieve in SwiftUI. But, it's getting better. – thedp Mar 09 '23 at 10:10
  • Not really. You just have to separate your view from your model properly. If you try and write procedural code in your view you are going to have a bad time. – Paulw11 Mar 09 '23 at 10:39
  • @Paulw11 well, of course. But I’m talking about the UI part, it’s buggy, glitchy, and often hacky. – thedp Mar 09 '23 at 18:14
  • OP see my answer. If it answers your question, please consider accepting it. – thedp Mar 12 '23 at 08:48

1 Answers1

0

You can do this as follows. You will have to use a button to call the Task, which will update a @State var loadedText when done, and then activate the NavigationLink.

It seems the only way to achieve what you're looking for.

struct ContentView: View {
    @State private var isDetailViewActive = false
    @State private var loadedText: String = ""

    var body: some View {
        NavigationStack {
            VStack {
                Button("Show Detail View") {
                    Task {
                        try await Task.sleep(nanoseconds: 1_000_000_000)
                        loadedText = "HELLO"
                        isDetailViewActive = true
                    }
                }
                NavigationLink(
                    destination: DetailView(text: loadedText),
                    isActive: $isDetailViewActive
                ) {
                    Text("").hidden()
                }
            }
        }
    }
}

struct DetailView: View {
    let text: String

    var body: some View {
        Text("Detail View - \(text)")
    }
}
thedp
  • 8,350
  • 16
  • 53
  • 95