The FormKit API includes a set of UITableViewCells that should be used in the Fiori Design Language to construct table views for creating or editing business objects, or to build filter controls. Each cell implements the FUIFormCell protocol and invokes an optional onChangeHandler closure to handle value changes.

FormKit cells are used in Filter and Create Floorplans.

FormKit Image

Interface

All cells in FormKit implement the FUIFormCell protocol. FUIFormCell is generic for its value property, and has an associatedtype: ValueType, that allows the value to be accessed with type safety.

FUITitleFormCell - displays the title of the form. The cell can be marked as editable to allow user editing of the title (for example, the Work Request cell in the above image):

let cell = tableView.dequeueReusableCell(withIdentifier: FUITitleFormCell.reuseIdentifier, for: indexPath) as! FUITitleFormCell
cell.value = "Work Request"
cell.isEditable = false
return cell

FUINoteFormCell - the user can enter notes in the cell (for example, the cell between Work Request and Request ID in the above image):

let cell = tableView.dequeueReusableCell(withIdentifier: FUINoteFormCell.reuseIdentifier, for: indexPath) as! FUINoteFormCell
cell.placeholder.text = "Description"
cell.value = self.noteText
cell.onChangeHandler = { [unowned self] newValue in
    self.noteText = newValue
}
return cell

FUIKeyValueFormCell - the user can enter text in the cell:

FUIKeyValueFormCell

let cell = tableView.dequeueReusableCell(withIdentifier: FUIKeyValueFormCell.reuseIdentifier, for: indexPath) as! FUIKeyValueFormCell
cell.keyName = "Note"
cell.placeholder.text = "Description"
cell.value = myObject.descriptionText1
cell.isTrackingLiveChanges = true
cell.isEditable = false
cell.onChangeHandler = { [unowned myObject] newValue in
    myObject.descriptionText1 = newValue
}
return cell

FUIFilterFormCell - allow users to select one or multiple values from a value set:

FUIFilterFormCell

let cell = tableView.dequeueReusableCell(withIdentifier: FUIFilterFormCell.reuseIdentifier, for: indexPath) as! FUIFilterFormCell
cell.valueOptions = buttonTitles.flatMap { $0.map { $0.value }}
cell.keyName = "Sort By"
cell.value = self.selectedValue
cell.allowsMultipleSelection = true
cell.allowsEmptySelection = false
cell.onChangeHandler = { [unowned self] newValue in
    self.selectedValue = newValue
}
return cell

FUIListPickerFormCell - provides a key/value pair that displays the key and value of the cell. For single selection, set its allowsMultipleSelection property to false:

FUIListPickerFormCell Single Selectable

var selectedValue: [Int] = [0]

    // A property cell with list picker
    let cell = tableView.dequeueReusableCell(withIdentifier: FUIListPickerFormCell.reuseIdentifier, for: indexPath) as! FUIListPickerFormCell
    cell.keyName = "Work Group"
    cell.value = selectedValue
    cell.valueOptions = ["Construction", "Repair", "Engineering", "Vendor"]
    cell.allowsMultipleSelection = false
    cell.valueLabel.text = descriptionForSelectedStrings(cell.valueOptions, at: selectedValue)
    cell.onChangeHandler = { [unowned self] newValue in
        self.selectedValue = newValue
    }
    return cell

When this cell is selected, a table view displays the available options for the user to choose:

FUIListPickerFormCell Single Selectable

A FUIListPickerFormCell can also be set as multiple selectable in the app by setting its allowsMultipleSelection property to true, as follows in the UITableViewController:

FUIListPickerFormCell Multiple Selectable Table

let valueOptions12 = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"]
var propValue12 = [1, 3, 6]

//...

    // A property cell with list picker
    let cell = tableView.dequeueReusableCell(withIdentifier: FUIListPickerFormCell.reuseIdentifier, for: indexPath) as! FUIListPickerFormCell
    cell.keyName = "Choose Multiple"
    cell.value = propValue12
    cell.valueOptions = valueOptions12

    // Developer is responsible for setting the text for the valueTextField of the FUIListPickerFormCell.
    // Here, function descriptionForSelectedStrings just returns the
    // comma separated selected string.
    cell.valueLabel.text = descriptionForSelectedStrings(valueOptions12, at: propValue12)
    cell.allowsMultipleSelection = true
    cell.listPicker.prompt = "Please select multiple items"
    cell.onChangeHandler = { [unowned self] newValue in
        self.propValue12 = newValue
    }

    return cell

FUIListPickerFormCell Multiple Select Cell

When the cell is tapped, a multiple select table with specified optional values is displayed:

FUIListPickerFormCell Multiple Select Table

A developer could customize the cells displayed in the selection table by setting the dataSource property of the listPicker property of the FUIListPickerFormCell, as described below:

  let propKey11 = "List Picker with Object Cells"
  var propValue11: [Int] = []

  // ObjectCellListPickerDataSource implements ListPickerDataSource and ListPickerSearchResultsUpdating
  let listPickerDataSource11 = ObjectCellListPickerDataSource(40)

  // ...


      // A FUIListPickerFormCell with FUIObjectTableViewCell
      let cell = tableView.dequeueReusableCell(withIdentifier: FUIListPickerFormCell.reuseIdentifier, for: indexPath) as! FUIListPickerFormCell

      cell.keyName = propKey11
      cell.value = propValue11
      cell.valueLabel.text = listPickerDataSource11.descriptionForSelectedItems(at: propValue11)

      cell.listPicker.dataSource = listPickerDataSource11
      cell.listPicker.searchResultsUpdating = listPickerDataSource11
      cell.listPicker.prompt = "Please select multiple items"
      cell.listPicker.register(FUIObjectTableViewCell.self, forCellReuseIdentifier: FUIObjectTableViewCell.reuseIdentifier)
      cell.allowsMultipleSelection = true
      cell.allowsEmptySelection = true
      cell.onChangeHandler = { [unowned self] newValue in
          self.propValue11 = newValue
      }

      return cell

When the cell is tapped, a multiple select table with FUIObjectTableViewCell is displayed:

FUIListPickerFormCell ObjectCell Table

A search bar could be added to the FUIListPickerFormCell in the selection table view by setting isSearchEnabled, dataSource and listPickerResultsUpdating. Optionally, a barcode scanner could be added to the search bar by setting the isBarcodeScannerEnabled property of the searchBar property, as follows:

let propKey7 = "Choose Multiple"
var propValue7: [Int] = []
let listPickerDataSource7 = StringListPickerDataSource(options: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"])

// ...

    let cell = tableView.dequeueReusableCell(withIdentifier: FUIListPickerFormCell.reuseIdentifier, for: indexPath) as! FUIListPickerFormCell

    cell.keyName = propKey7
    cell.value = propValue7

    cell.allowsMultipleSelection = true
    cell.allowsEmptySelection = false
    cell.valueLabel.text = descriptionForSelectedStrings(cell.valueOptions, at: propValue7)

    cell.listPicker.dataSource = listPickerDataSource7
    cell.listPicker.searchResultsUpdating = listPickerDataSource7
    cell.listPicker.prompt = "Please select multiple items"
    cell.listPicker.isSearchEnabled = true
    cell.onChangeHandler = { [unowned self] newValue in
        self.propValue7 = newValue
    }

    cell.listPicker.searchBar?.isBarcodeScannerEnabled = true
    cell.listPicker.searchBar?.barcodeScanner?.scanMode = .EAN_UPC
    cell.listPicker.searchBar?.barcodeScanner?.scanResultTransformer = { (scanString) -> String in
        return self.transformStringToSearchBar(scanResultString: scanString)
    }

    return cell

FUIListPickerFormCell with SearchBar

When the cell is tapped, the multiple selection table is shown with a search bar under the navigation bar.

FUIListPickerFormCell ObjectCell Table

When the user starts typing in the search bar, the displayed list is filtered.

FUIListPickerFormCell Table with Active SearchBar

When the barcode scanner icon is tapped, the barcode scan view is displayed. Note that the barcode scanner icon is not displayed when the device does not support a camera, for example, when it’s running on a simulator. Also, the icon is not displayed when the search bar is active.

FUIListPickerFormCell Barcode Scan

FUIDatePickerFormCell - provides a key/value pair that displays a key and a date as the property value. When this cell is selected, a UIDatePicker displays to allow the user to select a date. The app can provide a DateFormatter to customize how the date displays:

FUIDatePickerFormCell

var propValue4: Date = Date()

// ...

    let cell = tableView.dequeueReusableCell(withIdentifier: FUIDatePickerFormCell.reuseIdentifier, for: indexPath) as! FUIDatePickerFormCell
    cell.key = "Appointment Date"
    cell.value = propValue4
    cell.onChangeHandler = { [unowned self] newValue in
        self.propValue4 = newValue
    }
    return cell

FUIDurationPickerFormCell - provides a key/value pair that displays a key and a TimeInterval as the property value. When this cell is selected, a UIDatePicker displays to allow the user to select a duration. The app can provide a text formatter to customize how the duration displays (for example, the formatter by default is %d Hrs %d Min):

FUIDurationPickerFormCell

var propValue10: TimeInterval = 3900

// ...

    let cell = tableView.dequeueReusableCell(withIdentifier: FUIDurationPickerFormCell.reuseIdentifier, for: indexPath) as! FUIDurationPickerFormCell
    cell.keyName = "Duration"
    cell.value = propValue10
    cell.onChangeHandler = { [unowned self] newValue in
        self.propValue10 = newValue
    }
    return cell   

FUIValuePickerFormCell - provides a key/value pair that displays a key and a String as the property value. When this cell is selected, a UIPickerView displays to allow the user to select a value:

FUIValuePickerFormCell

let priceTitles = [[5: "5"], [10: "10"], [15: "15"], [20: "20"], [25: "25"]]

// ...

    let cell = tableView.dequeueReusableCell(withIdentifier: FUIValuePickerFormCell.reuseIdentifier, for: indexPath) as! FUIValuePickerFormCell
    cell.keyName = "Maximum Price"
    cell.valueOptions = priceTitles.compactMap { $0.first?.value }
    cell.value = priceTitles.index { $0.first!.key == self.myObject.price }!

    // MARK:  implement an onChangeHandler
    cell.onChangeHandler = { [unowned self] newValue in
        self.myObject.price = self.priceTitles[newValue].first!.key
    }

    return cell

FUISwitchFormCell - the property for this cell is a boolean value that uses a standard UISwitch to display the value. The user can change the value by tapping the switch:

FUISwitchFormCell

var isConfirmed = false

// ...

    // A FUISwitchFormCell
    let cell = tableView.dequeueReusableCell(withIdentifier: FUISwitchFormCell.reuseIdentifier, for: indexPath) as! FUISwitchFormCell
    cell.key = "Confirmed"
    cell.value = isConfirmed
    cell.onChangeHandler = { [unowned self] newValue in
        self.isConfirmed = newValue
    }
    return cell

FUISliderFormCell - the property for this cell is a float value. Users select a value from a continuous range using the slider. Also set a unit if needed (default unit is mi):

FUISliderFormCell

let cell = tableView.dequeueReusableCell(withIdentifier: FUISliderFormCell.reuseIdentifier, for: indexPath) as! FUISliderFormCell
cell.keyName = "Distance"
cell.minimumValue = 0
cell.maximumValue = 30
cell.value = myObject.distance

// MARK:  implement an onChangeHandler
cell.onChangeHandler = { newValue in
    self.myObject.distance = newValue
}

return cell

FUIAttachmentFormCell - a cell to which photos or selected files can be added as attachments:

    let cell = tableView.dequeueReusableCell(withIdentifier: FUIAttachmentsFormCell.reuseIdentifier, for: indexPath) as! FUIAttachmentsFormCell
    cell.attachmentsController.delegate = self
    cell.attachmentsController.dataSource = self
    cell.attachmentsController.reloadData()
    cell.attachmentsController.maxItems = 8
    return cell

The application needs to add the following to its info.plist in order to access the camera and photo library:

<key>NSCameraUsageDescription</key>
<string>Please permit access to camera</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Please permit access to photo library</string>

Each UITableViewController is allowed to have only one FUIAttachmentFormCell.

FUISegmentedControlFormCell - A type of FUIPropertyFormCell, representing a key/value pair of the cell. The user can select a value by clicking the button. The cell is editable by default and can be set to isEditable to disable user interaction.

FUISliderFormCell

var selectedItemIndex = 1

// ...

    let cell = tableView.dequeueReusableCell(withIdentifier: FUISegmentedControlFormCell.reuseIdentifier, for: indexPath) as! FUISegmentedControlFormCell

    cell.valueOptions = ["Low", "Medium", "High"]
    cell.keyName = "Key"
    cell.value = selectedItemIndex

    cell.onChangeHandler = { [unowned self] newValue in
        self.selectedItemIndex = newValue
    }
    return cell

Usage

Implement a UITableViewController that hosts the cells and constructs a Create Window view similar to the one in the example above:

The UITableViewController must subclass FUIFormTableViewController.

class MyFormTableViewController: FormTableViewController {
...
}

Register the reuse identifiers of all needed FUIFormCells, and enable auto-dimension row height calculation.

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.register(FUITitleFormCell.self, forCellReuseIdentifier: FUITitleFormCell.reuseIdentifier)
    ...
    self.tableView.estimatedRowHeight = 200
    self.tableView.rowHeight = UITableViewAutomaticDimension
}

Reuse the registered cells in tableView(_:cellForRowAt:), and bind the form data to the cell views:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // An simple key-value property cell
    let cell = tableView.dequeueReusableCell(withIdentifier: FUISimplePropertyFormCell.reuseIdentifier, for: indexPath) as! FUISimplePropertyFormCell
    cell.key = "Location"
    cell.value = "127 Higgins Drive, Palo Alto"
    // MARK:  Implement `onChangeHandler` closure to process the new value entered by the user
    cell.onChangeHandler = { newValue in
        myObject.value = newValue
    }
    return cell
}