{"id":21026478,"url":"https://github.com/nimblehq/ios-form","last_synced_at":"2026-03-11T18:31:24.210Z","repository":{"id":53084969,"uuid":"350973296","full_name":"nimblehq/ios-form","owner":"nimblehq","description":"UITableView with Protocol Oriented Programming","archived":false,"fork":false,"pushed_at":"2021-04-07T11:18:58.000Z","size":363,"stargazers_count":22,"open_issues_count":0,"forks_count":4,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-07-01T16:09:24.397Z","etag":null,"topics":["protocol-oriented-programming","swift","uitableview"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nimblehq.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-24T06:41:14.000Z","updated_at":"2025-03-05T11:55:32.000Z","dependencies_parsed_at":"2022-09-12T12:30:19.078Z","dependency_job_id":null,"html_url":"https://github.com/nimblehq/ios-form","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nimblehq/ios-form","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimblehq%2Fios-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimblehq%2Fios-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimblehq%2Fios-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimblehq%2Fios-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nimblehq","download_url":"https://codeload.github.com/nimblehq/ios-form/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimblehq%2Fios-form/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":283929670,"owners_count":26918166,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-11T02:00:06.610Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["protocol-oriented-programming","swift","uitableview"],"created_at":"2024-11-19T11:44:50.912Z","updated_at":"2025-11-11T20:45:15.762Z","avatar_url":"https://github.com/nimblehq.png","language":"Swift","readme":"# iOS Form\n\n| Form Demo                           | New Contact                         |\n| ----------------------------------- | ----------------------------------- |\n| ![Form demo](/images/form-demo.png) | ![New Contact](/images/contact.png) |\n\n## Implement iOS Form with UITableView\n\n### Create a base controller\n\nBase class `FormViewController` with\n- A table view\n- A data source to handle data\n- Implement table view's data source and delegate\n\n```swift\nclass FormViewController: UIViewController {\n\n  let tableView = UITableView(frame: .zero, style: .grouped)\n  let dataSource = FormDataSource()\n\n  override func viewDidLoad() {\n    super.viewDidLoad()\n    configure()\n  }\n}\n\n// MARK: - UITableViewDataSource\n\nextension FormViewController: UITableViewDataSource {\n\n  func numberOfSections(in tableView: UITableView) -\u003e Int {\n    dataSource.sections.count\n  }\n\n  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -\u003e Int {\n    dataSource.sections[section].fields.count\n  }\n\n  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -\u003e UITableViewCell {\n    let field = dataSource.sections[indexPath.section].fields[indexPath.row]\n    return field.dequeue(for: tableView, at: indexPath)\n  }\n}\n\n// MARK: - UITableViewDelegate\n\nextension FormViewController: UITableViewDelegate {\n\n  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -\u003e CGFloat {\n    let field = dataSource.sections[indexPath.section].fields[indexPath.row]\n    return field.height\n  }\n\n  func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -\u003e UIView? {\n    dataSource.sections[section].header?.dequeue(for: tableView, in: section)\n  }\n\n  func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -\u003e CGFloat {\n    guard let header = dataSource.sections[section].header else { return .zero }\n    return header.height\n  }\n\n  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {\n    let field = dataSource.sections[indexPath.section].fields[indexPath.row]\n    field.tableView(tableView, didSelectRowAt: indexPath)\n  }\n}\n```\n\n### Detail about FormDataSource\n\nThe class `FormDataSource` contains data that is used for the form.\n\n```swift\nfinal class FormDataSource {\n\n  private(set) var sections: [FormSection] = []\n}\n```\n\n#### FormSection\n\n`FormSection` contains data for each section on the table view.\n\n```swift\nfinal class FormSection {\n\n  var key: String\n  var header: FormHeader?\n  var fields: [FormField]\n\n  init(key: String, header: FormHeader? = nil, fields: [FormField]) {\n    self.key = key\n    self.header = header\n    self.fields = fields\n  }\n}\n```\n\n#### FormHeader\n\nA object conform to `FormHeader` protocol, it will contains only the configuration of a header.\n\n`FormHeader` makes it easy to create and maintain a header in a `FormSection`.\n\n```swift\nimport UIKit\n\nprotocol FormHeader: AnyObject {\n\n  var key: String { get }\n  var height: CGFloat { get }\n\n  func register(for tableView: UITableView)\n  func dequeue(for tableView: UITableView, in section: Int) -\u003e UIView?\n}\n```\n\nHow the implementation might look:\n\n```swift\nfinal class TitleFormHeader {\n\n  let key: String\n  let viewModel: TitleHeaderFooterViewModel\n\n  init(key: String, viewModel: TitleHeaderFooterViewModel) {\n    self.key = key\n    self.viewModel = viewModel\n  }\n}\n\nextension TitleFormHeader: FormHeader {\n\n  var height: CGFloat { 60.0 }\n\n  func register(for tableView: UITableView) {\n    tableView.register(TitleHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: \"TitleHeaderFooterView\")\n  }\n\n  func dequeue(for tableView: UITableView, in section: Int) -\u003e UIView? {\n    let view = tableView.dequeueReusableHeaderFooterView(\n      withIdentifier: \"TitleHeaderFooterView\"\n    ) as? TitleHeaderFooterView\n    view?.configure(with: viewModel)\n    return view\n  }\n}\n```\n\n#### FormField\n\n`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.\n\n```swift\nprotocol FormField: AnyObject {\n\n  var key: String { get }\n  var height: CGFloat { get }\n  var delegate: FormFieldDelegate? { get set }\n\n  func register(for tableView: UITableView)\n  func dequeue(for tableView: UITableView, at indexPath: IndexPath) -\u003e UITableViewCell\n  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)\n}\n```\n\nHere is an example about an implementation of `FormField`\n\n```swift\nfinal class TextInputFormField {\n\n  let key: String\n  var viewModel: TextInputViewModel\n\n  weak var delegate: FormFieldDelegate?\n\n  init(key: String, viewModel: TextInputViewModel) {\n    self.key = key\n    self.viewModel = viewModel\n  }\n}\n\n// MARK: - FormField\n\nextension TextInputFormField: FormField {\n\n  var height: CGFloat { 44.0 }\n\n  func register(for tableView: UITableView) {\n    tableView.register(TextInputCell.self, forCellReuseIdentifier: \"TextInputCell\")\n  }\n\n  func dequeue(for tableView: UITableView, at indexPath: IndexPath) -\u003e UITableViewCell {\n    let cell = tableView.dequeueReusableCell(withIdentifier: \"TextInputCell\", for: indexPath) as! TextInputCell\n    cell.delegate = self\n    cell.configure(viewModel)\n    return cell\n  }\n}\n```\n\n## Form Demo\n\nCheck out the demo for more detail about different types of cell.\n\n```bash\ngit clone git@github.com:nimblehq/ios-form.git\n\npod install\n```\n\n## License\n\nThis project is Copyright (c) 2014 and onwards. It is free software,\nand may be redistributed under the terms specified in the [LICENSE] file.\n\n[LICENSE]: /LICENSE\n\n## About\n\n![Nimble](https://assets.nimblehq.co/logo/dark/logo-dark-text-160.png)\n\nThis project is maintained and funded by Nimble.\n\nWe love open source and do our part in sharing our work with the community!\nSee [our other projects][community] or [hire our team][hire] to help build your product.\n\n[community]: https://github.com/nimblehq\n[hire]: https://nimblehq.co/","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnimblehq%2Fios-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnimblehq%2Fios-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnimblehq%2Fios-form/lists"}