Skip to content

Commit 15e2148

Browse files
author
Reed Es
committed
Rewrite of validation indicator config, fixing #1
1 parent a88e857 commit 15e2148

File tree

4 files changed

+58
-68
lines changed

4 files changed

+58
-68
lines changed

README.md

+11-5
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ macOS | iOS
1212

1313
## Features
1414

15-
* Convenient editing of structured data in your app
15+
* Convenient editing (and viewing) of fielded data in your app
1616
* Can be used with various collection container types, such as `List`, `Table`, `LazyVStack`, etc.\*
1717
* Presently targeting macOS v11+ and iOS v14+\*\*
18-
* Optional support for operations to Add New records and Delete them
19-
* Optional support for both field-level validation and record-level validation
20-
* No View type erasure (i.e., use of `AnyView`), which can impact scalability and performance
18+
* Both bound (`editDetailer`) and unbound (`viewDetailer`) views available
19+
* Optional support for operations to add new records and delete them
20+
* Optional support for field-level validation, with indicators
21+
* Optional support for record-level validation, with alert view
22+
* Minimal use of View type erasure (i.e., use of `AnyView`)
2123
* No external dependencies!
2224

2325
\* And also the `Tabler` table component (by the same author; see link below)
@@ -149,7 +151,9 @@ struct ContentView: View {
149151

150152
On macOS, ctrl-click (or right-click) on a row to invoke the context menu. On iOS, swipe the row to invoke the menu.
151153

152-
For a full implementation, see the _DetailerDemo_ project (link below). It extends the example with operations to add new records, delete records, and validate input. Among other features, it shows _Detailer_ used with `LazyVGrid` and `Table` containers.
154+
For a full implementation, see the _DetailerDemo_ project (link below). It extends the example with operations to add new records, delete records, and validate input.
155+
156+
It shows _Detailer_ used with `LazyVGrid` and `Table` containers.
153157

154158
## Menuing
155159

@@ -195,6 +199,8 @@ By default, invalid fields will be suffixed with a warning icon, currently an "e
195199

196200
All field-level validations must return `true` for the `Save` button to be enabled.
197201

202+
TIP: for consistent margin spacing in layout, you can create a validation that always succeeds: `.validate(...) { _ in true }`.
203+
198204
### Record-level validation
199205

200206
This can be a *heavyweight* form of validation executed when the user presses the `Save` button.

Sources/DetailerConfigBase.swift renamed to Sources/DetailerConfig.swift

+43-35
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,25 @@
1818

1919
import SwiftUI
2020

21-
/// Convenience alias which hides the "Image"-bound type ugliness.
22-
/// Can't figure out how to avoid it yet. Curiously this isn't
23-
/// needed for Views.
24-
public typealias DetailerConfig<E> = DetailerConfigBase<E, Image> where E: Identifiable
25-
26-
public let detailerDefaultMinWidth: CGFloat = 300
27-
private let defaultValidateFail = "exclamationmark.triangle"
28-
// TODO: defaults for all the non-nil parameters
21+
public enum DetailerConfigDefaults {
22+
23+
#if os(macOS)
24+
public static let detailerDefaultMinWidth: CGFloat = 300
25+
#elseif os(iOS)
26+
public static let detailerDefaultMinWidth: CGFloat = 0
27+
#endif
28+
29+
public static let defaultValidateIndicator: (Bool) -> AnyView = { AnyView(
30+
Image(systemName: "exclamationmark.triangle")
31+
.font(.title2)
32+
.backport.symbolRenderingMode()
33+
.foregroundColor(.orange)
34+
.opacity($0 ? 0 : 1)
35+
)}
36+
}
2937

30-
public struct DetailerConfigBase<Element, ValidateImage>
31-
where Element: Identifiable,
32-
ValidateImage: View
38+
public struct DetailerConfig<Element>
39+
where Element: Identifiable
3340
{
3441
public typealias Context = DetailerContext<Element>
3542

@@ -40,6 +47,7 @@ public struct DetailerConfigBase<Element, ValidateImage>
4047
public typealias OnCancel = (Context, Element) -> Void
4148
public typealias OnSave = (Context, Element) -> Void
4249
public typealias Titler = (Element) -> String
50+
public typealias ValidateIndicator = (Bool) -> AnyView
4351

4452
// MARK: Parameters
4553

@@ -50,18 +58,18 @@ public struct DetailerConfigBase<Element, ValidateImage>
5058
public let onValidate: OnValidate
5159
public let onSave: OnSave?
5260
public let onCancel: OnCancel
53-
public let titler: (Element) -> String
54-
public let validateFail: () -> ValidateImage
61+
public let titler: Titler
62+
public let validateIndicator: ValidateIndicator
5563

56-
public init(minWidth: CGFloat = detailerDefaultMinWidth,
64+
public init(minWidth: CGFloat = DetailerConfigDefaults.detailerDefaultMinWidth,
5765
canEdit: CanEdit? = nil,
5866
canDelete: @escaping CanDelete = { _ in true },
5967
onDelete: OnDelete? = nil,
6068
onValidate: @escaping OnValidate = { _, _ in [] },
6169
onSave: OnSave? = nil,
6270
onCancel: @escaping OnCancel = { _, _ in },
6371
titler: @escaping Titler,
64-
@ViewBuilder validateFail: @escaping () -> ValidateImage)
72+
validateIndicator: @escaping ValidateIndicator = DetailerConfigDefaults.defaultValidateIndicator)
6573
{
6674
self.minWidth = minWidth
6775
self.canEdit = canEdit
@@ -71,27 +79,27 @@ public struct DetailerConfigBase<Element, ValidateImage>
7179
self.onSave = onSave
7280
self.onCancel = onCancel
7381
self.titler = titler
74-
self.validateFail = validateFail
82+
self.validateIndicator = validateIndicator
7583
}
84+
}
7685

77-
// omitting: validateFail
78-
public init(minWidth: CGFloat = detailerDefaultMinWidth,
79-
canEdit: CanEdit? = nil,
80-
canDelete: @escaping CanDelete = { _ in true },
81-
onDelete: OnDelete? = nil,
82-
onValidate: @escaping OnValidate = { _, _ in [] },
83-
onSave: OnSave? = nil,
84-
onCancel: @escaping OnCancel = { _, _ in },
85-
titler: @escaping Titler)
86-
where ValidateImage == Image
87-
{
88-
self.init(canEdit: canEdit,
89-
canDelete: canDelete,
90-
onDelete: onDelete,
91-
onValidate: onValidate,
92-
onSave: onSave,
93-
onCancel: onCancel,
94-
titler: titler,
95-
validateFail: { Image(systemName: defaultValidateFail) })
86+
87+
// Backport for .symbolRenderingMode which isn't supported in earlier versions
88+
struct Backport<Content: View> {
89+
let content: Content
90+
}
91+
92+
extension View {
93+
var backport: Backport<Self> { Backport(content: self) }
94+
}
95+
96+
extension Backport {
97+
@ViewBuilder func symbolRenderingMode() -> some View {
98+
if #available(macOS 12.0, iOS 15.0, *) {
99+
self.content
100+
.symbolRenderingMode(.hierarchical)
101+
} else {
102+
content
103+
}
96104
}
97105
}

Sources/DetailerContext.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@ import SwiftUI
2121
public struct DetailerContext<Element>
2222
where Element: Identifiable
2323
{
24+
public typealias Config = DetailerConfig<Element>
2425
public typealias OnValidate = (AnyKeyPath, Bool) -> Void
2526

2627
// MARK: Parameters
2728

28-
public let config: DetailerConfig<Element>
29+
public let config: Config
2930
public let onValidate: OnValidate
3031
public let isAdd: Bool
3132

32-
public init(config: DetailerConfig<Element>,
33+
public init(config: Config,
3334
onValidate: @escaping OnValidate,
3435
isAdd: Bool)
3536
{

Sources/Internal/Validate.swift

+1-26
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,7 @@ struct Validate<Element, T>: View
3131
var test: Test
3232

3333
var body: some View {
34-
ctx.config.validateFail()
35-
.backport.symbolRenderingMode() // .symbolRenderingMode(.hierarchical)
36-
.font(.title2)
37-
.foregroundColor(.secondary)
38-
.opacity(test(value) ? 0 : 1)
34+
ctx.config.validateIndicator(test(value))
3935
.onAppear {
4036
updateSet(value)
4137
}
@@ -51,24 +47,3 @@ struct Validate<Element, T>: View
5147
}
5248
}
5349
}
54-
55-
// Backport for .symbolRenderingMode which isn't supported in earlier versions
56-
57-
struct Backport<Content: View> {
58-
let content: Content
59-
}
60-
61-
extension View {
62-
var backport: Backport<Self> { Backport(content: self) }
63-
}
64-
65-
extension Backport {
66-
@ViewBuilder func symbolRenderingMode() -> some View {
67-
if #available(macOS 12.0, iOS 15.0, *) {
68-
self.content
69-
.symbolRenderingMode(.hierarchical).foregroundColor(.orange)
70-
} else {
71-
content
72-
}
73-
}
74-
}

0 commit comments

Comments
 (0)