Skip to content

Create and Filter Patterns

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.

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:

Key Value Form Cell

    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:

Filter Form Cell

    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:

List Picker Form Cell 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:

List Picker Form Cell 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:

List Picker Form Cell 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

List Picker Form Cell Multiple Select Cell

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

List Picker Form Cell 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:

List Picker Form Cell Object Cell 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

List Picker Form Cell with Search Bar

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

List Picker Form Cell Object Cell Table

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

List Picker Form Cell Table with Active Search Bar

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.

List Picker Form Cell 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:

Date Picker Form Cell

    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"):

Duration Picker Form Cell

    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:

Value Picker Form Cell

    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:

Switch Form Cell

    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"):

Slider Form Cell

    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.

Slider Form Cell

    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
    }

Last update: January 15, 2021