Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

nimblehq/ios-form

Repository files navigation

iOS Form

Form Demo New Contact
Form demo New Contact

Implement iOS Form with UITableView

Create a base controller

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)
 }
}

Detail about FormDataSource

The class FormDataSource contains data that is used for the form.

final class FormDataSource {
 private(set) var sections: [FormSection] = []
}

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
 }
}

FormHeader

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

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
 }
}

Form Demo

Check out the demo for more detail about different types of cell.

git clone git@github.com:nimblehq/ios-form.git
pod install

License

This project is Copyright (c) 2014 and onwards. It is free software, and may be redistributed under the terms specified in the LICENSE file.

About

Nimble

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.

About

UITableView with Protocol Oriented Programming

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

AltStyle によって変換されたページ (->オリジナル) /