Composite
Composite treats a leaf and a group through one common interface. It is a good fit for tree-shaped UI data: menus, settings, document outlines, or accessibility groups.
iOS problem and when to use it
An iPad settings sidebar has rows inside sections, and sections inside broader categories. The UI needs to count visible rows without special-casing every level.
Original example: settings tree
import Foundation
indirect enum SettingsNode {
case row(title: String)
case group(title: String, children: [SettingsNode])
var visibleRowCount: Int {
switch self {
case .row:
return 1
case .group(_, let children):
return children.reduce(0) { $0 + $1.visibleRowCount }
}
}
var title: String {
switch self {
case .row(let title), .group(let title, _): return title
}
}
}
let settings = SettingsNode.group(title: "Account", children: [
.row(title: "Profile"),
.group(title: "Privacy", children: [
.row(title: "Blocked accounts"),
.row(title: "Permissions")
])
])
print(settings.visibleRowCount) // 3The recursive property applies the same operation to a row or a nested group. In SwiftUI, a recursive view can render the enum into DisclosureGroup and row links; in UIKit, flatten the tree into a diffable data source snapshot.
Production decisions
Use stable IDs for rows and groups before passing them to a diffable data source. Keep expansion state outside this immutable tree so collapsing a section does not rewrite the underlying settings model. If the hierarchy is only one level deep, an array of sections may be clearer.
Understand the recursive operation
SettingsNode has two cases: a leaf row and a group with children of the same type. That self-similar shape is the Composite. visibleRowCount returns one for a leaf and sums child counts for a group; it does not need to know whether a child is another group or a row. indirect allows the recursive enum to store its own type through the child array.
From example to an iOS sidebar
A SwiftUI implementation can render a .group as DisclosureGroup and a .row as a NavigationLink. A UIKit implementation can walk the tree to build section and item identifiers for a diffable data source. Stable identifiers matter: using titles as IDs breaks if two rows share a title or if localization changes. Keep a separate expansion-state set keyed by group ID so collapsing a group does not mutate the model itself.
Failure modes and verification
Test empty groups, several nesting levels, and the order of flattened rows. Composite is useful when operations such as count, search, and rendering apply across both leaves and groups. It is unnecessary for a flat settings list with one section level. Avoid hiding different permissions or actions behind a single leaf interface if those differences are important to callers.