NavigationSplitView with List selection of different types
I came across an interesting Stack Overflow question about how to support List
selection of different types when using NavigationSplitView
when in regular width, e.g. on iPad landscape. This problem doesn’t arise with NavigationStackView
because multiple ForEach
can simply iterate an array of each value and then there is a .navigationDestination
for each type. In the SplitView, we have to make use of the List(selection:
) binding which can only be one type. I recently discovered this blog post which is using an enum when implementing Focusable
and thought it could also be applied to this problem and seems to work. It was implemented by:
- using an enum type for the selection binding that wraps each selectable type.
- making use of the fact that Swift enums support associated values.
- using
.tag
for custom List row IDs. - using a switch statement for the detail.
- switching an optional goes to default case when nil. Below is a copy of the code I shared in my answer.
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
}