Last active
August 11, 2021 17:18
-
-
Save mattyoung/b36d5957701e14a3aa7f9876481cbc0e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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