Skip to content

Instantly share code, notes, and snippets.

@seit
Last active December 9, 2025 01:32
Show Gist options
  • Select an option

  • Save seit/e7def7b9da6383f8ebe5267b28dc0cfe to your computer and use it in GitHub Desktop.

Select an option

Save seit/e7def7b9da6383f8ebe5267b28dc0cfe to your computer and use it in GitHub Desktop.
DrillDownList
import SwiftUI
struct DrillDownList<Data, RowContent>: View
where
Data: RandomAccessCollection,
Data.Element: Identifiable,
Data.Element: Hashable,
RowContent: View
{
@Environment(\.dismiss) private var dismiss
var data: Data
var children: KeyPath<Data.Element, Data>
var headerTitle: String
var itemName: KeyPath<Data.Element, String>
@Binding var navigationPath: NavigationPath
var rowContent: (Data.Element) -> RowContent
var onSelect: (Data.Element) -> Void
var onAppear: ((Data) -> Void)? = nil
var onNextPage: ((Data.Element) -> Void)? = nil
let chevronBack = Image(systemName: "chevron.backward")
var body: some View {
VStack(spacing: 0) {
header
ScrollView {
VStack(alignment: .leading, spacing: 0) {
Divider()
ForEach(data) { item in
drillDownItem(item: item)
Divider()
}
}
.padding(.horizontal, 16)
}
}
.navigationBarHidden(true)
.navigationDestination(for: Data.Element.self) { item in
let child = item[keyPath: children]
DrillDownList(
data: child,
children: children,
headerTitle: item[keyPath: itemName],
itemName: itemName,
navigationPath: $navigationPath,
rowContent: rowContent,
onSelect: onSelect,
onAppear: onAppear,
onNextPage: onNextPage
)
}
.onAppear { onAppear?(data) }
}
@ViewBuilder
func drillDownItem(item: Data.Element) -> some View {
DrillDownListItem(
item: item,
children: children,
itemName: itemName,
rowContent: rowContent,
navigationPath: $navigationPath,
onSelect: onSelect,
onNextPage: onNextPage
)
}
var header: some View {
ZStack {
Button {
dismiss()
} label: {
chevronBack
}
.frame(maxWidth: .infinity, alignment: .leading)
Text(headerTitle)
.padding(13)
}
.padding(.vertical, 16)
.padding(.horizontal, 16)
}
}
struct DrillDownListItem<Data, RowContent>: View
where
Data: RandomAccessCollection,
Data.Element: Identifiable,
Data.Element: Hashable,
RowContent: View
{
var item: Data.Element
var children: KeyPath<Data.Element, Data>
var itemName: KeyPath<Data.Element, String>
var rowContent: (Data.Element) -> RowContent
@Binding var navigationPath: NavigationPath
var onSelect: (Data.Element) -> Void
var onNextPage: ((Data.Element) -> Void)? = nil
var body: some View {
let child = item[keyPath: children]
if child.isEmpty {
Button {
onSelect(item)
navigationPath.removeLast(navigationPath.count)
} label: {
rowContent(item)
}
} else {
Button {
navigationPath.append(item)
} label: {
rowContent(item)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment