A lot of the functionality of D2S is built around "Environment Keys".
"Environment Keys" are keys which you can use like so in SwiftUI:
public struct D2SInspectPage: View {
@Environment(\.ruleObjectContext) private var database : Database // retrieve a key
var body: some View {
BlaBlub()
.environment(\.task, "edit") // set a key
}
}
They are scoped along the view hierarchy. D2S uses them to pass down its rule execution context.
D2S has quiet a set of builtin environment keys, including:
- ZeeQL Objects:
database
object
- ZeeQL Model:
model
entity
attribute
relationship
propertyKey
- Rendering
title
displayNameForEntity
displayNameForProperty
displayStringForNil
hideEmptyProperty
formatter
displayPropertyKeys
visibleEntityNames
navigationBarTitle
- Components and Pages
task
nextTask
page
rowComponent
component
pageWrapper
debugComponent
- Permissions
user
isObjectEditable
isObjectDeletable
isEntityReadOnly
readOnlyEntityNames
- Misc
look
platform
debug
initialPropertyValues
Checkout the D2SKeys
for the full set.
A key concept of D2S is that environment keys are not just static keys, but that the value of a key can be derived from a "Rule Model".
For example:
entity.name = 'Movie' AND attribute.name = 'name'
=> displayNameForProperty = 'Movie'
*true*
=> displayNameForProperty = attribute.name
The value of displayNameForProperty
will be different depending on the context
which arounds it.
All environment keys which are of that kind conform to the new
RuleEnvironmentKey
protocol, which also requires EnvironmentKey
conformance.
Unfortunately the builtin SwiftUI
EnvironmentValues
struct lacks a few operations to allow us to directly make any environment key dynamic.
Dynamic environment keys are stored in a RuleContext
. RuleContext
is a
struct similar to SwiftUI's EnvironmentValues
, but in addition to providing
key storage, it can also evaluate keys against a rule model.
The
RuleContext
itself is stored as a regular environment key!
The RuleContext
is also the root object passed into the rule engine. So its
keys are exposed to the rule engine.
Since we want to support D2S keys as EnvironmentKey
keys,
but also as KeyValueCoding
keys,
and everything should still be as typesafe as possible,
it is quite some work to set one up ...
This is the same as creating a regular EnvironmentKey
.
Define a struct representing the key (D2S ones are in the D2S
namespacing
enum):
struct object: DynamicEnvironmentKey {
public static let defaultValue : NSManagedObject = NSManagedObject()
}
A requirement of EnvironmentKey
is that all keys have a default value which is
active when no explicit key was set.
If you want to make an optional key, just define it as an optional type! Note that the
defaultValue
is always queried (at least as of beta6).
SwiftUI accesses environment keys using keypathes, e.g. the \.ruleObjectContext
in
here:
@Environment(\.ruleObjectContext) var database : Database
Those need to be declared as an extension to D2SDynamicEnvironmentValues
:
public extension D2SDynamicEnvironmentValues {
var database : Database {
set { self[dynamic: D2SKeys.ruleObjectContext.self] = newValue }
get { self[dynamic: D2SKeys.ruleObjectContext.self] }
}
The rule
subscript dispatches the set/get calls to the RuleContext
, which
either
- returns a value previously set,
- retrieves a value from the rule system
- or falls back to the default value (two variants are provided).
NOTE: Please keep all D2S system keypathes together in
D2SEnvironmentKeys.swift
.
This needs the stringly mapping ... The internal ones are declared in a map in
D2SEnvironmentKeys.swift
.
private static var kvcToEnvKey : [ String: KVCMapEntry ] = [
"database" : .init(D2SKeys.ruleObjectContext.self),
...
]
A custom key can be added using D2SContextKVC.expose(Key.self, "kvcname")
by a framework consumer.