• Stars
    star
    372
  • Rank 114,885 (Top 3 %)
  • Language
    Swift
  • License
    MIT License
  • Created almost 7 years ago
  • Updated about 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Swift library for building component-based interfaces on top of UITableView and UICollectionView 🍱

Bento 🍱 弁当

is a single-portion take-out or home-packed meal common in Japanese cuisine. A traditional bento holds rice or noodles, fish or meat, with pickled and cooked vegetables, in a box.

Bento is a Swift library for building component-based interfaces on top of UITableView.

  • Declarative: provides a painless approach for building UITableView interfaces
  • Diffing: reloads your UI with beautiful animations when your data changes
  • Component-based: Design reusable components and share your custom UI across multiple screens of your app

In our experience it makes UI-related code easier to build and maintain. Our aim is to make the UI a function of state (i.e: UI = f(state)), which makes Bento a perfect fit for Reactive Programming.

Content 📋

Installation 💾

  • Cocoapods
target 'MyApp' do
    pod 'Bento'
end
  • Carthage
github "Babylonpartners/Bento"

What's it like? 🧐

When building a Box, all you need to care about are Sectionss and Nodes.

let box = Box<SectionId, RowId>.empty
            |-+ 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? 🤔

Setup

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 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
UITableView TableViewAdapterBase UITableViewDataSource & UITableViewDelegate
UICollectionView CollectionViewAdapterBase UITableViewDataSource & UITableViewDelegate

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 to perform animated changes of the UITableView content. Box is just a container for an array of sections.

Sections and Nodes 🏗

Sections and Nodes are building blocks of a Box:

  • Section is an abstraction of UITableView's section which defines whether a header or footer should be shown.
  • Node is an abstraction of UITableView's row which defines how the data is rendered.
struct Section<SectionId: Hashable, RowId: Hashable> {
    let id: SectionId
    let header: AnyRenderable?
    let footer: AnyRenderable?
    let rows: [Node<RowId>]
}

public struct Node<Identifier: Hashable> {
    let id: Identifier
    let component: AnyRenderable
}

Identity 🎫

Identity, one of the key concepts, is used by the diffing algorithm to perform changes.

For general business concerns, full inequality of two instances does not necessarily mean inequality in terms of identity — it just means the data being held has changed if the identity of both instances is the same.

(More info here.)

SectionID and ItemID define the identity of sections and their items, respectively.

Renderable 🖼

Renderable is similar to React's Components. It's an abstraction of the real UITableViewCell that is going to be displayed. The idea is to make it possible to create small independent components that can be reused across many parts of your app.

public protocol Renderable: class {
    associatedtype View: UIView

    func render(in view: View)
}

class IconTextComponent: Renderable {
    private let title: String
    private let image: UIImage

    init(image: UIImage,
         title: String) {
        self.image = image
        self.title = title
    }

    func render(in view: IconTextCell) {
        view.titleLabel.text = title
        view.iconView.image = image
    }
}

Bento's arithmetics 💡

There are several custom operators that provide syntax sugar to make it easier to build Bentos:

precedencegroup ComposingPrecedence {
    associativity: left
    higherThan: NodeConcatenationPrecedence
}

precedencegroup NodeConcatenationPrecedence {
    associativity: left
    higherThan: SectionConcatenationPrecedence
}

precedencegroup SectionConcatenationPrecedence {
    associativity: left
    higherThan: AdditionPrecedence
}

infix operator |-+: SectionConcatenationPrecedence
infix operator |-?: SectionConcatenationPrecedence
infix operator |---+: NodeConcatenationPrecedence
infix operator |---?: NodeConcatenationPrecedence

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

As you might have noticed:

  • |-+ has SectionConcatenationPrecedence;
  • |---+ has NodeConcatenationPrecedence

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

The order of the expression above is:

  1. Section() |---+ Node() => Section
  2. Box() |-+ Section() => Box

Conditional operators

In addition to the |-+ and |---+ concatenation operators, Bento has conditional concatenation operators:

  • |-? for Section
  • |---? for Node

They are used to provide a Section or Node in a closure for the Bool and Optional happy path, via the .iff and .some functions.

Here are some examples:

let box = Box.empty
    |-? .iff(aBoolCondition) {
        Section()  // <-- Section only added if `boolCondition` is `true`
    }
let box = Box.empty
    |-? 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.

Components & StyleSheets 🎨

Bento includes set of generic components like ``Description, TextInput`, `EmptySpace` etc. Bento uses StyleSheets to style components.

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.

StyleSheets support KeyPaths for easier composition.

let styleSheet = LabelStyleSheet()
    .compose(\.numberOfLines, 3)
    .compose(\.font, UIFont.preferredFont(forTextStyle: .body))

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

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

Example 😎

Additional documentation 📙

Development Installation 🛠

If you want to clone the repo for contributing or for running the example app you will need to install its dependencies which are stored as git submodules:

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
UITableView
UICollectionView
Carthage Support
Free functions as alternative to the operators

Development Resources

  • Bento Component Contract

    Define requirements that must be complied by the components from Bento, and best practices for developing a custom component.

Contributing ✍️

Contributions are very welcome and highly appreciated! ❤️ Here's how to do it:

  • If you have any questions feel free to create an issue with a question label;
  • If you have a feature request you can create an issue with a Feature request label;
  • If you found a bug feel free to create an issue with a bug label or open a PR with a fix.

Image attributions

Coffee Pomegranate fruit Cherries Strawberries

More Repositories

1

fastText_multilingual

Multilingual word vectors in 78 languages
Jupyter Notebook
1,195
star
2

DrawerKit

DrawerKit lets an UIViewController modally present another UIViewController in a manner similar to the way Apple's Maps app works.
Swift
780
star
3

ios-playbook

Ruby
397
star
4

orbit-mvi

An MVI framework for Kotlin and Android
Kotlin
384
star
5

iOS-Interview-Demo

Interview Demo Project for babylon health
Objective-C
200
star
6

certificate-transparency-android

Certificate transparency for Android and Java
Kotlin
198
star
7

ReactiveFeedback

Unidirectional reactive architecture
Swift
158
star
8

rgat

A TensorFlow implementation of Relational Graph Attention Networks, paper: https://arxiv.org/abs/1904.05811
Python
114
star
9

hmrb

Python
70
star
10

android-playbook

Babylon Health Android Team Playbook
68
star
11

Stevenson

Stevenson is a Vapor framework designed to build integrations between Slack apps, GitHub, JIRA and CI services (CircleCI).
Swift
58
star
12

counterfactual-diagnosis

Python
54
star
13

Tota11y

Accessibility visualization toolkit for web content creators and editors.
JavaScript
44
star
14

fuzzymax

Code for the paper: Don't Settle for Average, Go for the Max: Fuzzy Sets and Max-Pooled Word Vectors, ICLR 2019.
Python
43
star
15

corrsim

Code for the papers: Correlation Coefficients and Semantic Textual Similarity, NAACL-HLT 2019 & Correlations between Word Vector Sets, EMNLP-IJCNLP 2019.
Python
35
star
16

primock57

Dataset of 57 mock medical primary care consultations: audio, consultation notes, human utterance-level transcripts.
Python
35
star
17

lit-fhir

Opinionated library for easily constructuring FHIR (http://hl7.org/fhir) resources in Scala and Java.
Scala
35
star
18

Wall-E

A bot that monitors and manages your pull requests.
Swift
32
star
19

github-proxy

A minimal caching proxy to GitHub's REST & GraphQL APIs
Python
29
star
20

neuralTPPs

Shell
27
star
21

medisim

Medical Similarity Dataset creation from SNOMED
Python
27
star
22

simba

Semantic similarity measures from Babylon Health
Python
16
star
23

decoding-decoders

Python
12
star
24

EHR-Rel

Biomedical concept relatedness benchmark sampled from electronic health records
10
star
25

multiverse

MultiVerse: Probabilistic Programming Language for Causal Reasoning
Python
9
star
26

TwinNetworks

A library for handling Structural Causal Models and performing interventional and counterfactual inference on them.
Python
9
star
27

MCSG

Python
8
star
28

web-interview

JavaScript
6
star
29

nameko-extras

Nameko run with autoloading, logging file CLI option
Python
6
star
30

event-stream-registry-ui

A React component for observing and monitoring event streams.
TypeScript
5
star
31

slack011y-bus

Python
4
star
32

sticky-layoutmanager

Java
2
star
33

fhir-hydrant

FHIR templating engine
Scala
2
star
34

snow-owl

🦉 Snow Owl - production ready, scalable terminology server (SNOMED CT, ICD-10, LOINC, dm+d, ATC and others)
Dockerfile
2
star
35

terraform-provider-aws-babylon

Go
1
star
36

laymaker

1
star