Search code examples
swiftlistviewswiftuidirectorytreeview

SwiftUI - Create a filesystem tree view from a list of paths


I have a list of paths, for example a list parsed from the 'ls -R' stdout into an array, how can I create a very simple tree view list with that?

I am still learning swift, I can't figure out how to create the data structure necessary to populate a List view with children.


Solution

  • Moved this from the user's question to an answer:

    Eventually I came up with this solution:

    Create a Class for the items to display in the tree

    class TreeItem: Identifiable{
        
        let Id = UUID()
        let Name: String
        var isExpanded = false
        var Children: [TreeItem] = [] //for subentries
    
        init(name: String, children: [TreeItem] = []) {
            self.Name = name
            self.Children = children
        }
    } 
    

    Create a View for the tree

    struct TreeItemView: View {
        @State var item: TreeItem
        @State private var topExpanded: Bool = false
    
        var body: some View {
            DisclosureGroup(
                isExpanded: $topExpanded,
                content: {
                    if !item.Children.isEmpty {             
                        ForEach(item.Children) { child in
                            if (child.Children.isEmpty){
                                Text(child.Name))
                            }else{
                                TreeItemView(item: child)    
                            }
                        }
                    }
                },
                label: {
                    Text(item.Name)
                }
            )
        }
    }
    

    And here is the function returning a nested array of type TreeItem from a list of paths

    func parsePaths(PathList: [String]) -> [TreeItem]{
        let rootFolder = TreeItem(name: "root") //create startpoint
        var pathList: [String] = PathList
    
        pathList.removeAll { $0.isEmpty } //remove empty lines
        
        for path in pathList{
    
            let components = path.split(separator: "/").map(String.init) //split path in components by '/''
            var currentFolder = rootFolder //set root folder
    
            for component in components {
    
                if let existingFolder = currentFolder.Children.first(where: { $0.Name == component }) { 
                    currentFolder = existingFolder
                } else {
                    let newFolder = TreeItem(name: component)
                    currentFolder.Children.append(newFolder)
                    currentFolder = newFolder 
                }
                
            }
        }
    
        return rootFolder.Children
    }
    

    Here is the main view with some sample data:

    struct ContentView: View {
    
        @State var content:[TreeItem] = [] //initialize the content var
    
        var body: some View {
            Button("Show"){
                content = parsePaths(PathList: [
                    "/FolderA/file.a",
                    "/FolderA/FolderAA/file.aa",
                    "/FolderB/FolderBB/FolderBBB/file.bbb",
                    "/FolderB/file.bbb",
                    "/FolderA/file.a1"
                ])
            }
            List{
                ForEach(content, id: \.Id){item in
                        TreeItemView(item: item)
                    }
            }
        }
    }