Mischa
Mischa

Reputation: 17269

How to trigger a NavigationLink programmatically in a LazyVGrid

I have a LazyVGrid inside a NavigationView.

NavigationView {
    ScrollView {
        LazyVGrid(columns: columns) {
            ForEach(items) { item in
                NavigationLink(tag: item, selection: $displayedItem) {
                    DetailView(item)
                } label: {
                    GridItemView(item)
                }
            }
        }
    }
}

The referenced variables are defined as follows on the view:

@State var displayedItem: Item?
let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2)

Now I want to show the detail view for a specific item. I do this by simply assigning this item to the displayedItem property:

func showDetailView(for item: Item) {
    displayedItem = item
}

This works great when the respective item is visible on the LazyVGrid at the moment when I call this function. However, when the item is not visible, I first need to scroll to the item for the NavigationLink to fire. I know why this is happening (because the items are loaded lazily, it's a lazy grid after all), but I don't know how to make the LazyVGrid load the specific item when I need it.


What I've tried:

I have also tried to programmatically scroll to the target item by wrapping the entire ScrollView inside a ScrollViewReader and appending the following modifier:

.onChange(of: displayedItem) { item in
    if let item = item {
        scrollProxy.scrollTo(item.id)
    }
}

Unfortunately, this has the same problem: Scrolling to a given item doesn't work until the item is loaded.


Question:

Is there any way to make this work, i.e. to trigger a NavigationLink for an item that is not currently visible in the LazyVGrid? (It's important for me as I need this functionality to deep-link to a specific item's DetailView.)

Upvotes: 2

Views: 1741

Answers (1)

Asperi
Asperi

Reputation: 258355

An possible approach can be like in this topic - use one link somewhere in background of ScrollView and activate it by tapGesture/button from user or assigning corresponding value programmatically.

Tested with Xcode 13.4 / iOS 15.5

enter image description here

Main part:

ScrollView {
    LazyVGrid(columns: columns) {
        ForEach(items) { item in
            RoundedRectangle(cornerRadius: 16).fill(.yellow)
                .frame(maxWidth: .infinity).aspectRatio(1, contentMode: .fit)
                .overlay(Text("Item \(item.value)"))
                .onTapGesture {
                    selectedItem = item
                }
        }
    }
}
.padding(.horizontal)
.background(
    NavigationLink(destination: DetailView(item: selectedItem), isActive: isActive) {
        EmptyView()
    }
)
.toolbar {
    Button("Random") { selectedItem = items.randomElement() }
}

Test module on GitHub

Upvotes: 6

Related Questions