Skip to content

Instantly share code, notes, and snippets.

@mattyoung
Last active August 11, 2021 17:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattyoung/b36d5957701e14a3aa7f9876481cbc0e to your computer and use it in GitHub Desktop.
Save mattyoung/b36d5957701e14a3aa7f9876481cbc0e to your computer and use it in GitHub Desktop.
// https://gist.github.com/zntfdr/9d94cac84235f04f021cb6c7e8c1a1c5
// Original article here: https://www.fivestars.blog/code/swiftui-hierarchy-list.html
import SwiftUI
struct FileItem: Identifiable {
var name: String
var children: [FileItem]?
let id = UUID()
static let spmData: [FileItem] = [
FileItem(name: ".gitignore"),
FileItem(name: "Package.swift"),
FileItem(name: "README.md"),
FileItem(name: "Sources", children: [
FileItem(name: "fivestars", children: [
FileItem(name: "main.swift")
]),
]),
FileItem(name: "Tests", children: [
FileItem(name: "fivestarsTests", children: [
FileItem(name: "fivestarsTests.swift"),
FileItem(name: "XCTestManifests.swift"),
]),
FileItem(name: "LinuxMain.swift")
])
]
}
// Compiler bug crash the compiler as of Xcode Version 13.0 beta 4 (13A5201i)
// see https://bugs.swift.org/browse/SR-15033
// public init(@Binding data: Data, children: KeyPath<Binding<Data.Element>, Binding<Data?>>, rowContent: @escaping (Binding<Data.Element>) -> RowContent) {
//
// recursiveView = RecursiveView(data: $data, children: children, rowContent: rowContent)
// }
public struct HierarchyList<Data, RowContent>: View where Data: MutableCollection, Data: RandomAccessCollection, Data.Index: Hashable, Data.Element: Identifiable, RowContent: View {
private let recursiveView: RecursiveView<Data, RowContent>
/// Make an editable HierarchyList
///
/// - Parameters:
/// - data: A Binding to collection of data item
/// - children: the KeyPath to the optional children array
/// - rowContent: A closure View to display the row, its parameter is a binding to recursive data item
public init(_ data: Binding<Data>, children: KeyPath<Binding<Data.Element>, Binding<Data?>>, rowContent: @escaping (Binding<Data.Element>) -> RowContent) {
recursiveView = RecursiveView(data: data, children: children, rowContent: rowContent)
}
public var body: some View {
List {
recursiveView
}
}
}
// ๐Ÿคข๐Ÿ˜• ๐Ÿ‘‡๐Ÿ‘‡
// this version uses ForEach w/ SE-0293 @Binding closure parameter.
// this version is preferred but doesn't compile as of Xcode Version 13.0 beta 4 (13A5201i)
// Compiles in Xcode Version 13.0 beta 5 (13A5212g) (beta 5)
private struct RecursiveView<Data, RowContent>: View where Data: MutableCollection, Data: RandomAccessCollection, Data.Index: Hashable, Data.Element: Identifiable, RowContent: View {
@Binding var data: Data
let children: KeyPath<Binding<Data.Element>, Binding<Data?>>
let rowContent: (Binding<Data.Element>) -> RowContent
var body: some View {
// https://forums.swift.org/t/xcode-abort-trap-6-and-non-sensical-compile-error-maybe-related-to-se-0293/50907/2
// Use SE-0293 @Binding property wrapper closure parameter:
// this compiler bug is fixed: https://github.com/apple/swift/pull/38480
ForEach($data) { $item in // ๐Ÿž๐Ÿž compile error: Cannot convert value of type 'Binding<Data>.Element' (aka 'Binding<Data.Element>') to expected argument type 'Binding<Data.Element>'
if let unwrappedBindingToChildren = Binding($item[keyPath: children]) {
DisclosureGroup(content: {
RecursiveView(data: unwrappedBindingToChildren, children: children, rowContent: rowContent)
}, label: {
rowContent($item)
})
} else {
rowContent($item)
}
}
}
}
// the above do not compile, so use ForEach(.indices, ...) instead for now
//private struct RecursiveView<Data, RowContent>: View where Data: MutableCollection, Data: RandomAccessCollection, Data.Index: Hashable, Data.Element: Identifiable, RowContent: View {
// @Binding var data: Data
// let children: KeyPath<Binding<Data.Element>, Binding<Data?>>
// let rowContent: (Binding<Data.Element>) -> RowContent
//
// var body: some View {
// ForEach(data.indices, id: \.self) { index in
// if let unwrappedBindingToChildren = Binding($data[index][keyPath: children]) {
// DisclosureGroup(content: {
// RecursiveView(data: unwrappedBindingToChildren, children: children, rowContent: rowContent)
// }, label: {
// rowContent($data[index])
// })
// } else {
// rowContent($data[index])
// }
// }
// }
//}
struct HierarchyListDemo: View {
@State private var data = FileItem.spmData
var body: some View {
// rowContent closure receive a binding to data item, enabling mutate on the item
HierarchyList($data, children: \.children, rowContent: { $item in TextField("Edit", text: $item.name) })
}
}
struct HierarchyList_Previews: PreviewProvider {
static var previews: some View {
HierarchyListDemo()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment