The UI of a D2S application is driven by its SwiftUI Views. Those views are assembled using the rule engine, and they builtin D2S Views use the rule engine to render things.
All of the views can be customized and replaced with own SwiftUI Views. Also all of the views can be embedded in a custom SwiftUI View. Just make sure the environment is properly setup.
There are different types of builtin D2S Views:
- Page Wrapper: Those wrap around Page views, for example they embed them in a NavigationView on watchOS and iOS, and in a SplitView on macOS
- Pages: Top level pages which map to and are selected based on the D2S
"tasks":
- EntityList (associated w/
query
task) - QueryList (associated w/
list
task) - Inspect (associated w/
inspect
task) - Edit (associated w/
edit
task)
- EntityList (associated w/
- Row Views: Most of the pages use a List View to display their content. Row Views can be used to customized the appearance of a list view row.
- Property Views: Also called "components". Those are used to view or edit a
single property (attribute or relationship) of an object, say the
lastname
attribute. There are components for different types of properties, e.g. aD2SDisplayString
and aD2SDisplayBool
view. Properties. - Debug Views: There is a set of views to support debugging. Debug views can be
enabled using the
debug
environment key. - Reusable Views: Just helper views to make common tasks easy.
┌──────────────┐
┌─┤ Page Wrapper ├──────────────┐
│ └──────────────┘ ┌────┐ │
│ ┌─────────────────┤Page├──┐ │
│ │ ┌────┐ └────┘ │ │
│ │ ┌┤Row ├───────────────┐ │ │
│ │ │└────┘ ┌───────────┐ │ │ │
│ │ │ │ Property │ │ │ │
│ │ │ └───────────┘ │ │ │
│ │ └─────────────────────┘ │ │
│ │ ┌────┐ │ │
│ │ ┌┤Row ├───────────────┐ │ │
│ │ │└────┘ ┌───────────┐ │ │ │
│ │ │ │ Property │ │ │ │
│ │ │ └───────────┘ │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────┘ │
└───────────────────────────────┘
There can be different "looks". Looks are just namespaced Views which can be
selected using the .look
environment key.
By default the "BasicLook" is provided (and used as the fallback for everything).
A page is a view which is bound to and selected using the page
environment
key:
\.task == "edit" => \.page <= BasicLook.Page.Edit()
To summon a page using the rule system, just use the D2SPageView
View.
Attach the necessary environment if necessary. Example:
var body: some View {
VStack {
Text("Hello!").font(.title)
D2SPageView()
}
}
It is almost the same like:
@Environment(\.page) var page
var body: some View {
VStack {
Text("Hello!").font(.title)
page
}
}
but also configures the navigation bar title (and other things a page might need).
Shows a customizable list of entities (think "tables") in the model/database.
When clicked, it shows the page associated with the nextTask
, for example
"list" which pulls up the QueryList
page.
This page lists the objects in an entity (think "records" of a "table"). By
default a summary is shown (using the D2SSummaryView
), but this can be fully
customized using the rule system.
When clicked, again the page associated with the nextTask
is invoked. For
example "inspect" which pulls up the D2SInspectPage by default.
This shows a customizable read only view of the "properties" (attributes and
relationships) of a single object (think columns
of a record
).
Those properties are usually embedded in "Row Views" and displayed using
"Property Views".
If the object is editable the inspect by default has a button to invoke the "edit" action (which by default invokes the D2SEditPage).
Same like Inspect
page, but for editing objects.
A property is a view which is bound to and selected using the component
environment key:
\.task == "edit" && \.attribute.attributeType == .dateAttributeType
=> \.component <= D2SEditDate()
To summon a property using the rule system, just use the D2SComponentView
View.
Attach the necessary environment if necessary. Example:
var body: some View {
VStack {
Text("Value:").font(.title)
D2SComponentView()
}
}
It is the same (but less typing) as:
@Environment(\.component) var value
var body: some View {
VStack {
Text("Value:").font(.title)
value
}
}
Let assume that instead of using the generic D2SInspectPage
to view a
"customer" object in the database, we want to provide an own view which displays
the title of the customer at the top.
But we still want to use the D2SInspectPage
to display all the properties in a
List:
struct CustomerView: View {
@Environment(\.object) var object
var body: some View {
VStack {
HStack {
Text(verbatim: "\(object.firstName ?? "-")")
Text(verbatim: "\(object.lastName ?? "-")")
Spacer()
}
.font(.title)
.padding()
Divider()
BasicLook.Page.Inspect()
}
}
}
To activate that in a rule model:
let MyRules : RuleModel = [
\.task == "inspect" && \.entity.name == "Customer"
=> \.page <= CustomerView()
]
If the task is inspect
and the entity is the "Customer" entity, use our
CustomerView
as the page
.
If the edit or inspect page shows a property, it asks the rule engine what view should be used for that property. Or in other words: You can replace the builtin display/edit views on a per property basis.
For example the dvdrental database "Film" entity has a property "rating" which contains the movie ratings of a given film. In the database this is defined as a String, but only "G", "PG", "PG-13", "R", "NC-17" are actually allowed as values.
For display we can use the regular D2SDisplayString View. But for edit
we
might rather want to show a list of buttons (a Picker would also work) for this
property:
struct EditRating: View {
@EnvironmentObject private var object : NSManagedObject
@Environment(\.displayNameForProperty) private var label
@Environment(\.propertyKey) private var propertyKey
let ratings = [ "G", "PG", "PG-13", "R", "NC-17" ]
var body: some View {
HStack(spacing: 16) {
Text(label)
Spacer()
ForEach(ratings, id: \.self) { rating in
Group { // Button didn't work in here
if self.object.rating as? String == rating {
Text(rating)
.foregroundColor(.black)
}
else {
Text(rating)
.foregroundColor(.gray)
}
}
.onTapGesture { self.object.rating = rating }
}
}
}
}
To activate that in a rule model:
let MyRules : RuleModel = [
\.propertyKey == "rating" && \.task == "edit"
=> \.component <= EditRating(),
\.propertyKey == "description" && \.task == "edit"
=> \.component <= D2SEditLargeString(),
\.propertyKey == "rentalRate" || \.propertyKey == "replacementCost"
=> \.formatter <= currencyFormatter
]
If the property is named "rating" and the task is "edit", use the EditRating
as the property component.
This rule model also shows how the builtin D2SEditLargeString
is used to edit
the longer film "description", and how the formatter
key is used to customize
the D2SDisplayString
/D2SEditString
Views with a currency formatter.