diff --git a/Hierarchy-List/ContentView.swift b/Hierarchy-List/ContentView.swift new file mode 100644 index 0000000..7a3aecf --- /dev/null +++ b/Hierarchy-List/ContentView.swift @@ -0,0 +1,90 @@ +// Original article here: https://www.fivestars.blog/code/swiftui-hierarchy-list.html + +import SwiftUI + +struct FileItem: Identifiable { + let name: String + var children: [FileItem]? + + var id: String { name } + + 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") + ]) + ] +} + +struct ContentView: View { + let data: [FileItem] = .spmData + + var body: some View { +// List(data, children: \.children, rowContent: { Text($0.name) }) + HierarchyList(data: data, children: \.children, rowContent: { Text($0.name) }) + } +} + +public struct HierarchyList: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View { + private let recursiveView: RecursiveView + + public init(data: Data, children: KeyPath, rowContent: @escaping (Data.Element) -> RowContent) { + self.recursiveView = RecursiveView(data: data, children: children, rowContent: rowContent) + } + + public var body: some View { + List { + recursiveView + } + } +} + +private struct RecursiveView: View where Data: RandomAccessCollection, Data.Element: Identifiable, RowContent: View { + let data: Data + let children: KeyPath + let rowContent: (Data.Element) -> RowContent + + var body: some View { + ForEach(data) { child in + if self.containsSub(child) { + FSDisclosureGroup(content: { + RecursiveView(data: child[keyPath: self.children]!, children: self.children, rowContent: self.rowContent) + .padding(.leading) + }, label: { + self.rowContent(child) + }) + } else { + self.rowContent(child) + } + } + } + + func containsSub(_ element: Data.Element) -> Bool { + element[keyPath: children] != nil + } +} + +struct FSDisclosureGroup: View where Label: View, Content: View { + @State var isExpanded: Bool = false + var content: () -> Content + var label: () -> Label + + @ViewBuilder + var body: some View { + Button(action: { self.isExpanded.toggle() }, label: { label().foregroundColor(.blue) }) + if isExpanded { + content() + } + } +} \ No newline at end of file diff --git a/Hierarchy-List/README.md b/Hierarchy-List/README.md new file mode 100644 index 0000000..8f91e20 --- /dev/null +++ b/Hierarchy-List/README.md @@ -0,0 +1,6 @@ +Code snippet from [SwiftUI Hierarchy Lists][fs]. + +![][gif] + +[fs]: https://fivestars.blog/articles/swiftui-hierarchy-list/ +[gif]: spm.gif diff --git a/Hierarchy-List/spm.gif b/Hierarchy-List/spm.gif new file mode 100644 index 0000000..164a51c Binary files /dev/null and b/Hierarchy-List/spm.gif differ