iosdev

Modeling form field

First step in building a form on iOS, which is going to be used inside a collection view is to make up your mind on the design. Second step, implement the design of the cell. Third step – create an appropriate model object for the given cell.

Let’s go back to example from previous intro post – regular one-line text field.

First, let’s build a simple FieldCell subclass, which contains UILabel for the title and UITextField for the value.

final class TextFieldCell: FieldCell {
	//	UI
	@IBOutlet private var titleLabel: UILabel!
	@IBOutlet private var textField: UITextField!
}

Nothing unusual here: both controls are private for the cell, as they should be.

Now, let’s build a (view)model object to supply the required values. First, we need a unique ID for this field:

/// Model that corresponds to TextFieldCell instance.
class TextFieldModel: FieldModel {
	///	unique identifier (across the containing form) for this field
	let id: String
}

Next, we need few strings that corresponds to values shown in label and text field:

///	String to display in the title label
var title: String?

///	Value to show inside the textField
var value: String?

///	Placeholder value to show inside the textField
var placeholder: String?

Notice that value is plain String. Your form data source object, whatever that may be, is responsible to convert application’s model objects into TextFieldModel.

Now comes the fun part. Text field can be used to input unlimited variety of values: username, password, street name, email, city name, phone number, card number…anything, really. Some of these uses require custom configurations.

There’s an ever increasing array of possible configuration options. Making a model property for each one is fool’s errand. Instead, let’s expose the UITextField through custom closure:

///	Custom configuration for the textField.
///
///	Default implementation does nothing.
var customSetup: (UITextField) -> Void = {_ in}

This way, the model stays really clean and endlessly expandable while the field itself is still private and encapsulated inside the TextFieldCell component. If you have custom config, supply the closure and it will be executed at the end of the cell.populate(_ model:TextFieldModel) method.

Finally, we need a way to receive the final edited value back. For that, we use the similar closure approach:

///	Method called when editing is done.
///
///	Default implementation does nothing.
var valueChanged: (String?, TextFieldCell) -> Void = {_, _ in}

This is the minimal setup for the field. You can expand this to whatever you want, depending on the cell design. Let’s say that you want to show some glyph icon that suggest when a particular cell is mandatory + each field has an icon in-front of it:

class TextFieldModel: FieldModel {
	...
	var isMandatory: Bool = false
	var icon: UImage
}

Now adjust the cell design to use these new properties and…done. There are really no design limits with this approach and there is nothing left unused; you adapt the Cell + Model for your intended design and then go on and just build your forms by creating instances of these model objects.

Ok, going back now to the cell. When it’s instantiated from its .xib file, first clean any contents (set in Interface Builder) and then process edited value when editing ends:

override func awakeFromNib() {
	super.awakeFromNib()
	cleanup()

	textField.addTarget(self, action: #selector(editText), for: .editingDidEnd)
	textField.addTarget(self, action: #selector(editText), for: .editingDidEndOnExit)
}

private	@objc func editText(_ sender: UITextField) {
	valueChanged(sender.text, self)
}

editText() method has to call model’s valueChanged() closure. In order to call it, we need to save it locally as the cell’s property. We do this in the same method that populates the cell:

func populate(with model: TextFieldModel) {
	valueChanged = model.valueChanged
	render(model)
}

private func render(_ model: TextFieldModel) {
	titleLabel.text = model.title
	textField.text = model.value
	textField.placeholder = model.placeholder
	model.customSetup( textField )
}

And that’s about it. The very same approach is applied to all other models, like ButtonModel, ToggleModel, TextViewModel etc. They are fairly self explanatory, I hope.

Not all fields are this straight-forward thus in next post I’ll look at the PickerCell and its model.