11# Working with Data
22
3- Learn the different ways to provide data to your table.
3+ Learn how to provide data to your table using the type-safe API .
44
55## Overview
66
7- SwiftDataTables supports multiple data formats, from simple string arrays to type-safe model objects. Choose the approach that best fits your needs .
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 .
88
9- ## Data Formats
9+ ## Basic Usage
1010
11- ### String Arrays (Simplest)
12-
13- For quick prototypes or simple data:
11+ Define columns using KeyPaths, then pass your model array:
1412
1513``` swift
16- let data = [
17- [" Alice" , " 28" , " London" ],
18- [" Bob" , " 34" , " Paris" ],
19- [" Carol" , " 25" , " Berlin" ]
14+ struct Person : Identifiable {
15+ let id: Int
16+ let name: String
17+ let age: Int
18+ let city: String
19+ }
20+
21+ let columns: [DataTableColumn<Person>] = [
22+ .init (" Name" , \.name ),
23+ .init (" Age" , \.age ),
24+ .init (" City" , \.city )
2025]
2126
22- let dataTable = SwiftDataTable (
23- data : data,
24- headerTitles : [ " Name " , " Age " , " City " ]
25- )
27+ let dataTable = SwiftDataTable (columns : columns)
28+
29+ // Load data later
30+ dataTable. setData (people, animatingDifferences : true )
2631```
2732
28- ### DataTableValueType Arrays (Typed Values)
33+ ## Dynamic Data Sources
2934
30- For explicit control over sorting behavior:
35+ For data without a predefined model (CSV files, JSON responses, database queries), create a wrapper struct:
36+
37+ ### CSV Import
3138
3239``` swift
33- let data: [[DataTableValueType]] = [
34- [. string ( " Alice " ), . int ( 28 ), . string ( " London " )],
35- [. string ( " Bob " ), . int ( 34 ), . string ( " Paris " )],
36- [. string ( " Carol " ), . int ( 25 ), . string ( " Berlin " )]
37- ]
40+ /// Wrapper for CSV row data
41+ struct CSVRow : Identifiable {
42+ let id: Int // Row index as ID
43+ let values: [ String ] // Column values
44+ }
3845
39- let dataTable = SwiftDataTable (
40- data : data,
41- headerTitles : [" Name" , " Age" , " City" ]
42- )
43- ```
46+ func loadCSV (from url : URL) {
47+ let csvData = parseCSV (url) // Your CSV parser
48+ let headers = csvData.headers
4449
45- Using ` .int() ` instead of ` .string("28") ` ensures numeric sorting (2, 10, 25) instead of alphabetic ("10", "2", "25").
50+ // Create columns dynamically
51+ let columns: [DataTableColumn<CSVRow>] = headers.enumerated ().map { index, header in
52+ .init (header) { row in
53+ .string (row.values [index])
54+ }
55+ }
4656
47- ### Model Objects (Recommended)
57+ // Create rows with index as ID
58+ let rows = csvData.rows .enumerated ().map { index, values in
59+ CSVRow (id : index, values : values)
60+ }
61+
62+ dataTable = SwiftDataTable (columns : columns)
63+ dataTable.setData (rows, animatingDifferences : false )
64+ }
65+ ```
4866
49- For production apps, use your own model types:
67+ ### JSON Response
5068
5169``` swift
52- struct Person : Identifiable {
53- let id: Int
54- let name: String
55- let age: Int
56- let city: String
70+ /// Wrapper for dynamic JSON objects
71+ struct JSONRow : Identifiable {
72+ let id: String
73+ let data: [String : Any ]
74+
75+ func value (for key : String ) -> DataTableValueType {
76+ 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 (" " )
81+ }
82+ }
5783}
5884
59- let people = [
60- Person (id : 1 , name : " Alice" , age : 28 , city : " London" ),
61- Person (id : 2 , name : " Bob" , age : 34 , city : " Paris" ),
62- Person (id : 3 , name : " Carol" , age : 25 , city : " Berlin" )
63- ]
85+ func loadJSON (_ json : [[String : Any ]], keys : [String ]) {
86+ let columns: [DataTableColumn<JSONRow>] = keys.map { key in
87+ .init (key.capitalized ) { row in
88+ row.value (for : key)
89+ }
90+ }
6491
65- let columns: [DataTableColumn<Person>] = [
66- .init (" Name" , \.name ),
67- .init (" Age" , \.age ),
68- .init (" City" , \.city )
69- ]
92+ let rows = json.enumerated ().map { index, dict in
93+ let id = (dict[" id" ] as? String ) ?? " \( index ) "
94+ return JSONRow (id : id, data : dict)
95+ }
7096
71- let dataTable = SwiftDataTable (data : people, columns : columns)
97+ dataTable = SwiftDataTable (columns : columns)
98+ dataTable.setData (rows, animatingDifferences : false )
99+ }
72100```
73101
74- ## Accessing Data
75-
76- ### Get All Data
102+ ### Database Query
77103
78104``` swift
79- // For typed tables
80- let allPeople: [Person] = dataTable.allModels ()
105+ /// Wrapper for database result rows
106+ struct QueryRow : Identifiable {
107+ let id: Int64 // Primary key or row number
108+ let columns: [String : DataTableValueType]
109+ }
110+
111+ func executeQuery (_ sql : String , columnNames : [String ]) async {
112+ let results = await database.query (sql)
113+
114+ let columns: [DataTableColumn<QueryRow>] = columnNames.map { name in
115+ .init (name) { row in
116+ row.columns [name] ?? .string (" " )
117+ }
118+ }
119+
120+ let rows = results.enumerated ().map { index, record in
121+ QueryRow (
122+ id : record.primaryKey ?? Int64 (index),
123+ columns : record.toDictionary ()
124+ )
125+ }
81126
82- // For array-based tables
83- let rowCount = dataTable.currentRowCount
127+ dataTable = SwiftDataTable (columns : columns)
128+ dataTable.setData (rows, animatingDifferences : false )
129+ }
84130```
85131
86- ### Get Specific Row
132+ ## Why Use a Wrapper?
133+
134+ The typed API requires ` Identifiable ` conformance for diffing. Wrapping dynamic data provides:
135+
136+ | Benefit | Description |
137+ | ---------| -------------|
138+ | ** Diffing** | Rows can be tracked by ID for animated updates |
139+ | ** Type Safety** | Compiler ensures column extractors match the row type |
140+ | ** Performance** | Only changed rows update, not the entire table |
141+ | ** Consistency** | Same API whether data is static or dynamic |
142+
143+ ## Accessing Data
144+
145+ ### Get All Models
87146
88147``` swift
89- // For typed tables
90- if let person: Person = dataTable.model (at : 5 ) {
91- print (" Row 5 is \( person.name ) " )
148+ if let allPeople: [Person] = dataTable.allModels () {
149+ print (" Total: \( allPeople.count ) " )
92150}
93-
94- // For array-based tables
95- let rowData = dataTable.data (for : 5 ) // Returns [DataTableValueType]
96151```
97152
98- ### Get Filtered Data
99-
100- After search/filter, access visible rows:
153+ ### Get Specific Row
101154
102155``` swift
103- let visibleCount = dataTable.currentRowCount // After filtering
156+ if let person: Person = dataTable.model (at : 5 ) {
157+ print (" Row 5: \( person.name ) " )
158+ }
104159```
105160
106161## Data Transformations
107162
108163### Formatting Values
109164
110- Use closures in column definitions :
165+ Use closures for formatted display :
111166
112167``` swift
113168let columns: [DataTableColumn<Product>] = [
@@ -138,12 +193,6 @@ let columns: [DataTableColumn<Order>] = [
138193### Date Formatting
139194
140195``` swift
141- struct Event : Identifiable {
142- let id: Int
143- let name: String
144- let date: Date
145- }
146-
147196let dateFormatter: DateFormatter = {
148197 let f = DateFormatter ()
149198 f.dateStyle = .medium
@@ -165,7 +214,7 @@ Handle empty data gracefully:
165214let dataTable = SwiftDataTable (columns : columns)
166215
167216// Later, populate with animation
168- let items = fetchItems ()
217+ let items = await fetchItems ()
169218dataTable.setData (items, animatingDifferences : true )
170219```
171220
@@ -189,4 +238,4 @@ func setTableData(_ rawItems: [RawItem]) {
189238
190239- < doc:TypeSafeColumns >
191240- < doc:AnimatedUpdates >
192- - `` DataTableValueType ``
241+ - `` DataTableColumn ``
0 commit comments