Skip to content

Latest commit

 

History

History
269 lines (219 loc) · 8.44 KB

File metadata and controls

269 lines (219 loc) · 8.44 KB

Direct to SwiftUI Views

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)
  • 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. a D2SDisplayString and a D2SDisplayBool 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  │ │ │  │
│  │ │       └───────────┘ │ │  │
│  │ └─────────────────────┘ │  │
│  └─────────────────────────┘  │
└───────────────────────────────┘

Looks

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).

Pages

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).

EntityList

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.

QueryList

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.

Inspect

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).

Edit

Same like Inspect page, but for editing objects.

Properties

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
  }
}

Properties

Writing your own Page View

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.

Writing your own Property View

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.