NavigationLink without a List

This page contains sample showing how to use the new NavigationLink without a SwiftUI List. While updating Farness for iOS 16, I found very few examples of using the new Navigation API without a List. In addition, List offers a selection binding that makes it pretty easy to set the selected object for the navigation.

Here is how I handled both the selection and navigation without using List. First, create a navigation data structure to handle navigation when the user taps on a saved map.


struct SavedMapNavigation: Equatable {
    var map: Map?
    
    init() {}
    
    init(map: Map) {
        self.map = map
    }
}

Add a state variable to the SwiftUI view for the navigation object.

@State private var savedMapsNavigation = SavedMapNavigation()

The SavedMapsView is a 2 column ScrollView with a LazyVGrid of Images. While looping over the saved maps, it creates NavigationLinks and sets the value to the map.


ForEach(maps) { map in
    NavigationLink(value: map) {
        Image(uiImage: UIImage(data: map.imageData!)!)
            .resizable()
            .frame(width: cellWidth, height: cellWidth)
    }

The simultaneousGesture view modifier is used to update the navigation object with the selected map. This is required for both the navigation link and tap gesture to happen together, otherwise one will override the other.

.simultaneousGesture(TapGesture().onEnded({
    savedMapsNavigation = SavedMapNavigation(map: map)
}))

The onChange modifier is used to send a didSelectMap Action to the Reactive Store in order to update the App State with the selected map.

.onChange(of: savedMapsNavigation, perform: { newValue in
    if let savedMap = newValue.map {
        store.dispatch(.didSelectMap(savedMap: savedMap))
    }
})

Lastly, the navigationDestination modifier tells the Navigation API that DetailView is the destination for the selected map.

.navigationDestination(for: Map.self) { map in
    detailView
}

Here’s the body View that should help show how the navigation and selection works with a ScrollView, and not a List.

@State private var savedMapsNavigation = SavedMapNavigation()
    
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                LazyVGrid(columns: columns) {
                    let width = geometry.size.width
                    let cellWidth = width / 2
                    
                    ForEach(maps) { map in
                        NavigationLink(value: map) {
                            Image(uiImage: UIImage(data: map.imageData!)!)
                                .resizable()
                                .frame(width: cellWidth, height: cellWidth)
                        }
                        .contextMenu {
                            Button {
                                deleteMap(map)
                            } label: {
                                Text("Delete")
                            }
                        }
                        .simultaneousGesture(TapGesture().onEnded({
                            savedMapsNavigation = SavedMapNavigation(map: map)
                        }))
                    }
                }
            }
            .onChange(of: savedMapsNavigation, perform: { newValue in
                if let savedMap = newValue.map {
                    store.dispatch(.didSelectMap(savedMap: savedMap))
                }
            })
            .navigationDestination(for: Map.self) { map in
                detailView
            }
        }
        .navigationTitle("Saved")
    }

Want to see it in action? Download Farness on the App Store!

https://apps.apple.com/us/app/farness/id918635264

Leave a comment

Your email address will not be published. Required fields are marked *