Reading time: 2 min

This recipe shows how to create a simple file tree using SwiftUI expandable list. This feature is useful for representing any hierarchical data structure, allowing the user to expand and collapse branches to navigate the tree.

The end result looks like this:

ezgif-7-d7434b693fff

This feature is only available starting iOS 14.

OK, to make this work, data for your List must satisfy two conditions:

  • It has to be Identifiable.
  • It has to be tree-structured, meaning that each item needs to have a field that is an optional array of the same type. This allows you to recursively build a tree of items and use nil to specify if an item is a branch or a leaf.

Here's a sample data structure that covers both points:

// Represents a simple file or a folder
struct File: Identifiable { // identifiable ✓
  let id = UUID()
  let name: String
  var children: [File]? // optional array of type File ✓

  var icon: String { // makes things prettier
    if children == nil {
       return "doc"
    } else if children?.isEmpty == true {
       return "folder"
    } else {
       return "folder.fill"
    }
  }
}

And here's some sample data:

let items = [
    File(name: "Documents", children: [
        File(name: "Work", children: [
            File(name: "Revision 1.doc", children: nil),
            File(name: "Revision 2.doc", children: nil),
        ]),
        File(name: "Sheet 1.pdf", children: nil),
        File(name: "Sheet 2.pdf", children: nil)
    ]),
    File(name: "Photos", children: [
        File(name: "Photo 1.jpg", children: nil),
       File(name: "Photo 2.jpg", children: nil)
    ]),
    File(name: "Empty folder", children: []),
    File(name: "sys.info", children: nil)
]

Finally, here's the code that does all the magic. Simply add the children parameter to the List initializer and specify the key path to the optional children array. In the case of our File struct, it's named children.

var body: some View {
  List(items, children: \.children) { item in
    Image(systemName: item.icon)
    Text(item.name)
  }
}

Next Post Previous Post