TechnologiesSwiftUILists and Collections

OutlineGroup struct

iOSmacOStvOSwatchOSvisionOS✓ renders

A structure that computes views and disclosure groups on demand from an

How it works

OutlineGroup generates a tree of disclosable rows from a collection of hierarchical data, walking each element's child collection recursively so you don't have to manage expansion state or nesting by hand. Reach for it whenever your data forms a tree — a file system, an organizational chart, a nested outline — and you want each branch to expand and collapse in place. Because it produces views rather than its own container, you place an OutlineGroup inside a List (or any view that lays out a sequence) and it contributes one row per node, complete with the disclosure triangles that drive the hierarchy.

  1. Conform the node type to Identifiable

    OutlineGroup needs a stable identity for every node so it can track which branches are expanded across updates. The simplest path is to conform your data type to Identifiable; in the example, Item does so with a let id = UUID(), which lets you pass the collection directly without supplying a separate id key path.

  2. Construct it with the data and a children key path

    The core initializer takes the root collection and a children key path that points to each node's optional array of sub-nodes. Here OutlineGroup(items, children: \.children) hands the group the top-level items and tells it to descend through \.children; a node whose children is nil is treated as a leaf and shown without a disclosure triangle.

  3. Supply the row content closure

    The trailing closure receives one node at a time and returns the view for that row — OutlineGroup calls it for every node at every depth. In the example the closure builds Label(item.name, systemImage:), so each folder and file renders the same way regardless of how deeply it's nested.

  4. Distinguish branches from leaves

    Inside the row closure you can inspect the node to vary its appearance, since the same view describes both branches and leaves. The example checks item.children == nil to choose between a "doc" and a "folder" symbol, giving files and directories distinct icons.

  5. Place it inside a List

    OutlineGroup emits rows rather than hosting them, so it belongs inside a container that arranges a sequence. Embedding it in the List here turns those rows into a scrollable, selectable outline where tapping a disclosure triangle expands or collapses that node's subtree.

Try it — Add a deeper level by giving one of the leaf items its own children — for example change Item(name: "Trip.jpg") to Item(name: "Trip.jpg", children: [Item(name: "Caption.txt")]) — and watch OutlineGroup grow a new disclosure triangle and nested row for it automatically.

Example & preview

Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.

OutlineGroup.swift
struct OutlineGroupDemo: View {
    struct Item: Identifiable {
        let id = UUID()
        let name: String
        var children: [Item]? = nil
    }

    let items: [Item] = [
        Item(name: "Documents", children: [
            Item(name: "Report.pdf"),
            Item(name: "Photos", children: [
                Item(name: "Trip.jpg"),
                Item(name: "Family.jpg")
            ])
        ]),
        Item(name: "Music", children: [
            Item(name: "Song.mp3")
        ])
    ]

    var body: some View {
        List {
            OutlineGroup(items, children: \.children) { item in
                Label(item.name, systemImage: item.children == nil ? "doc" : "folder")
            }
        }
        .padding()
    }
}
Live preview
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →