Skip to content

Commit

Permalink
Merge pull request #123 from TheAdamBorek/adam/improve-documentation
Browse files Browse the repository at this point in the history
Improves documentation of Bento
  • Loading branch information
mluisbrown authored Apr 4, 2019
2 parents b24b009 + 9455187 commit a30f616
Show file tree
Hide file tree
Showing 21 changed files with 161 additions and 44 deletions.
3 changes: 3 additions & 0 deletions Bento/Bento/Node.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import UIKit

/// Node is kept by a Section. Node requires an identifier for diff algorithm.
/// Node **always has** a visual representation, because a component needs to be provided.
/// To simplify, you can think of a Node in Bento as equivalent to a cell in UITableView / UICollectionView.
public struct Node<Identifier: Hashable> {
public let id: Identifier
let component: AnyRenderable
Expand Down
3 changes: 3 additions & 0 deletions Bento/Bento/Section.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import UIKit

/// Section is part of a Box and holds an array of nodes. SectionID needs to be provided for the diffing algorithm.
/// Section **can** have a visual representation if components are provided for the header and/or footer.
/// To simplify, you can think of a Section in Bento as equivalent to a section in a UITableView.
public struct Section<SectionID: Hashable, ItemID: Hashable> {
public typealias Item = Node<ItemID>

Expand Down
2 changes: 2 additions & 0 deletions Bento/Renderable/Renderable.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import UIKit

/// Protocol which every Component needs to conform to.
/// - View: UIView subtype which is the top level view type of the component.
public protocol Renderable {
associatedtype View: NativeView

Expand Down
2 changes: 2 additions & 0 deletions Bento/Views/Box.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ infix operator |---+: NodeConcatenationPrecedence
infix operator |---*: NodeConcatenationPrecedence
infix operator |---?: NodeConcatenationPrecedence

/// Box holds an array of sections which should be displayed on the screen.
/// Box **doesn't** have a visual representation.
public struct Box<SectionID: Hashable, ItemID: Hashable> {
public typealias Section = Bento.Section<SectionID, ItemID>

Expand Down
1 change: 1 addition & 0 deletions BentoKit/BentoKit/Components/Component.swift
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/// Namespace for all Components so they are easily located with Xcode hints.
public enum Component {}
7 changes: 7 additions & 0 deletions BentoKit/BentoKit/Components/DatePicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ extension Component {
public let configurator: (View) -> Void
public let styleSheet: StyleSheet

/// Creates Component.DatePicker
/// - parameter date: Date which should be preselected when opening the DatePicker.
/// - parameter minDate: Minimum allowable date.
/// - parameter maxDate: Maximum allowable date.
/// - parameter datePickerMode: Mode of a picker (date, date & time, etc.).
/// - parameter styleSheet: StyleSheet with styling.
/// - parameter didPickDate: Closure which is invoked when a date is selected.
public init(
date: Date? = nil,
minDate: Date? = nil,
Expand Down
7 changes: 7 additions & 0 deletions BentoKit/BentoKit/Components/MultilineTextInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ extension Component {
public let focusEligibility: FocusEligibility
public let styleSheet: Component.MultilineTextInput.StyleSheet

/// Creates Component.MultilineTextInput
/// - parameter text: Value of the input which should be presented. Can have more than 1 line.
/// - parameter placeholder: Placeholder which is displayed when `text` is empty.
/// - parameter showsFocusToolbar: Shows/hides inputAccessoryView of the TextView.
/// - parameter didChangeText: Closure which notifies of text changes.
/// - parameter didFinishEditing: Closure which notifies when user finishes editing.
/// - parameter styleSheet: StyleSheet with styling.
public init(
text: String,
placeholder: String,
Expand Down
6 changes: 6 additions & 0 deletions BentoKit/BentoKit/Components/OptionPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ extension Component {
public let configurator: (View) -> Void
public let styleSheet: StyleSheet

/// Creates Component.OptionPicker
/// - parameter options: Possible options to pick
/// - parameter selected: Option which should be preselected.
/// Has to be an element of `options`
/// - parameter didPickItem: Closure inovked when user picks an option
/// - parameter styleSheet: StyleSheet with styling for the view
public init<Option: BentoKit.Option>(
options: [Option],
selected: Option?,
Expand Down
10 changes: 9 additions & 1 deletion BentoKit/BentoKit/Components/Search.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ extension Component {
public final class Search: AutoRenderable {
public let configurator: (View) -> Void
public let styleSheet: StyleSheet


/// Creates Component.Search which representes UISearchBar from iOS.
/// - parameter placeholder: Text displayed when searchField is empty.
/// - parameter keyboardType: Type of the keybaord used for editing.
/// - parameter didBeginEditing: Notifies when searchBar becomes first responder.
/// - parameter textDidChange: Notifies when text changes.
/// - parameter showsCancelButton: Shows/hides the cancel button.
/// - parameter cancelButtonClicked: Notifies that cancel button has been clicked.
/// - parameter styleSheet: StyleSheet with styling.
public init(
placeholder: String? = nil,
keyboardType: UIKeyboardType = .default,
Expand Down
11 changes: 11 additions & 0 deletions BentoKit/BentoKit/Components/TextInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ extension Component {
public let styleSheet: StyleSheet
public let focusEligibility: FocusEligibility

/// Creates Component.TextInput
/// - parameter title: Label of a the text input which describes it (i.e: "E-mail").
/// - parameter placeholder: Placeholder which is displayed instead of text when text is empty.
/// - parameter text: Pre-filled value of the TextInput. It can be modifed by user. (i.e: "[email protected]"). Limited to only one line.
/// - parameter keyboardType: UIKit keyboard type.
/// - parameter isEnabled: Indicates if user can change a text value.
/// - parameter accessory: Additional icon displayed on the right side.
/// - parameter textWillChange: Closure which may rejected text changes.
/// - parameter textDidChange: Closure which notifies about text changes.
/// - parameter didTapAccessory: Closure which is invoked when user taps on the accessory.
/// - parameter styleSheet: StyleSheet with styling.
public init(
title: String? = nil,
placeholder: String? = nil,
Expand Down
12 changes: 12 additions & 0 deletions BentoKit/BentoKit/Components/TitledDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ public extension Component {
return heightComputer(width, inheritedMargins)
}

/// Creates a TitledDescription
/// - parameter texts: Array of TextValues which are displayed vertically on left side of the row.
/// - parameter detail: Description field displayed to the right of `texts`
/// - parameter image: Usually an image wrapped within `ImageOrLabel` component. Property makes it possible to be loaded async.
/// However, you need to guarantee fixed size across all changes.
/// - parameter accessory: Accessory which should be displayed near the right edge of the row.
/// - parameter badgeIcon: Badge icon which is displayed in the bottom-right corner of the `image`.
/// - parameter inputNodes: Input's component which is displayed when the `TitledDescription` becomes the first responder.
/// - parameter didTap: Closure which is invoked when whole row is tapped.
/// - parameter didTapAccessory: Closure which is invoked when tapping on the accessory.
/// - parameter interactionBehavior: Defines an behaviour when tapped. Usually `.becomeFirstResponder`.
/// - parameter styleSheet: StyleSheet how view should be styled (fonts, colors, text alignment)
public init(
texts: [TextValue] = [],
detail: TextValue? = nil,
Expand Down
10 changes: 10 additions & 0 deletions BentoKit/BentoKit/Components/Toggle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ extension Component {
public let configurator: (View) -> Void
public let styleSheet: StyleSheet

/// Creates Component.Toggle
/// - parameter title: Name/label/title of a row which is displayed on the left side.
/// - parameter attributedTitle: The same as `title` but allows for using attributed text.
/// - parameter image: Icon displayed on the left side of the row.
/// - parameter isOn: Indicates if the toggle shows true or false.
/// - parameter isEnabled: Indicates if user can change value of the toggle.
/// - parameter isRefreshing: If `true` it displays `UIActivityIndicator` instead of the switch view.
/// - parameter animateValueChange: Indicates if should animate setting of the `isOn` value.
/// - parameter styleSheet: StyleSheet with styling.
/// - parameter didChangeValue: Closure which notifies about true/false change.
public init(
title: String,
attributedTitle: NSAttributedString? = nil,
Expand Down
3 changes: 3 additions & 0 deletions BentoKit/BentoKit/Helpers/BoxTableViewAdapter.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Bento

/// - Warning: When you use `BoxTableViewAdapter` your `tableView.render(box)` cannot be invoked in `viewWillAppear`. We suggest to call it in
/// `viewDidAppear`. UIKit changes UITableView's `layoutMargins` in between `viewWillAppear` & `viewDidAppear`. The UITableView's `layoutMargins` is used
/// in the BoxTableViewAdapter's implementation.
public final class BoxTableViewAdapter<SectionId: Hashable, RowId: Hashable>
: TableViewAdapterBase<SectionId, RowId>,
UITableViewDataSource,
Expand Down
10 changes: 10 additions & 0 deletions Documentation/common_usecases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#Common UseCases
## Invoke action while tapping on a cell
The effect is similar to using `tableView(_:didSelectRowAt:)` method on `UITableViewDelegate`. You need to do few steps to be able to react on tapping on a cell.
1. `BentoKit` has a class named `InteractiveView`. Your component's view needs to inherit from this class.
2. Add `didTap: (() -> Void)?` closure to your component's init.
3. Bind `didTap` with the component view's `highlightingGesture` property in the component's `render(in:)` function:
```swift
//didTap's type is (() -> Void)?
view.highlightingGesture.didTap = didTap.map(HighlightingGesture.TapAction.resign)
```
1 change: 0 additions & 1 deletion Example/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
}

112 changes: 70 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,49 @@ In our experience it makes UI-related code easier to build and maintain. Our aim

## Content 📋

- [Installation](#installation-)
- [What's it like?](#whats-it-like-)
- [How does it work?](#how-does-it-work-)
- [BentoKit & StyleSheets](#bentokit--stylesheets-)
- [Examples](#examples-)
- [Installation](#installation-)
- [Additional documentation](#additional-documentation-)
- [Development installation](#development-installation-)
- [State of the project](#state-of-the-project-%EF%B8%8F)
- [Development Resources](#development-resources)
- [Contributing](#contributing-%EF%B8%8F)

### Installation 💾

* Cocoapods

```ruby
target 'MyApp' do
pod 'Bento'

# Optionally, if you want to install BentoKit & StyleSheets
pod 'BentoKit'
pod 'StyleSheets'
end
```

* Carthage

```
github "Babylonpartners/Bento"
```

### What's it like? 🧐

When building a `Box`, all you need to care about are `Sections`s and `Node`s.

```swift
let box = Box<SectionId, RowId>.empty
|-+ Section(id: SectionId.user,
header: EmptySpaceComponent(height: 24, color: .clear))
|---+ RowId.user <> IconTitleDetailsComponent(icon: image, title: patient.name)
|-+ Section(id: SectionId.consultantDate,
header: EmptySpaceComponent(height: 24, color: .clear))
|---+ RowId.loading <> LoadingIndicatorComponent(isLoading: true)

tableView.render(box)
|-+ Section(id: SectionId.user,header: EmptySpaceComponent(height: 24, color: .clear))
|---+ Node(id: RowID.user, component: IconTitleDetailsComponent(icon: image, title: patient.name))
|-+ Section(id: SectionId.consultantDate, header: EmptySpaceComponent(height: 24, color: .clear))
|---+ Node(id: RowID.loading, component: LoadingIndicatorComponent(isLoading: true))

tableView.render(box)
```

### How does it work? 🤔
Expand All @@ -44,7 +64,7 @@ tableView.render(box)

Bento automatically performs the data source and delegate setup upon the very first time `UITableView` or `UICollectionView` is asked to render a Bento `Box`.

In other words, for Bento to work, it cannot be overriden with your own data source and delegate. If you wish to respond to delegate messages which Bento does not support as a feature, you may consider supplying a custom adapter using `prepareForBoxRendering(_:)`.
In other words, for Bento to work, it cannot be overridden with your own data source and delegate. If you wish to respond to delegate messages which Bento does not support as a feature, you may consider supplying a custom adapter using `prepareForBoxRendering(_:)`.

| Collection View | Adapter Base Class | Required Protocol Conformances |
| ---- | ---- | ---- |
Expand All @@ -53,7 +73,7 @@ In other words, for Bento to work, it cannot be overriden with your own data sou

#### Box 📦

`Box ` is a fundamental component of the library, essentially a virtual representation of the `UITableView` content. It has two generic parameters - `SectionId` and `RowId` - which are unique identifiers for `Section<SectionId, RowId>` and `Node<RowId>`, used by the [diffing engine](https://github.com/RACCommunity/FlexibleDiff) to perform animated changes of the `UITableView` content.
`Box ` is a fundamental component of the library, essentially a virtual representation of the `UITableView` content. It has two generic parameters - `SectionId` and `RowId` - which are unique identifiers for `Section<SectionId, RowId>` and `Node<RowId>`, used by the [diffing engine](https://github.com/RACCommunity/FlexibleDiff) to perform animated changes of the `UITableView` content. Box is just a container for an array of sections.

#### Sections and Nodes 🏗

Expand Down Expand Up @@ -134,28 +154,26 @@ precedencegroup SectionConcatenationPrecedence {
higherThan: AdditionPrecedence
}

infix operator <>: ComposingPrecedence
infix operator |-+: SectionConcatenationPrecedence
infix operator |-?: SectionConcatenationPrecedence
infix operator |---+: NodeConcatenationPrecedence
infix operator |---?: NodeConcatenationPrecedence

let bento = Box.empty // 3
|-+ Section() // 2
|---+ RowId.id <> Component() // 1
let bento = Box.empty
|-+ Section(id: SectionID.first) // 2
|---+ Node(id: RowID.someId, Component()) // 1
```

As you might have noticed:
* `<>` has `ComposingPrecedence`;
* `|-+` has `SectionConcatenationPrecedence`;
* `|---+` has `NodeConcatenationPrecedence`

`<> / NodeConcatenationPrecedence` is higher than `|-+ / SectionConcatenationPrecedence`, meaning Nodes will be computed first.
`NodeConcatenationPrecedence` is higher than `|-+ / SectionConcatenationPrecedence`, meaning Nodes will be computed first.

The order of the expression above is:

1. `RowId.id <> Component()` => `Node`
2. `Section() |---+ Node()` => `Section`
3. `Box() |-+ Section()` => `Box`
1. `Section() |---+ Node()` => `Section`
2. `Box() |-+ Section()` => `Box`

#### Conditional operators ❓

Expand All @@ -174,33 +192,49 @@ let box = Box.empty
```
```swift
let box = Box.empty
   |-? .some(anOptional) { theOptional in // <-- the value of anOptional unwrapped
   |-? anOptional.map { unwrappedOptional in // <-- the value of anOptional unwrapped
       Section() // <-- Section only added if `anOptional` is not `nil`
   }
```

`|---?` works in exactly the same way for `Node`s
`|---?` works in exactly the same way for `Node`.

### Examples 😎
### BentoKit & StyleSheets 🎨
BentoKit it's a set of generic components like `TiteledDescription`, `Description`, `TextInput`, `EmptySpace` etc. BentoKit uses StyleSheets to style components.

Sections | Appointment | Movies
--- | --- | ---
![](Resources/example1.gif) | ![](Resources/example2.gif) | ![](Resources/example3.gif)
StyleSheets are a way to define **how** particular view should be rendered. Component's job is to provide **what** should be displayed while StyleSheets provide a style **how** it's done. Fonts, colors, alignment should go into StyleSheet.

### Installation 💾
StyleSheets support KeyPaths for easier composition.

* Cocoapods

```ruby
target 'MyApp' do
pod 'Bento'
end
```swift
let styleSheet = LabelStyleSheet()
.compose(\.numberOfLines, 3)
.compose(\.font, UIFont.preferredFont(forTextStyle: .body))
```
* Carthage

StyleSheets can be used with BentoKit's components. All you need to do is to use correct stylesheet:

```swift
return .empty
|-+ Section(id: .first)
|---+ Node(
id: .componentId,
component: Component.Description(
text: "Text",
styleSheet: Component.Description.StyleSheet()
.compose(\.text.font, UIFont.preferredFont(forTextStyle: .body))
)
)
```
github "Babylonpartners/Bento"
```

### Examples 😎

Movies | CollectionView | SignUp |
--- | --- | ---
![](Resources/example1.gif) | ![](Resources/example2.gif) | ![](Resources/example3.gif) |

### Additional documentation 📙
- [Common use cases](./Documentation/common_usecases.md)

### Development Installation 🛠

Expand All @@ -210,12 +244,6 @@ If you want to clone the repo for contributing or for running the example app yo
git submodule update --init --recursive
```

Or, if you have Carthage installed, you can use it to do the same thing:

```
carthage checkout
```

### State of the project 🤷‍♂️

Feature | Status
Expand Down
Binary file modified Resources/example1.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Resources/example2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Resources/example3.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions StyleSheets/StyleSheets/StyleSheet.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import UIKit

///Protocol for StyleSheets to allow using `compose()` to modify it's value using KeyPaths
public protocol StyleSheetProtocol {
associatedtype Element

Expand Down
4 changes: 4 additions & 0 deletions StyleSheets/StyleSheets/ViewStyleSheet.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import UIKit

/// Basic StyleSheet.
/// StyleSheets can be used to provide how components should look like.
/// Component should only takes data which reflects what is rendered.
/// StyleSheet's job is to provide how component's view should looks like.
open class ViewStyleSheet<View: UIView>: StyleSheetProtocol {

// MARK: Visual Appearance
Expand Down

0 comments on commit a30f616

Please sign in to comment.