Skip to content

Commit 36babec

Browse files
committed
Restructure WorkingWithData docs to lead with models
1 parent 6eb31cc commit 36babec

File tree

1 file changed

+109
-69
lines changed

1 file changed

+109
-69
lines changed

SwiftDataTables/SwiftDataTables.docc/WorkingWithData.md

Lines changed: 109 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,124 @@ Learn how to provide data to your table using the type-safe API.
44

55
## Overview
66

7-
SwiftDataTables uses a type-safe API with `DataTableColumn<T>` to define your table structure. This approach provides automatic diffing, animated updates, and compile-time safety.
7+
SwiftDataTables works directly with your model types. Define columns using KeyPaths, pass your array of models, and the table handles the rest - including animated updates when your data changes.
88

9-
## Basic Usage
9+
The key requirement: your models must conform to `Identifiable`. This allows SwiftDataTables to track individual rows and animate only what changed.
1010

11-
Define columns using KeyPaths, then pass your model array:
11+
## Making Your Models Identifiable
12+
13+
If your model already has a unique identifier, conforming to `Identifiable` is straightforward:
1214

1315
```swift
1416
struct Person: Identifiable {
15-
let id: Int
17+
let id: Int // Already have a unique ID? You're done.
18+
let name: String
19+
let email: String
20+
let department: String
21+
}
22+
```
23+
24+
For models without a natural ID, add one:
25+
26+
```swift
27+
struct Product: Identifiable {
28+
let id = UUID() // Generate a unique ID
1629
let name: String
17-
let age: Int
18-
let city: String
30+
let price: Double
31+
let category: String
1932
}
33+
```
34+
35+
> Important: The `id` property enables diffing. Without it, SwiftDataTables can't determine which rows changed, which means no animated updates and no scroll position preservation.
36+
37+
## Defining Columns
2038

39+
Use KeyPaths to map model properties to columns:
40+
41+
```swift
2142
let columns: [DataTableColumn<Person>] = [
2243
.init("Name", \.name),
23-
.init("Age", \.age),
24-
.init("City", \.city)
44+
.init("Email", \.email),
45+
.init("Department", \.department)
2546
]
2647

2748
let dataTable = SwiftDataTable(columns: columns)
49+
```
2850

29-
// Load data later
51+
The compiler verifies your KeyPaths at build time - typos are caught immediately.
52+
53+
## Loading and Updating Data
54+
55+
Pass your model array to `setData()`:
56+
57+
```swift
58+
// Initial load
59+
let people = await fetchPeople()
3060
dataTable.setData(people, animatingDifferences: true)
61+
62+
// Later, when data changes
63+
let updatedPeople = await fetchPeople()
64+
dataTable.setData(updatedPeople, animatingDifferences: true) // Only changed rows animate
65+
```
66+
67+
SwiftDataTables compares the old and new arrays by ID, then:
68+
- Animates insertions and deletions
69+
- Updates changed rows in place
70+
- Leaves unchanged rows alone
71+
- Preserves scroll position
72+
73+
## Data Transformations
74+
75+
### Formatting Values
76+
77+
Use closures for formatted display:
78+
79+
```swift
80+
let columns: [DataTableColumn<Product>] = [
81+
.init("Product", \.name),
82+
.init("Price") { String(format: "£%.2f", $0.price) },
83+
.init("In Stock") { $0.inStock ? "Yes" : "No" }
84+
]
85+
```
86+
87+
### Computed Properties
88+
89+
Calculate values on the fly:
90+
91+
```swift
92+
struct Order: Identifiable {
93+
let id: Int
94+
let quantity: Int
95+
let unitPrice: Double
96+
}
97+
98+
let columns: [DataTableColumn<Order>] = [
99+
.init("Qty") { $0.quantity },
100+
.init("Unit Price") { "£\($0.unitPrice)" },
101+
.init("Total") { "£\(Double($0.quantity) * $0.unitPrice)" }
102+
]
103+
```
104+
105+
### Date Formatting
106+
107+
```swift
108+
let dateFormatter: DateFormatter = {
109+
let f = DateFormatter()
110+
f.dateStyle = .medium
111+
return f
112+
}()
113+
114+
let columns: [DataTableColumn<Event>] = [
115+
.init("Event", \.name),
116+
.init("Date") { dateFormatter.string(from: $0.date) }
117+
]
31118
```
32119

33-
## Dynamic Data Sources
120+
## Dynamic Data Without Models
34121

35-
For data without a predefined model (CSV files, JSON responses, database queries), create a wrapper struct:
122+
Sometimes you're working with data that doesn't have a predefined model - CSV files, JSON responses, or database queries where the schema isn't known at compile time.
123+
124+
For these cases, create a simple wrapper struct:
36125

37126
### CSV Import
38127

@@ -50,7 +139,7 @@ func loadCSV(from url: URL) {
50139
// Create columns dynamically
51140
let columns: [DataTableColumn<CSVRow>] = headers.enumerated().map { index, header in
52141
.init(header) { row in
53-
.string(row.values[index])
142+
row.values[index]
54143
}
55144
}
56145

@@ -72,12 +161,12 @@ struct JSONRow: Identifiable {
72161
let id: String
73162
let data: [String: Any]
74163

75-
func value(for key: String) -> DataTableValueType {
164+
func value(for key: String) -> String {
76165
switch data[key] {
77-
case let string as String: return .string(string)
78-
case let int as Int: return .int(int)
79-
case let double as Double: return .double(double)
80-
default: return .string("")
166+
case let string as String: return string
167+
case let int as Int: return "\(int)"
168+
case let double as Double: return "\(double)"
169+
default: return ""
81170
}
82171
}
83172
}
@@ -105,15 +194,15 @@ func loadJSON(_ json: [[String: Any]], keys: [String]) {
105194
/// Wrapper for database result rows
106195
struct QueryRow: Identifiable {
107196
let id: Int64 // Primary key or row number
108-
let columns: [String: DataTableValueType]
197+
let columns: [String: String]
109198
}
110199

111200
func executeQuery(_ sql: String, columnNames: [String]) async {
112201
let results = await database.query(sql)
113202

114203
let columns: [DataTableColumn<QueryRow>] = columnNames.map { name in
115204
.init(name) { row in
116-
row.columns[name] ?? .string("")
205+
row.columns[name] ?? ""
117206
}
118207
}
119208

@@ -129,9 +218,7 @@ func executeQuery(_ sql: String, columnNames: [String]) async {
129218
}
130219
```
131220

132-
## Why Use a Wrapper?
133-
134-
The typed API requires `Identifiable` conformance for diffing. Wrapping dynamic data provides:
221+
### Why Use a Wrapper?
135222

136223
| Benefit | Description |
137224
|---------|-------------|
@@ -158,53 +245,6 @@ if let person: Person = dataTable.model(at: 5) {
158245
}
159246
```
160247

161-
## Data Transformations
162-
163-
### Formatting Values
164-
165-
Use closures for formatted display:
166-
167-
```swift
168-
let columns: [DataTableColumn<Product>] = [
169-
.init("Product", \.name),
170-
.init("Price") { "$\(String(format: "%.2f", $0.price))" },
171-
.init("In Stock") { $0.inStock ? "Yes" : "No" }
172-
]
173-
```
174-
175-
### Computed Properties
176-
177-
Calculate values on the fly:
178-
179-
```swift
180-
struct Order: Identifiable {
181-
let id: Int
182-
let quantity: Int
183-
let unitPrice: Double
184-
}
185-
186-
let columns: [DataTableColumn<Order>] = [
187-
.init("Qty") { $0.quantity },
188-
.init("Unit Price") { "$\($0.unitPrice)" },
189-
.init("Total") { "$\(Double($0.quantity) * $0.unitPrice)" }
190-
]
191-
```
192-
193-
### Date Formatting
194-
195-
```swift
196-
let dateFormatter: DateFormatter = {
197-
let f = DateFormatter()
198-
f.dateStyle = .medium
199-
return f
200-
}()
201-
202-
let columns: [DataTableColumn<Event>] = [
203-
.init("Event", \.name),
204-
.init("Date") { dateFormatter.string(from: $0.date) }
205-
]
206-
```
207-
208248
## Empty States
209249

210250
Handle empty data gracefully:

0 commit comments

Comments
 (0)