Skip to content

Commit 2e84d05

Browse files
authored
Added footer support #43 (#46)
First cut of footer support #43 Footer use orthogonal to header use. As before, header/footer inside scroll region for List-based variants. And outside scroll region for Stack- and Grid-based variants. Configurability of header/footer in inside/outside scrolling region disabled for now, as it may have scalability issues. Will need to be investigated.
1 parent 024e0a5 commit 2e84d05

File tree

65 files changed

+5320
-117
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+5320
-117
lines changed

README.md

+50-19
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ macOS | iOS
1717
* Supporting both value and reference semantics (including Core Data, which uses the latter)
1818
* Option to support a bound data source, where inline controls can directly mutate your data model
1919
* Support for single-select, multi-select, or no selection
20-
* Option to sort by column, with indicators and concise syntax
20+
* Option to specify a header and/or footer
21+
* Option to sort by column in header/footer, with indicators and concise syntax
2122
* Option to specify a row background and/or overlay
2223
* On macOS, option for hover events, such as to highlight row under the mouse cursor
2324
* MINIMAL use of View erasure (i.e., use of `AnyView`), which can impact scalability and performance\*\*
@@ -150,6 +151,41 @@ Table View | Type | Select | Value | Reference | Bound | Filter
150151

151152
\* filtering with bound values likely not scalable as implemented. If you can find a better way to implement, please submit a pull request!
152153

154+
## Header/Footer
155+
156+
Optionally attach a header (or footer) to your table:
157+
158+
```swift
159+
var body: some View {
160+
TablerList(header: header,
161+
footer: footer,
162+
row: row,
163+
results: fruits)
164+
}
165+
166+
private func header(ctx: Binding<Context>) -> some View {
167+
LazyVGrid(columns: gridItems) {
168+
Text("ID")
169+
Text("Name")
170+
Text("Weight")
171+
Text("Color")
172+
}
173+
}
174+
175+
private func footer(ctx: Binding<Context>) -> some View {
176+
LazyVGrid(columns: gridItems) {
177+
Text("ID")
178+
Text("Name")
179+
Text("Weight")
180+
Text("Color")
181+
}
182+
}
183+
```
184+
185+
Where you don't want a header (or footer), simply omit from the declaration of the table.
186+
187+
For **List** based variants, the header and footer are *inside* the scrolling region. For **Stack** and **Grid** based variants, they are *outside*. (This may be configurable at some point once any scaling/performance issues are resolved.)
188+
153189
## Column Sorting
154190

155191
Column sorting is available through the `tablerSort` view function.
@@ -366,17 +402,6 @@ private func hoverAction(fruitID: Fruit.ID, isHovered: Bool) {
366402

367403
To coordinate hover with other backgrounds, such as for selection on **Stack** tables, see the demo apps.
368404

369-
## Headless Tables
370-
371-
Where you don't want a header, simply omit it from the declaration of the table:
372-
373-
```swift
374-
var body: some View {
375-
TablerList(row: row,
376-
results: fruits)
377-
}
378-
```
379-
380405
## Moving Rows
381406

382407
Row moving via drag and drop is available for the **List** based variants.
@@ -446,12 +471,15 @@ Stack configuration is optional.
446471

447472
`TablerStackConfig<Element>.init` parameters:
448473

449-
- `rowPadding: EdgeInsets` - Stack-specific default; varies by platform
450-
- `headerSpacing: CGFloat` - Stack-specific default; varies by platform
451-
- `rowSpacing: CGFloat` - Stack-specific default of 0
474+
- `rowPadding: EdgeInsets` - Stack-specific defaults; varies by platform
475+
- `headerSpacing: CGFloat` - default varies by platform
476+
- `footerSpacing: CGFloat` - default varies by platform
477+
- `rowSpacing: CGFloat` - default of 0
478+
- `headerFixed: Bool` - defaults to `true`
479+
- `footerFixed: Bool` - defaults to `false`
452480
- `filter: Filter?` - with a default of `nil`, indicating no filtering
453481
- `onHover: (Element.ID, Bool) -> Void` - defaults to `{ _,_ in }`
454-
- `tablePadding: EdgeInsets` - per Stack defaults
482+
- `tablePadding: EdgeInsets` - default varies by platform
455483
- `sortIndicatorForward: AnyView` - per Base defaults
456484
- `sortIndicatorReverse: AnyView` - per Base defaults
457485
- `sortIndicatorNeutral: AnyView` - per Base defaults
@@ -465,11 +493,14 @@ Grid configuration is required, where you supply a `GridItem` array.
465493
- `gridItems: [GridItem]` - required
466494
- `alignment: HorizontalAlignment` - `LazyVGrid` alignment, with a default of `.leading`
467495
- `itemPadding: EdgeInsets` - Grid-specific defaults, varies by platform
468-
- `headerSpacing: CGFloat` - Grid-specific default; varies by platform
469-
- `rowSpacing: CGFloat` - Grid-specific default of 0
496+
- `headerSpacing: CGFloat` - default varies by platform
497+
- `footerSpacing: CGFloat` - default varies by platform
498+
- `rowSpacing: CGFloat` - default of 0
499+
- `headerFixed: Bool` - defaults to `true`
500+
- `footerFixed: Bool` - defaults to `false`
470501
- `filter: Filter?` - with a default of `nil`, indicating no filtering
471502
- `onHover: (Element.ID, Bool) -> Void` - defaults to `{ _,_ in }`
472-
- `tablePadding: EdgeInsets` - Grid-specific default; varies by platform
503+
- `tablePadding: EdgeInsets` - default varies by platform
473504
- `sortIndicatorForward: AnyView` - per Base defaults
474505
- `sortIndicatorReverse: AnyView` - per Base defaults
475506
- `sortIndicatorNeutral: AnyView` - per Base defaults

Sources/Generated/TablerGrid+AutoInit.generated.swift

+163
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import SwiftUI
66
public extension TablerGrid {
77
// omitting Header
88
init(_ config: Config = .init(),
9+
@ViewBuilder footer: @escaping FooterContent,
910
@ViewBuilder row: @escaping RowContent,
1011
@ViewBuilder rowBackground: @escaping RowBackground,
1112
@ViewBuilder rowOverlay: @escaping RowOverlay
@@ -15,6 +16,7 @@ public extension TablerGrid {
1516
{
1617
self.init(config,
1718
header: { _ in EmptyView() },
19+
footer: footer,
1820
row: row,
1921
rowBackground: rowBackground,
2022
rowOverlay: rowOverlay,
@@ -25,6 +27,7 @@ public extension TablerGrid {
2527
// omitting Overlay
2628
init(_ config: Config = .init(),
2729
@ViewBuilder header: @escaping HeaderContent,
30+
@ViewBuilder footer: @escaping FooterContent,
2831
@ViewBuilder row: @escaping RowContent,
2932
@ViewBuilder rowBackground: @escaping RowBackground
3033
, results: Results
@@ -33,6 +36,7 @@ public extension TablerGrid {
3336
{
3437
self.init(config,
3538
header: header,
39+
footer: footer,
3640
row: row,
3741
rowBackground: rowBackground,
3842
rowOverlay: { _ in EmptyView() },
@@ -44,6 +48,7 @@ public extension TablerGrid {
4448
// omitting Background
4549
init(_ config: Config = .init(),
4650
@ViewBuilder header: @escaping HeaderContent,
51+
@ViewBuilder footer: @escaping FooterContent,
4752
@ViewBuilder row: @escaping RowContent,
4853
@ViewBuilder rowOverlay: @escaping RowOverlay
4954
, results: Results
@@ -52,6 +57,7 @@ public extension TablerGrid {
5257
{
5358
self.init(config,
5459
header: header,
60+
footer: footer,
5561
row: row,
5662
rowBackground: { _ in EmptyView() },
5763
rowOverlay: rowOverlay,
@@ -61,6 +67,7 @@ public extension TablerGrid {
6167

6268
// omitting Header AND Overlay
6369
init(_ config: Config = .init(),
70+
@ViewBuilder footer: @escaping FooterContent,
6471
@ViewBuilder row: @escaping RowContent,
6572
@ViewBuilder rowBackground: @escaping RowBackground
6673
, results: Results
@@ -69,6 +76,7 @@ public extension TablerGrid {
6976
{
7077
self.init(config,
7178
header: { _ in EmptyView() },
79+
footer: footer,
7280
row: row,
7381
rowBackground: rowBackground,
7482
rowOverlay: { _ in EmptyView() },
@@ -78,6 +86,7 @@ public extension TablerGrid {
7886

7987
// omitting Header AND Background
8088
init(_ config: Config = .init(),
89+
@ViewBuilder footer: @escaping FooterContent,
8190
@ViewBuilder row: @escaping RowContent,
8291
@ViewBuilder rowOverlay: @escaping RowOverlay
8392
, results: Results
@@ -86,6 +95,7 @@ public extension TablerGrid {
8695
{
8796
self.init(config,
8897
header: { _ in EmptyView() },
98+
footer: footer,
8999
row: row,
90100
rowBackground: { _ in EmptyView() },
91101
rowOverlay: rowOverlay,
@@ -96,13 +106,15 @@ public extension TablerGrid {
96106
// omitting Background AND Overlay
97107
init(_ config: Config = .init(),
98108
@ViewBuilder header: @escaping HeaderContent,
109+
@ViewBuilder footer: @escaping FooterContent,
99110
@ViewBuilder row: @escaping RowContent
100111
, results: Results
101112
)
102113
where RowBack == EmptyView, RowOver == EmptyView
103114
{
104115
self.init(config,
105116
header: header,
117+
footer: footer,
106118
row: row,
107119
rowBackground: { _ in EmptyView() },
108120
rowOverlay: { _ in EmptyView() },
@@ -112,6 +124,7 @@ public extension TablerGrid {
112124

113125
// omitting Header, Background, AND Overlay
114126
init(_ config: Config = .init(),
127+
@ViewBuilder footer: @escaping FooterContent,
115128
@ViewBuilder row: @escaping RowContent
116129
, results: Results
117130
)
@@ -120,6 +133,156 @@ public extension TablerGrid {
120133
{
121134
self.init(config,
122135
header: { _ in EmptyView() },
136+
footer: footer,
137+
row: row,
138+
rowBackground: { _ in EmptyView() },
139+
rowOverlay: { _ in EmptyView() },
140+
results: results
141+
)
142+
}
143+
// omitting Footer
144+
init(_ config: Config = .init(),
145+
@ViewBuilder header: @escaping HeaderContent,
146+
@ViewBuilder row: @escaping RowContent,
147+
@ViewBuilder rowBackground: @escaping RowBackground,
148+
@ViewBuilder rowOverlay: @escaping RowOverlay
149+
, results: Results
150+
)
151+
where Footer == EmptyView
152+
{
153+
self.init(config,
154+
header: header,
155+
footer: { _ in EmptyView() },
156+
row: row,
157+
rowBackground: rowBackground,
158+
rowOverlay: rowOverlay,
159+
results: results
160+
)
161+
}
162+
163+
// omitting Header, Footer
164+
init(_ config: Config = .init(),
165+
@ViewBuilder row: @escaping RowContent,
166+
@ViewBuilder rowBackground: @escaping RowBackground,
167+
@ViewBuilder rowOverlay: @escaping RowOverlay
168+
, results: Results
169+
)
170+
where Header == EmptyView, Footer == EmptyView
171+
{
172+
self.init(config,
173+
header: { _ in EmptyView() },
174+
footer: { _ in EmptyView() },
175+
row: row,
176+
rowBackground: rowBackground,
177+
rowOverlay: rowOverlay,
178+
results: results
179+
)
180+
}
181+
182+
// omitting Footer, Overlay
183+
init(_ config: Config = .init(),
184+
@ViewBuilder header: @escaping HeaderContent,
185+
@ViewBuilder row: @escaping RowContent,
186+
@ViewBuilder rowBackground: @escaping RowBackground
187+
, results: Results
188+
)
189+
where Footer == EmptyView, RowOver == EmptyView
190+
{
191+
self.init(config,
192+
header: header,
193+
footer: { _ in EmptyView() },
194+
row: row,
195+
rowBackground: rowBackground,
196+
rowOverlay: { _ in EmptyView() },
197+
results: results
198+
)
199+
200+
}
201+
202+
// omitting Footer, Background
203+
init(_ config: Config = .init(),
204+
@ViewBuilder header: @escaping HeaderContent,
205+
@ViewBuilder row: @escaping RowContent,
206+
@ViewBuilder rowOverlay: @escaping RowOverlay
207+
, results: Results
208+
)
209+
where Footer == EmptyView, RowBack == EmptyView
210+
{
211+
self.init(config,
212+
header: header,
213+
footer: { _ in EmptyView() },
214+
row: row,
215+
rowBackground: { _ in EmptyView() },
216+
rowOverlay: rowOverlay,
217+
results: results
218+
)
219+
}
220+
221+
// omitting Header, Footer AND Overlay
222+
init(_ config: Config = .init(),
223+
@ViewBuilder row: @escaping RowContent,
224+
@ViewBuilder rowBackground: @escaping RowBackground
225+
, results: Results
226+
)
227+
where Header == EmptyView, Footer == EmptyView, RowOver == EmptyView
228+
{
229+
self.init(config,
230+
header: { _ in EmptyView() },
231+
footer: { _ in EmptyView() },
232+
row: row,
233+
rowBackground: rowBackground,
234+
rowOverlay: { _ in EmptyView() },
235+
results: results
236+
)
237+
}
238+
239+
// omitting Header, Footer AND Background
240+
init(_ config: Config = .init(),
241+
@ViewBuilder row: @escaping RowContent,
242+
@ViewBuilder rowOverlay: @escaping RowOverlay
243+
, results: Results
244+
)
245+
where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView
246+
{
247+
self.init(config,
248+
header: { _ in EmptyView() },
249+
footer: { _ in EmptyView() },
250+
row: row,
251+
rowBackground: { _ in EmptyView() },
252+
rowOverlay: rowOverlay,
253+
results: results
254+
)
255+
}
256+
257+
// omitting Footer, Background AND Overlay
258+
init(_ config: Config = .init(),
259+
@ViewBuilder header: @escaping HeaderContent,
260+
@ViewBuilder row: @escaping RowContent
261+
, results: Results
262+
)
263+
where Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView
264+
{
265+
self.init(config,
266+
header: header,
267+
footer: { _ in EmptyView() },
268+
row: row,
269+
rowBackground: { _ in EmptyView() },
270+
rowOverlay: { _ in EmptyView() },
271+
results: results
272+
)
273+
}
274+
275+
// omitting Header, Footer, Background, AND Overlay
276+
init(_ config: Config = .init(),
277+
@ViewBuilder row: @escaping RowContent
278+
, results: Results
279+
)
280+
281+
where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView
282+
{
283+
self.init(config,
284+
header: { _ in EmptyView() },
285+
footer: { _ in EmptyView() },
123286
row: row,
124287
rowBackground: { _ in EmptyView() },
125288
rowOverlay: { _ in EmptyView() },

0 commit comments

Comments
 (0)