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:
databaseobject
- ZeeQL Model:
modelentityattributerelationshippropertyKey
- Rendering
titledisplayNameForEntitydisplayNameForPropertydisplayStringForNilhideEmptyPropertyformatterdisplayPropertyKeysvisibleEntityNamesnavigationBarTitle
- Components and Pages
tasknextTaskpagerowComponentcomponentpageWrapperdebugComponent
- Permissions
userisObjectEditableisObjectDeletableisEntityReadOnlyreadOnlyEntityNames
- Misc
lookplatformdebuginitialPropertyValues
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
EnvironmentValuesstruct 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
RuleContextitself 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
defaultValueis always queried (at least as of beta6).
SwiftUI accesses environment keys using keypathes, e.g. the \.ruleObjectContext in
here:
@Environment(\.ruleObjectContext) var database : DatabaseThose 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.