I've found this great video from Sean Allen https://www.youtube.com/watch?v=oxp8Qqwr4AY on having two different structs in a NavigationStack
, but I cannot figure out how to make this work if I am using a NavigationSplitView
.
This is the code, which compiles, but I get the console error:
A NavigationLink is presenting a value of type “Game” but there is no matching navigation destination visible from the location of the link. The link cannot be activated.
So, Xcode seems to think I should be able to give it a navigation destination, but where? Or is this limited to the NavigationStack
?
struct ContentView: View {
var platforms: [Platform] = [
.init(name: "Xbox", imageName: "xbox.logo", color: .green),
.init(name: "Playstation", imageName: "playstation.logo", color: .indigo),
.init(name: "PC", imageName: "pc", color: .yellow),
.init(name: "Mobile", imageName: "iphone", color: .mint),
]
var games: [Game] = [
.init(name: "Minecraft", rating: "5"),
.init(name: "Gof of War", rating: "15"),
.init(name: "Fortnite", rating: "25"),
.init(name: "Civ 5", rating: "20"),
]
var body: some View {
NavigationSplitView {
List {
Section("Platforms"){
ForEach(platforms, id: \.name) { platform in
NavigationLink(value: platform){
Label(platform.name, systemImage: platform.imageName)
.foregroundColor(platform.color)
}
}
}
Section("Games"){
ForEach(games, id: \.name) { game in
NavigationLink(value: game) {
Label(game.name, systemImage: "\(game.rating).circle.fill")
}
}
}
}
.navigationTitle("Gaming")
.navigationDestination(for: Platform.self) { platform in
ZStack {
platform.color.ignoresSafeArea()
Label(platform.name, systemImage: platform.imageName)
}
}
.navigationDestination(for: Game.self) { game in
Text("\(game.name) Rating \(game.rating) ")
}
} detail: {
// ???
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Platform: Hashable {
let name: String
let imageName: String
let color: Color
}
struct Game: Hashable {
let name: String
let rating: String
}
It's limited to NavigationStack
, however you can workaround by:
.tag
for custom List row IDs.e.g.
struct ContentView: View {
enum Selection: Hashable {
case platform(id: Platform.ID)
case game(id: Game.ID)
}
var platforms: [Platform] = [
.init(name: "Xbox", imageName: "xbox.logo", color: .green),
.init(name: "Playstation", imageName: "playstation.logo", color: .indigo),
.init(name: "PC", imageName: "pc", color: .yellow),
.init(name: "Mobile", imageName: "iphone", color: .mint),
]
var games: [Game] = [
.init(name: "Minecraft", rating: "5"),
.init(name: "Gof of War", rating: "15"),
.init(name: "Fortnite", rating: "25"),
.init(name: "Civ 5", rating: "20"),
]
@State var selection: Selection?
var body: some View {
NavigationSplitView {
List(selection: $selection) {
Section("Platforms"){
ForEach(platforms) { platform in
Label(platform.name, systemImage: platform.imageName)
.foregroundColor(platform.color)
.tag(Selection.platform(id: platform.id))
}
}
Section("Games"){
ForEach(games) { game in
Label(game.name, systemImage: "\(game.rating).circle.fill")
.tag(Selection.game(id: game.id))
}
}
}
.navigationTitle("Gaming")
} detail: {
switch(selection) {
case .platform(let id):
if let platform = platforms.first(where: { $0.id == id }) {
ZStack {
platform.color.ignoresSafeArea()
Label(platform.name, systemImage: platform.imageName)
}
}
case .game(let id):
if let game = games.first(where: { $0.id == id }) {
Text("\(game.name) Rating \(game.rating) ")
}
default:
Text("Make a selection")
}
}
}
}
struct Platform: Identifiable {
let id = UUID()
let name: String
let imageName: String
let color: Color
}
struct Game: Identifiable {
let id = UUID()
let name: String
let rating: String
}
I got the idea from the use of enums with Focusable
demonstrated in this blog post.