Form Demo | New Contact |
---|---|
![]() | ![]() |
Base class FormViewController
with
- A table view
- A data source to handle data
- Implement table view's data source and delegate
class FormViewController: UIViewController { let tableView = UITableView(frame: .zero, style: .grouped) let dataSource = FormDataSource() override func viewDidLoad() { super.viewDidLoad() configure() } } // MARK: - UITableViewDataSource extension FormViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { dataSource.sections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { dataSource.sections[section].fields.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let field = dataSource.sections[indexPath.section].fields[indexPath.row] return field.dequeue(for: tableView, at: indexPath) } } // MARK: - UITableViewDelegate extension FormViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let field = dataSource.sections[indexPath.section].fields[indexPath.row] return field.height } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { dataSource.sections[section].header?.dequeue(for: tableView, in: section) } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { guard let header = dataSource.sections[section].header else { return .zero } return header.height } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let field = dataSource.sections[indexPath.section].fields[indexPath.row] field.tableView(tableView, didSelectRowAt: indexPath) } }
The class FormDataSource
contains data that is used for the form.
final class FormDataSource { private(set) var sections: [FormSection] = [] }
FormSection
contains data for each section on the table view.
final class FormSection { var key: String var header: FormHeader? var fields: [FormField] init(key: String, header: FormHeader? = nil, fields: [FormField]) { self.key = key self.header = header self.fields = fields } }
A object conform to FormHeader
protocol, it will contains only the configuration of a header.
FormHeader
makes it easy to create and maintain a header in a FormSection
.
import UIKit protocol FormHeader: AnyObject { var key: String { get } var height: CGFloat { get } func register(for tableView: UITableView) func dequeue(for tableView: UITableView, in section: Int) -> UIView? }
How the implementation might look:
final class TitleFormHeader { let key: String let viewModel: TitleHeaderFooterViewModel init(key: String, viewModel: TitleHeaderFooterViewModel) { self.key = key self.viewModel = viewModel } } extension TitleFormHeader: FormHeader { var height: CGFloat { 60.0 } func register(for tableView: UITableView) { tableView.register(TitleHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "TitleHeaderFooterView") } func dequeue(for tableView: UITableView, in section: Int) -> UIView? { let view = tableView.dequeueReusableHeaderFooterView( withIdentifier: "TitleHeaderFooterView" ) as? TitleHeaderFooterView view?.configure(with: viewModel) return view } }
FormField
is the most important protocol. For cells in table view, we will have field objects that carry the logic configuration of a view. We will create fields that conform FormField
for all the cells we want to display in a table view.
protocol FormField: AnyObject { var key: String { get } var height: CGFloat { get } var delegate: FormFieldDelegate? { get set } func register(for tableView: UITableView) func dequeue(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) }
Here is an example about an implementation of FormField
final class TextInputFormField { let key: String var viewModel: TextInputViewModel weak var delegate: FormFieldDelegate? init(key: String, viewModel: TextInputViewModel) { self.key = key self.viewModel = viewModel } } // MARK: - FormField extension TextInputFormField: FormField { var height: CGFloat { 44.0 } func register(for tableView: UITableView) { tableView.register(TextInputCell.self, forCellReuseIdentifier: "TextInputCell") } func dequeue(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "TextInputCell", for: indexPath) as! TextInputCell cell.delegate = self cell.configure(viewModel) return cell } }
Check out the demo for more detail about different types of cell.
git clone git@github.com:nimblehq/ios-form.git pod install
This project is Copyright (c) 2014 and onwards. It is free software, and may be redistributed under the terms specified in the LICENSE file.
This project is maintained and funded by Nimble.
We love open source and do our part in sharing our work with the community! See our other projects or hire our team to help build your product.