Binding API Guide

Model view binding in SCADE. Version 1.0 - 0.9.15 and higher

The binding API allows you to bind a model to a view. In previous versions of SCADE, we accomplished this in our binding editor, but decided to move it into code. We are pursuing multiple goals with this

  • Binding becomes easier to manage
  • Ability to version control and diff
  • Becomes the basis for evolving it into a declarative UI framework similar to Nick Lookwoods
    Layout, SwiftUI or Flutter's UI

Moreover, the binding API was designed to provide full type safety, greatly reducing errors and providing auto completion.

📘

The Binding Editor is still working

You currently - in this and the next releases - have the choice to use the binding editor or the binding api. However, we are phasing out the binding editor and will remove it in some later release.

So for new apps, please use the binding API.

Introduction

When you establish a model view binding, you create a relationship between data stored in a variable and a UI component. So whenever the variable changes, or the UI changes, the variable or the UI control are updated automatically.

Example 1 - Binding a variable to a label control

Creating the model

A simple Swift class City serves as the model

  • Inherit from EObject
  • Add attribute @objcMembers - The objcMembers attribute tells Swift to make all methods and variables inside this class available to Objective - C. This is important as UiKit relies on Objective - C.
@objcMembers 
class City: EObject {

  // city
  var cityName: String = ""

  init(cityName: String, tr: Bool) {
     super.init()
     self.cityName = cityName
 }
}

We create an instance variable of you City in our main.page.swift class and annotate it with @objc and dynamic.

@objc dynamic var city : City?

Creating the view

In this simple example, the view is an iOS / android label. We used the page editor to add the label into the model of the screen and now create a reference to it using the getWidgetByName method:

// Lets create a variable to hold the reference to the label
var label : SCDWidgetsLabel?

// lets set the label variable
self.label = self.page!.getWidgetByName("label1") as? SCDWidgetsLabel

Binding the variable to the view

We now use the new binding syntax to create a source and a destination and bind them together

  • we use the from method to specify the object we refer to
  • we use the select method to select a property from the object
  • we use the \ operator to refer the parent structure.

The fantastic thing about the binding syntax? It's fully type safe.

// Create two reference. the source and the target reference
let fldCityName = from(self.city!).select(\.cityName)
let lbLabel1 = from(self.label!).select(\.text)
	
// Bind field name 
self.binding = fldCityName.bind(to: lbLabel1)

When we run this little example, we can a nicely working binding

import ScadeKit

@objcMembers class City: EObject {
    
    @objc dynamic var cityName: String = ""
    
    var counter : Int = 100
    
    init(cityName: String, tr: Bool){
        super.init()
        self.cityName = cityName
    }
}

class MainPageAdapter: SCDLatticePageAdapter {
    
    @objc dynamic var city : City?
    
    var label : SCDWidgetsLabel?
    var binding: SCDBindingBinding?
    
    // page adapter initialization
    override func load(_ path: String) {
        super.load(path)
        
        self.city = City(cityName:"Brooklyn", tr:true)
        
        self.label = self.page!.getWidgetByName("label1") as? SCDWidgetsLabel
        
        let fldCityName = from(self.city!).select(\.cityName)
        let lbLabel1 = from(self.label!).select(\.text)
        
        self.binding = fldCityName.bind(to: lbLabel1)
        
    }
}

Example 2 - Binding objects to a List control

Our second example demonstrate the typical binding of a list of objects (with its properties) to a list control.

Creating the model

The model is a list of cities called cityList of type CityList:

// Class to hold my collection
@objcMembers
class Cities: EObject {
    dynamic var cities: [City]
    
    init(_ cities:[City]) {
    	self.cities = cities
    }
}

// variable to store my collection 
@objc dynamic var cityList : Cities?

// lets populate my collection of cities
self.cityList = Cities([
   City(cityName:"Brooklyn", tr:true) ,
   City(cityName:"Chicago", tr:true) ,
   City(cityName:"Bejing", tr:true) ,
])

Creating the view

This time, we want to have a UITableView the view. SCDWidgetsList is the SCADE name for it. The id of this control is list1. Its the default name that got created when we dragged and dropped the label into the list control

// specify view
var list : SCDWidgetsListView?

// get reference to the list view
self.listControl = self.page!.getWidgetByName("list1") as? SCDWidgetsListView

Two step binding

The binding for collections is currently done in two steps

  1. Binding the list of data objects to the list of graphical elements in the list control
  2. Binding the attribute of the data object to the attribute of the graphical element

Step 1 - Bind array of data object to list controls internal list:

  • bind cities to items
  • Items is a property of a list control and represents the data the list control is using
  • You need to use the cast method to map between non-identical data types
// First step : bind from data object to list control's internal model called items
// We create a link between the different collectons
let cities = from(self.cityList!).select(\.cities)
let listItems = from(self.listControl!).select(\.items)

// Our current api expects identical types
// using the cast operator allows to bind between different types
self.binding = cities.cast([Any].self).bind(to: listItems)

Step 2 - Bind array of data object to list controls internal list:

  • bind cityName to the text attribute of a label
  • in the current version of the API, we reference the label by number. Its the first element in the row at(0)
  • the .all parameter is used to select all elements.
  • Items is a property of a list control and represents the data the list control is using
// Second step is to bind on an attribute level from the list items attribute called cityname
// to the list's graphic elements property
let cityName = from(listControl!)
  .select(\.items, .all)
  .select(\City.cityName)
  
let labelText = from(listControl!).select(\.elements, .all)
  .select(\.children, .at(0))
  .select(\SCDWidgetsLabel.text)

// and bind it
self.binding2 = cityName.bind(to: labelText)

The final app

Outlook for July 2019 release

We released version 1 of the binding api. In our next release, we will add an improvement that shortens the two steps of binding to one step, greatly simplifying our 2nd example