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!