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.
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:
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:
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:
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:
A FUIListPickerFormCell
can also be set as multiple selectable in the app by setting its allowsMultipleSelection
property to true, as follows in the UITableViewController
:
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
When the cell is tapped, a multiple select table with specified optional values is displayed:
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:
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
When the cell is tapped, the multiple selection table is shown with a search bar under the navigation bar.
When the user starts typing in the search bar, the displayed list is filtered.
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.
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:
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”):
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:
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:
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”):
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.
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
}