I have a required row:
<<< TextRow { row in
row.title = "Full Name"
row.add(rule: RuleRequired())
}
Now I want to display the validation errors. A suggested solution is to use the cellUpdate
callback:
<<< TextRow { row in
row.title = "Full Name"
row.add(rule: RuleRequired())
}.cellUpdate { cell, row in
if !row.isValid {
cell.titleLabel?.textColor = .red
}
}
This works, but has to be done for each row. So if I have 10 required rows, the code has to be repeated 10 times. That’s clearly not perfect. I have tried to introduce a helper method:
<<< TextRow { row in
row.title = "Full Name"
row.add(rule: RuleRequired())
}.cellIUpdate(highlightValidationErrors)
func highlightValidationErrors(cell: Cell, row: Row) { … }
But here I am fighting the type system, since I can’t find a decent highlightValidationErrors
type that would fit different row types. I would like even better if I could hook up all required fields automatically after creating the form:
for row in form.allRows {
// hook validation error code
}
But how do get to the cellUpdate
hook that way? Surely there must be a good solution I am missing, since I can’t imagine people just repeating the validation display code over and over.
I see, I have just now discovered TextRow.defaultCellUpdate
and friends. Would it be possible to apply those defaults to a particular form instance only?
Hi!
By checking the form instance from TextRow.defaultCellUpdate closure, something like row.section?.form === theRelevantForm
There is no other way...
Thank you! It would be great if I could attach default handlers to a particular form. The more specific handler would then trump the class-wide defaults. But for now I am happy with the class-wide defaults. This is the code I came up with to share the validation logic between different row types:
extension Form {
static func installDefaultValidationHandlers() {
TextRow.defaultCellUpdate = highlightCellLabelIfValidationFailed
TextRow.defaultOnRowValidationChanged = showValidationErrors
}
private static func highlightCellLabelIfValidationFailed(cell: BaseCell, row: BaseRow) {
if !row.isValid {
cell.textLabel?.textColor = .red
}
}
private static func showValidationErrors(cell: BaseCell, row: BaseRow) {
row.removeValidationErrorRows()
row.addValidationErrorRows()
}
}
extension BaseRow {
fileprivate func removeValidationErrorRows() {
let rowIndex = indexPath!.row
while section!.count > rowIndex + 1 && section?[rowIndex + 1] is LabelRow {
_ = section?.remove(at: rowIndex + 1)
}
}
fileprivate func addValidationErrorRows() {
for (index, validationMsg) in validationErrors.map({ $0.msg }).enumerated() {
let labelRow = LabelRow {
$0.title = validationMsg
$0.cell.height = { 30 }
}
section?.insert(labelRow, at: indexPath!.row + index + 1)
}
}
}
It’s fairly obvious in hindsight, but took me a while to find the correct parent type to attach the shared logic to. Hopefully this helps someone.
Most helpful comment
Thank you! It would be great if I could attach default handlers to a particular form. The more specific handler would then trump the class-wide defaults. But for now I am happy with the class-wide defaults. This is the code I came up with to share the validation logic between different row types:
It’s fairly obvious in hindsight, but took me a while to find the correct parent type to attach the shared logic to. Hopefully this helps someone.