How it works
TableRowBuilder is the result builder that assembles the rows of a Table. Whenever you write a row-producing closure, SwiftUI applies TableRowBuilder behind the scenes to combine each row expression into a single TableRowContent value the table can lay out. You rarely name it directly — it's the @resultBuilder attached to the parameters that take table rows — but it's what lets you list rows declaratively, mix static and dynamic rows, and apply conditionals without manual collection wrapping. Reach for an understanding of it whenever you build a Table and need to know why its trailing closures accept plain row content the way ViewBuilder accepts views.
Let the builder collect a column-driven table's rows
When you initialize a
Tableover a data collection, you don't write rows at all —TableRowBuilderis the closure type that supplies a row per element automatically, while the closure you write describes columns. In the example,Table(people)hands eachPersonto the builder, which emits one row forAlex,Sam, andJordanfrom thepeoplearray.Pair the builder's rows with TableColumn definitions
Each row the builder produces is filled by the
TableColumnvalues declared in the table's content closure, which map a key path to a cell. HereTableColumn("Name", value: \.name)andTableColumn("Role", value: \.role)define the two cells every builder-generated row renders, sovalue: \.nameandvalue: \.rolepull the text shown in each row.Require Identifiable rows so the builder can track them
The builder needs a stable identity for every row it assembles so SwiftUI can diff and animate updates. That's why the row data conforms to
Identifiable; in the examplestruct Person: Identifiablesupplies anidvialet id = UUID(), and the builder keys each emitted row on that identity.Switch to an explicit builder closure for static rows
When you want fixed rows instead of a collection, you write them directly inside
Table { ... }andTableRowBuildercombines thoseTableRowexpressions — supportingif/switchand multiple statements just likeViewBuilder. The column-driven form shown withTable(people)is the common case, but the same builder powers the explicit-row form by accepting one or moreTableRowvalues.
TableColumn("ID", value: \.name) inside the Table(people) closure to watch every builder-generated row gain a cell, then remove a Person from people and see the builder drop that row.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.
struct TableRowBuilderDemo: View {
struct Person: Identifiable {
let id = UUID()
let name: String
let role: String
}
let people = [
Person(name: "Alex", role: "Designer"),
Person(name: "Sam", role: "Engineer"),
Person(name: "Jordan", role: "Manager")
]
var body: some View {
Table(people) {
TableColumn("Name", value: \.name)
TableColumn("Role", value: \.role)
}
.padding()
}
}