Skip to content

Commit a883c07

Browse files
author
Reed Es
committed
TablerGridC and README improvements
1 parent 2c6b82a commit a883c07

File tree

5 files changed

+209
-35
lines changed

5 files changed

+209
-35
lines changed

README.md

+84-32
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ For ScrollView/LazyVGrid-based tables:
3737
On macOS:
3838
* Hovering highlight, indicating which row the mouse is over
3939

40+
Notes:
41+
4042
\* Other platforms like macCatalyst, iPad on Mac, watchOS, tvOS, etc. are poorly supported, if at all. Please contribute to improve support!
4143

4244
\*\* AnyView only used to specify sort configuration images in configuration, which shouldn't impact scalability.
4345

4446
## Tabler Example
4547

46-
The basic example below shows the display of tabular data using `TablerList`, which is for the display of unbound data without any selection capability.
48+
The example below shows the display of tabular data from an array using `TablerList`, which is for the display of unbound data without any selection capability.
4749

4850
```swift
4951
import SwiftUI
@@ -102,42 +104,56 @@ struct ContentView: View {
102104
}
103105
```
104106

105-
## Tables
107+
While `LazyVGrid` is used to wrap the header and row items, you could alternatively wrap them with `HStack` or similar mechanism.
106108

107-
You can choose from any of sixteen (16) variants, which break down along the following lines:
109+
## Tabler Views
108110

109-
* Three foundations: List-based, ScrollView/LazyVStack-based, and ScrollView/LazyVGrid-based
110-
* Selection types offered: none, single-select, and multi-select; availability depending on base
111-
* RAC - usable with `RandomAccessCollection` (e.g., array of struct), with or without binding
112-
* CD - usable with Core Data, with or without binding
113-
* Filter - is `config.filter` supported?
111+
You can choose from any of eighteen (18) variants, which break down along the following lines:
114112

115-
Base | Row Selection | RAC | CD | Filter | View name | Element wrapping
116-
--- | --- | --- | --- | --- | --- | ---
117-
List | No Select | ✓ | ✓ | ✓ | TablerList | (none)
118-
List | No Select | ✓ | | ✓ | TablerListB | Binding\<Element>
119-
List | No Select | | ✓ | | TablerListC | ObservedObject
120-
List | Single-select | ✓ | ✓ | ✓ | TablerList1 | (none)
121-
List | Single-select | ✓ | | ✓ | TablerList1B | Binding\<Element>
122-
List | Single-Select | | ✓ | | TablerList1C | ObservedObject
123-
List | Multi-select | ✓ | ✓ | ✓ | TablerListM | (none)
124-
List | Multi-select | ✓ | | ✓ | TablerListMB | Binding\<Element>
125-
List | Multi-select | | ✓ | | TablerListMC | ObservedObject
126-
Stack | No Select | ✓ | ✓ | ✓ | TablerStack | (none)
127-
Stack | No Select | ✓ | | ✓ | TablerStackB | Binding\<Element>
128-
Stack | No Select | | ✓ | | TablerStackC | ObservedObject
129-
Stack | Single-select | ✓ | ✓ | ✓ | TablerStack1 | (none)
130-
Stack | Single-select | ✓ | | ✓ | TablerStack1B | Binding\<Element>
131-
Stack | Single-select | | ✓ | | TablerStack1C | ObservedObject
132-
Grid | No Select | ✓ | ✓ | ✓ | TablerGrid | (none)
133-
Grid | No Select | ✓ | ✓ | | TablerGridB | Binding\<Element
134-
Grid | No Select | ✓ | ✓ | | TablerGridC | ObservedObject
113+
* Base - three foundations: `List`, `ScrollView/LazyVStack`, and `ScrollView/LazyVGrid`
114+
* Select - single-select, and multi-select, or selection not supported
115+
* RAC - can be used with `RandomAccessCollection` (e.g., array of struct)
116+
* CD - can be used with Core Data
117+
* Filter - is `config.filter` supported?
118+
* Bound - the mechanism through which values are bound, if at all
119+
* View - the view to use
120+
121+
Base | Select | RAC | CD | Filter | Bound | View
122+
--- | --- | --- | --- | --- | --- | ---
123+
List | | ✓ | ✓ | ✓ | | TablerList
124+
List | | ✓ | | ✓\* | Binding\<Element> | TablerListB
125+
List | | | ✓ | | NSManagedObject | TablerListC
126+
List | Single | ✓ | ✓ | ✓ | | TablerList1
127+
List | Single | ✓ | | ✓\* | Binding\<Element> | TablerList1B
128+
List | Single | | ✓ | | NSManagedObject | TablerList1C
129+
List | Multi | ✓ | ✓ | ✓ | | TablerListM
130+
List | Multi | ✓ | | ✓\* | Binding\<Element> | TablerListMB
131+
List | Multi | | ✓ | | NSManagedObject | TablerListMC
132+
Stack | | ✓ | ✓ | ✓ | | TablerStack
133+
Stack | | ✓ | | ✓\* | Binding\<Element> | TablerStackB
134+
Stack | | | ✓ | | NSManagedObject | TablerStackC
135+
Stack | Single | ✓ | ✓ | ✓ | | TablerStack1
136+
Stack | Single | ✓ | | ✓\* | Binding\<Element> | TablerStack1B
137+
Stack | Single | | ✓ | | NSManagedObject | TablerStack1C
138+
Grid | | ✓ | ✓ | ✓ | | TablerGrid
139+
Grid | | ✓ | ✓ | | Binding\<Element> | TablerGridB
140+
Grid | | ✓ | ✓ | | NSManagedObject | TablerGridC
141+
142+
\* filtering with Binding-based data likely not scalable as implemented. If you can find a better way to implement, submit a pull request!
135143

136144
## Column Sorting
137145

138146
Column sorting is available through `tablerSort` view function.
139147

140-
From the demo app, an example of using the sort capability, where an indicator displays in the header if the column is actively sorted:
148+
The example below show how the header items can support sort.
149+
150+
The `columnTitle` is a convenience function that displays header name along with an indicator showing the current sort, if any.
151+
152+
Caret images are used by default, but are configurable in `TablerConfig`.
153+
154+
### Random Access Collection
155+
156+
From the _TablerDemo_ app:
141157

142158
```swift
143159
private typealias Context = TablerContext<Fruit>
@@ -156,9 +172,25 @@ private func header(ctx: Binding<Context>) -> some View {
156172
}
157173
```
158174

159-
When the user clicks on a header column for the first time, it is sorted in ascending order, with an up-chevron "^" indicator. If clicked a successive time, a descending sort is executed, with a down-chevron "v" indicator. See `TablerConfig` for configuration.
175+
### Core Data
176+
177+
The sort method used with Core Data differs. From the _TablerCoreDemo_ app:
160178

161-
For sorting with Core Data, see the _TablerCoreDemo_ app.
179+
```swift
180+
private typealias Context = TablerContext<Fruit>
181+
private typealias Sort = TablerSort<Fruit>
182+
183+
private func header(ctx: Binding<Context>) -> some View {
184+
LazyVGrid(columns: gridItems, alignment: .leading) {
185+
Sort.columnTitle("ID", ctx, \.id)
186+
.onTapGesture { fruits.sortDescriptors = [tablerSort(ctx, \.id)] }
187+
Sort.columnTitle("Name", ctx, \.name)
188+
.onTapGesture { fruits.sortDescriptors = [tablerSort(ctx, \.name)] }
189+
Sort.columnTitle("Weight", ctx, \.weight)
190+
.onTapGesture { fruits.sortDescriptors = [tablerSort(ctx, \.weight)] }
191+
}
192+
}
193+
```
162194

163195
## Bound data
164196

@@ -169,7 +201,7 @@ macOS | iOS
169201
When used with 'bound' variants (e.g., `TablerListB`), the data can be modified directly, mutating your data source. From the demo:
170202

171203
```swift
172-
private func brow(element: Binding<Fruit>) -> some View {
204+
private func brow(element: BoundValue) -> some View {
173205
LazyVGrid(columns: gridItems) {
174206
Text(element.wrappedValue.id)
175207
TextField("Name", text: element.name)
@@ -181,6 +213,26 @@ private func brow(element: Binding<Fruit>) -> some View {
181213
}
182214
```
183215

216+
### Random Access Collection
217+
218+
For Random Access Collection sources, `BoundValue` is:
219+
220+
```swift
221+
typealias BoundValue = Binding<Fruit>
222+
```
223+
224+
### Core Data
225+
226+
For Core Data sources, `BoundValue` is:
227+
228+
```swift
229+
typealias BoundValue = ObservedObject<Fruit>.Wrapper
230+
```
231+
232+
Also known as `ProjectedValue`.
233+
234+
Note that for Core Data, the user's changes will need to be committed to the Managed Object Context. See the _TablerCoreData_ code for an example of how this might be done.
235+
184236
## Row Background
185237

186238
macOS | iOS

Sources/Grid/Internal/BaseGrid.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ struct BaseGrid<Element, Header, Rows>: View
4646
BaseTable(context: $context,
4747
header: headerContent) { buildHeader in
4848

49-
VStack(spacing: config.rowSpacing) {
49+
VStack(spacing: config.rowSpacing) { //TODO headerSpacing
5050
buildHeader()
5151

5252
ScrollView {

Sources/Grid/TablerGridC.swift

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//
2+
// TablerGridC.swift
3+
//
4+
// Copyright 2022 FlowAllocator LLC
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
19+
import SwiftUI
20+
21+
/// Grid-based table, with support for bound values through Core Data
22+
public struct TablerGridC<Element, Header, Row, RowBack>: View
23+
where Element: Identifiable & NSFetchRequestResult & ObservableObject,
24+
Header: View,
25+
Row: View,
26+
RowBack: View
27+
{
28+
public typealias Config = TablerGridConfig<Element>
29+
public typealias Context = TablerContext<Element>
30+
public typealias Hovered = Element.ID?
31+
public typealias HeaderContent = (Binding<Context>) -> Header
32+
public typealias ProjectedValue = ObservedObject<Element>.Wrapper
33+
public typealias RowContent = (ProjectedValue) -> Row
34+
public typealias RowBackground = (Element) -> RowBack
35+
public typealias Fetched = FetchedResults<Element>
36+
37+
// MARK: Parameters
38+
39+
private let config: Config
40+
private let headerContent: HeaderContent
41+
private let rowContent: RowContent
42+
private let rowBackground: RowBackground
43+
private var results: Fetched
44+
45+
public init(_ config: Config = .init(),
46+
@ViewBuilder header: @escaping HeaderContent,
47+
@ViewBuilder row: @escaping RowContent,
48+
@ViewBuilder rowBackground: @escaping RowBackground,
49+
results: Fetched)
50+
{
51+
self.config = config
52+
headerContent = header
53+
rowContent = row
54+
self.rowBackground = rowBackground
55+
self.results = results
56+
_context = State(initialValue: TablerContext(config))
57+
}
58+
59+
// MARK: Locals
60+
61+
@State private var hovered: Hovered = nil
62+
@State private var context: Context
63+
64+
// MARK: Views
65+
66+
public var body: some View {
67+
BaseGrid(context: $context,
68+
header: headerContent) {
69+
ForEach(results) { rawElem in
70+
ObservableHolder(element: rawElem) { obsElem in
71+
rowContent(obsElem)
72+
.modifier(GridItemMod(config, rawElem, $hovered))
73+
.background(rowBackground(rawElem))
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
public extension TablerGridC {
81+
// omitting Header
82+
init(_ config: Config,
83+
@ViewBuilder row: @escaping RowContent,
84+
@ViewBuilder rowBackground: @escaping RowBackground,
85+
results: Fetched)
86+
where Header == EmptyView
87+
{
88+
self.init(config,
89+
header: { _ in EmptyView() },
90+
row: row,
91+
rowBackground: rowBackground,
92+
results: results)
93+
}
94+
95+
// omitting Background
96+
init(_ config: Config,
97+
@ViewBuilder header: @escaping HeaderContent,
98+
@ViewBuilder row: @escaping RowContent,
99+
results: Fetched)
100+
where RowBack == EmptyView
101+
{
102+
self.init(config,
103+
header: header,
104+
row: row,
105+
rowBackground: { _ in EmptyView() },
106+
results: results)
107+
}
108+
109+
// omitting Header AND Background
110+
init(_ config: Config,
111+
@ViewBuilder row: @escaping RowContent,
112+
results: Fetched)
113+
where Header == EmptyView, RowBack == EmptyView
114+
{
115+
self.init(config,
116+
header: { _ in EmptyView() },
117+
row: row,
118+
rowBackground: { _ in EmptyView() },
119+
results: results)
120+
}
121+
122+
}

Sources/Stack/TablerStack1C.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import CoreData
2020
import SwiftUI
2121

22-
/// Stack-based table, with support for bound values
22+
/// Stack-based table, with support for bound values through Core Data
2323
public struct TablerStack1C<Element, Header, Row, RowBack, Select>: View
2424
where Element: Identifiable & NSFetchRequestResult & ObservableObject,
2525
Header: View,

Sources/Stack/TablerStackC.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import CoreData
2020
import SwiftUI
2121

22-
/// Stack-based table, with support for bound values
22+
/// Stack-based table, with support for bound values through Core Data
2323
public struct TablerStackC<Element, Header, Row, RowBack>: View
2424
where Element: Identifiable & NSFetchRequestResult & ObservableObject,
2525
Header: View,

0 commit comments

Comments
 (0)