# Forms - Building table like UIs with mixed row types It is quite common with UIs laid out and styled as table views. But these tables are sometimes using rows with mixed types. A good example is iOS's general settings. Building tables with mixed row types are often hard to get right, especially if the rows being displayed might differ based on some configuration. If you ever attempted to maintain similar UIs, especially when backed by a `UITableView`, you are probably aware of the difficulties involved. To mitigate this, Form provides three helper views; `FormView`, `SectionView` and `RowView` for building table like UIs. These views are backed by `UIStackView`s, and laid out and styled to look like `UITableView`s. They were designed for convenience and are best suited for smaller tables. For more performant tables, Form provides [`TableKit`](./Tables.md) for working with `UITableView`s and reusable rows. Building forms using `FormView`, `SectionView` and `RowView` is straightforward: ```swift let form = FormView() let section = form.appendSection(header: "About") let row = section.appendRow(title: "Credits") bag += row.onValue { /* show credits */ } ``` Here we can see that we can build our UI more declaratively and directly. This is in sharp contrast to using table views where you have an indirection using indices, data sources and cells. As you build you table using code it is also simple to make them dynamic based on some configuration parameters: ```swift if hasFeature { let section = form.appendSection(header: "Feature") if hasSubFeature { let row = section.appendRow(title: "Sub feature") } } ``` To build this using table view's indirection would require a delicate juggling of section and row indices. ## FormView At the root of a form is the `FormView` that holds vertically laid out section views: ```swift let form = FormView() let section = SectionView(header: ..., footer: ...) form.append(section) ``` As adding sections to a form is so common there are convenience helpers to write this more succinctly: ```swift let section = form.appendSection(header: ..., footer: ...) ``` But it is worth pointing out that you can append any view to a form, not only section views, making it easier to build custom UI: ```swift form.append(customView) ``` ## SectionView A `SectionView` holds an array of vertically laid out row views, optionally starting with header and ending with a footer. Similar to `FormView` you can add any view to a section: ```swift let section = form.appendSection() section.append(customView) ``` But more commonly, you add row views instead: ```swift let row = RowView(title: ...) section.append(row) ``` Or more succinctly: ```swift let row = form.appendRow(title: ...) ``` By using `RowView`s we also ensure the layout is updated to use the provided `SectionStyle`'s `rowInsets` and `itemSpacing`: ```swift let style = SectionStyle.default.restyle { style in style.rowInset.left = 40 style.itemSpacing = 20 } let section = form.appendSection(style: style) ``` ## RowView A `RowView` holds an array of horizontally laid out views. You typically build a row starting out with a title (and optionally subtitle) and then appends (or prepends) more views to it: ```swift let row = RowView(title: "title", subtitle: "subtitle") .prepend(iconImage) .append("details") .append(.chevron) section.append(row) ``` Or more conveniently: ```swift let row = section.appendRow(title: "title", subtitle: "subtitle") .prepend(iconImage) .append("details") .append(.chevron) ``` ## RowAndProvider When adding a `RowView` to a section it returns a `RowAndProvider` holding both the row view and a `Signal<()>` for observing selections of the row: ```swift bag += section.appendRow(title: "title") // -> RowAndProvider> .onValue { /* row tapped */ } ``` A `RowAndProvider` behaves much like a standalone `RowView`, and you can continue appending and prepending views to its row: ```swift let row = section.appendRow(title: "title") // - RowAndProvider .prepend(iconImage) // - RowAndProvider .append("details") // - RowAndProvider .onValue { /* row tapped */ } ``` But as seen above `RowAndProvider` also takes the role of a signal so you can in the case above call `onValue` to observe the row being tapped. `RowAndProvider` is generic on a `Provider` type conforming to `SignalProvider`. As for the example above, the provider was just a basic signal `Signal<()>` for observing row taps. But if you append a view that conforms to `SignalProvider`, such as many `UIControl`s, `append` will return an updated `RowAndProvider` holding the added view as its provider: ```swift let enabledSwitch = UISwitch(...) let row = RowView(title: "title") // -> RowView .append(enabledSwitch) // -> RowAndProvider ``` Now `RowAndProvider` holds the switch and provides the switch's signal for convenience: ```swift bag += row.onValue { enabled in /* switch updated */ } ``` If you add another providing view to a `RowAndProvider` it will change to provide the latest view. ```swift bag += section.appendRow(title: "title") // -> RowAndProvider> .append(enabledSwitch) // -> RowAndProvider .onValue { enabled in ... } ``` If you want to opt out of changing the provider you can cast the appended provider to a `UIView`: ```swift bag += section.appendRow(title: "title") // -> RowAndProvider .append(enabledSwitch as UIView) // -> RowAndProvider .onValue { /* row tapped */ } bag += enabledSwitch.onValue { enabled in ... } ``` By using the power of Flow's signals together with forms we can build our UI and logic in a more declarative way: ```swift let feature: ReadWriteSignal bag += section.appendRow(title: "Feature") .append(UISwitch()) // -> The providedSignal is ReadWriteSignal .bidirectionallyBindTo(feature.atOnce()) ```