Skip to content

Commit 1ab4b34

Browse files
committed
Merge branch 'develop'
2 parents 19b5441 + 8ca1f8b commit 1ab4b34

File tree

14 files changed

+485
-166
lines changed

14 files changed

+485
-166
lines changed

README.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
_Going fully declarative_: SwiftUI Rules.
1313

14+
SwiftUI Rules is covered in the companion AlwaysRightInstitute
15+
blog post:
16+
[Dynamic Environments ¶ SwiftUI Rules](http://www.alwaysrightinstitute.com/swiftuirules/).
17+
1418
## Requirements
1519

1620
SwiftUI Rules requires an environment capable to run SwiftUI.
@@ -30,6 +34,214 @@ The package URL is:
3034
[https://github.com/AlwaysRightInstitute/SwiftUIRules.git
3135
](https://github.com/AlwaysRightInstitute/SwiftUIRules.git).
3236

37+
## Using SwiftUI Rules
38+
39+
SwiftUI Rules is covered in the companion AlwaysRightInstitute
40+
blog post:
41+
[Dynamic Environments ¶ SwiftUI Rules](http://www.alwaysrightinstitute.com/swiftuirules/).
42+
43+
### Declaring Own Environment Keys
44+
45+
Let's say we want to add an own
46+
environment key called `fancyColor`.
47+
48+
First thing we need is an
49+
[`DynamicEnvironmentKey`](https://github.com/AlwaysRightInstitute/SwiftUIRules/blob/develop/Sources/SwiftUIRules/DynamicEnvironment/DynamicEnvironmentKey.swift#L17)
50+
declaration:
51+
```swift
52+
struct FancyColorEnvironmentKey: DynamicEnvironmentKey {
53+
public static let defaultValue = Color.black
54+
}
55+
```
56+
Most importantly this specifies the static Swift type of the environment key
57+
(`Color`)
58+
and it provides a default value.
59+
That value is used when the environment key is queried,
60+
but no value has been explicitly set by the user.
61+
62+
Second we need to declare a property on the
63+
[DynamicEnvironmentPathes](https://github.com/AlwaysRightInstitute/SwiftUIRules/blob/develop/Sources/SwiftUIRules/DynamicEnvironment/DynamicEnvironmentPathes.swift#L19)
64+
struct:
65+
```swift
66+
extension DynamicEnvironmentPathes {
67+
var fancyColor : Color {
68+
set { self[dynamic: FancyColorEnvironmentKey.self] = newValue }
69+
get { self[dynamic: FancyColorEnvironmentKey.self] }
70+
}
71+
}
72+
```
73+
That's it. We can start using our new key.
74+
75+
Some View accessing our splendid new `fancyColor`
76+
using the
77+
[@Environment](https://developer.apple.com/documentation/swiftui/environment)
78+
property wrapper:
79+
```swift
80+
struct FancyText: View {
81+
82+
@Environment(\.fancyColor) private var color
83+
84+
var label : String
85+
86+
var body: some View {
87+
Text(label)
88+
.foregroundColor(color) // boooring
89+
}
90+
}
91+
```
92+
and a View providing it:
93+
94+
```swift
95+
struct MyPage: View {
96+
97+
var body: some View {
98+
VStack {
99+
Text("Hello")
100+
FancyText("World")
101+
}
102+
.environment(\.fancyColor, .red)
103+
}
104+
}
105+
```
106+
107+
### Setting Up a Ruling Environment
108+
109+
We recommend creating a `RuleModel.swift` Swift file. Put all your
110+
rules in that central location:
111+
```swift
112+
// RuleModel.swift
113+
import SwiftUIRules
114+
115+
let ruleModel : RuleModel = [
116+
\.priority == .low => \.fancyColor <= .gray,
117+
\.priority == .high => \.fancyColor <= .red,
118+
\.priority == .normal => \.fancyColor <= .black
119+
]
120+
```
121+
122+
You can hookup the rule system at any place in the SwiftUI View hierarchy,
123+
but we again recommend to do that at the very top.
124+
For example in a fresh application generated in Xcode, you could modify
125+
the generated `ContentView` like so:
126+
```swift
127+
struct ContentView: View {
128+
private let ruleContext = RuleContext(ruleModel: ruleModel)
129+
130+
var body: some View {
131+
Group {
132+
// your views
133+
}
134+
.environment(\.ruleContext, ruleContext)
135+
}
136+
}
137+
```
138+
139+
Quite often some “root” properties need to be injected:
140+
```swift
141+
struct TodoList: View {
142+
let todos: [ Todo ]
143+
144+
var body: someView {
145+
VStack {
146+
Text("Todos:")
147+
ForEach(todos) { todo in
148+
TodoView()
149+
// make todo available to the rule system
150+
.environment(\.todo, todo)
151+
}
152+
}
153+
}
154+
}
155+
```
156+
`TodoView` and child views of that can now derive environment values of
157+
the `todo` key using the rule system.
158+
159+
### Use Cases
160+
161+
Ha! Endless 🤓 It is quite different to “Think In Rules”™
162+
(a.k.a. declaratively),
163+
but they allow you to compose your application in a highly decoupled
164+
and actually “declarative” ways.
165+
166+
It can be used low level, kinda like CSS.
167+
Consider dynamic environment keys a little like CSS classes.
168+
E.g. you could switch settings based on the platform:
169+
```swift
170+
[
171+
\.platform == "watch" => \.details <= "minimal",
172+
\.platform == "phone" => \.details <= "regular",
173+
\.platform == "mac" || \.platform == "pad"
174+
=> \.details <= "high"
175+
]
176+
```
177+
178+
But it can also be used at a very high level,
179+
for example in a workflow system:
180+
```swift
181+
[
182+
\.task.status == "done" => \.view <= TaskFinishedView(),
183+
\.task.status == "done" => \.actions <= [],
184+
\.task.status == "created" => \.view <= NewTaskView(),
185+
\.task.status == "created" => \.actions = [ .accept, .reject ]
186+
]
187+
188+
struct TaskView: View {
189+
@Environment(\.view) var body // body derived from rules
190+
}
191+
```
192+
193+
Since SwiftUI Views are also just lightweight structs,
194+
you can build dynamic properties which carry them!
195+
196+
In any case: We are interested in any idea how to use it!
197+
198+
199+
### Limitations
200+
201+
#### Only `DynamicEnvironmentKey`s
202+
203+
Currently rules can only evaluate `DynamicEnvironmentKey`s,
204+
it doesn't take regular environment keys into account.
205+
That is, you can't drive for example the builtin SwiftUI `lineLimit`
206+
using the rulesystem.
207+
```swift
208+
[
209+
\.user.status == "VIP" => \.lineLimit <= 10,
210+
\.lineLimit <= 2
211+
]
212+
```
213+
**Does not work**. This is currently made explicit by requiring keys which
214+
are used w/ the system to have the `DynamicEnvironmentKey` type.
215+
SO you can't accidentially run into this.
216+
217+
We may open it up to any `EnvironmentKey`, TBD.
218+
219+
#### No KeyPath'es in Assignments
220+
221+
Sometimes one might want this:
222+
```swift
223+
\.todos.count > 10 => \.person.status <= "VIP"
224+
```
225+
I.e. assign a value to a multi component keypath (`\.person.status`).
226+
That **does not work**.
227+
228+
#### SwiftUI Bugs
229+
230+
Sometimes SwiftUI “looses” its environment during navigation or in List's.
231+
watchOS and macOS seem to be particularily problematic, iOS less so.
232+
If that happens, one can pass on the `ruleContext` manually:
233+
```swift
234+
struct MyNavLink<Destination, Content>: View {
235+
@Environment(\.ruleContext) var ruleContext
236+
...
237+
var body: someView {
238+
NavLink(destination: destination
239+
// Explicitly pass along:
240+
.environment(\.ruleContext, ruleContext))
241+
...
242+
}
243+
```
244+
33245

34246
## Who
35247

Sources/SwiftUIRules/Assignments/DefaultAssignment.swift

Lines changed: 0 additions & 13 deletions
This file was deleted.

Sources/SwiftUIRules/DynamicEnvironment/DynamicEnvironmentKey.swift

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,5 @@ import protocol SwiftUI.EnvironmentKey
1010

1111
/**
1212
* Environment keys which are dynamically evaluated against the RuleContext.
13-
*
14-
* The additional `defaultValueInContext` is essentially a replica of the
15-
* `DefaultAssignment` in D2W.
1613
*/
17-
public protocol DynamicEnvironmentKey: EnvironmentKey {
18-
19-
static func defaultValueInContext(_ context: DynamicEnvironmentValues)
20-
-> Self.Value
21-
22-
}
23-
24-
public extension DynamicEnvironmentKey {
25-
26-
/**
27-
* Default implementation just calls the `defaultValue` of the
28-
* SwiftUI `EnvironmentKey`.
29-
*/
30-
static func defaultValueInContext(_ context: DynamicEnvironmentValues)
31-
-> Self.Value
32-
{
33-
return defaultValue
34-
}
35-
}
14+
public protocol DynamicEnvironmentKey: EnvironmentKey {}

Sources/SwiftUIRules/DynamicEnvironment/DynamicEnvironmentValues.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ public protocol DynamicEnvironmentValues {
2424

2525
public extension DynamicEnvironmentValues { // lookup using type
2626

27-
func defaultValue<K: DynamicEnvironmentKey>(for key: K.Type) -> K.Value {
28-
return K.defaultValueInContext(self)
29-
}
30-
3127
/**
3228
* Returns a value for the `DynamicEnvironmentKey`, or the defaultValue of
3329
* the key if the `resolveValueForTypeID` returns no value.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// AssignmentOperators.swift
3+
// SwiftUIRules
4+
//
5+
// Created by Helge Heß on 01.09.19.
6+
//
7+
8+
import protocol SwiftUI.View
9+
import struct SwiftUI.AnyView
10+
11+
// \.title <= "hello"
12+
public func <= <Value>(lhs: Swift.KeyPath<RuleContext, Value>, rhs: Value)
13+
-> RuleTypeIDAssignment<Value>
14+
{
15+
RuleTypeIDAssignment(lhs, rhs)
16+
}
17+
18+
// \.title <= "hello"
19+
public func <= <Value>(lhs: Swift.KeyPath<RuleContext, Value>,
20+
rhs: Swift.KeyPath<RuleContext, Value>)
21+
-> RuleTypeIDPathAssignment<Value>
22+
{
23+
RuleTypeIDPathAssignment(lhs, rhs)
24+
}
25+
26+
// \.view <= MyView()
27+
public func <= <V: View>(lhs: Swift.KeyPath<RuleContext, AnyView>, rhs: V)
28+
-> RuleTypeIDAssignment<AnyView>
29+
{
30+
RuleTypeIDAssignment(lhs, AnyView(rhs))
31+
}
32+
public func <= (lhs: Swift.KeyPath<RuleContext, AnyView>, rhs: AnyView)
33+
-> RuleTypeIDAssignment<AnyView>
34+
{
35+
RuleTypeIDAssignment(lhs, rhs)
36+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// CompoundPredicateOperators.swift
3+
// SwiftUIRules
4+
//
5+
// Created by Helge Heß on 01.09.19.
6+
//
7+
8+
// e.g. !predicate
9+
public prefix func !<P: RulePredicate>(_ base: P) -> RuleNotPredicate<P> {
10+
RuleNotPredicate(predicate: base)
11+
}
12+
13+
// e.g. \.state == done && \.color == yellow
14+
public
15+
func &&<LHS, RHS>(lhs: LHS, rhs: RHS) -> RuleAndPredicate2<LHS, RHS>
16+
where LHS: RulePredicate, RHS: RulePredicate
17+
{
18+
RuleAndPredicate2(lhs, rhs)
19+
}
20+
// e.g. \.state == done || \.color == yellow
21+
public
22+
func ||<LHS, RHS>(lhs: LHS, rhs: RHS) -> RuleOrPredicate2<LHS, RHS>
23+
where LHS: RulePredicate, RHS: RulePredicate
24+
{
25+
RuleOrPredicate2(lhs, rhs)
26+
}

0 commit comments

Comments
 (0)