For more convenient and type-safe displaying and editing of your custom types, Forms provides the ValueLabel and ValueField types.
ValueLabel is like a UILabel but generic on a custom type. It exposes a value property and uses a formatter to convert a value to a String for display:
let label = ValueLabel(value: 4711, formatter: { "\($0) kg" })
label.value = 75You can also provide an instance of a Formatter:
let euroFormatter = NumberFormatter()
euroFormatter.currencyCode = "EUR"
euroFormatter.numberStyle = .currency
let label = ValueLabel(value: 47.11, formatter: euroFormatter)If you often use the same formatter for your custom type you can add a convenience initializer:
struct Euro {
var amount: Double
}
extension ValueLabel where Value == Euro {
convenience init(value: Value, style: TextStyle = .defaultLabel) {
self.init(value: value, keyPath: \.amount, formatter: euroFormatter)
}
}This means you do not have to provide explicit formatter everywhere:
let label = ValueLabel(value: Euro(4711))
label.value = Euro(0)Form already comes with similar initializers for integer and floating point types:
let label = ValueLabel(value: 47.11)
label.value += 100Similar to ValueLabel, ValueField is like a UITextField but generic on a custom type for editing custom values. However, to be able to edit a value it needs an instance of a TextEditor. TextEditor is a protocol and Form comes with two concrete implementations named ValueEditor and DecimalEditor.
ValueEditor comes with several convenience initializers:
/// Country code editor, e.g. +46
let editor = ValueEditor(isValidCharacter: isDigit,
minCharacters: 1,
maxCharacters: 3,
prefix: "+")
let field = ValueField(value: "46", editor: editor)When working with numeric values it is often better to use NumberEditor that uses a NumberFormatter to derive its behavior and formatting:
let editor = NumberEditor<Double>(formatter: euroFormatter)
let field = ValueField(value: 47.11, editor: editor)For integer and floating point values you can pass the formatter directly:
let field = ValueField(value: 47.11, formatter: euroFormatter)To avoid repetition, you typically add convenience initializers to ValueField for your custom types:
extension ValueField where Value == Euro {
convenience init(value: Value, style: FieldStyle = .decimal) {
let editor = NumberEditor<Double>(formatter: euroFormatter)
self.init(value: value, keyPath: \.amount, editor: editor, style: style)
}
}This allows you to use your custom types directly with ValueFields:
let field = ValueField(value: Euro(0))