{"id":18925205,"url":"https://github.com/babylonhealth/bento","last_synced_at":"2025-04-06T13:10:55.713Z","repository":{"id":52430613,"uuid":"118751888","full_name":"babylonhealth/Bento","owner":"babylonhealth","description":"Swift library for building component-based interfaces on top of UITableView and UICollectionView 🍱","archived":false,"fork":false,"pushed_at":"2020-09-01T11:53:19.000Z","size":14014,"stargazers_count":372,"open_issues_count":7,"forks_count":11,"subscribers_count":32,"default_branch":"develop","last_synced_at":"2024-04-11T15:06:50.868Z","etag":null,"topics":["declarative","diff","diffing","ios","ios-swift","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/babylonhealth.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":"2018-01-24T10:47:12.000Z","updated_at":"2024-03-04T23:15:34.000Z","dependencies_parsed_at":"2022-08-19T01:51:01.134Z","dependency_job_id":null,"html_url":"https://github.com/babylonhealth/Bento","commit_stats":null,"previous_names":["babylonpartners/bento"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babylonhealth%2FBento","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babylonhealth%2FBento/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babylonhealth%2FBento/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babylonhealth%2FBento/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/babylonhealth","download_url":"https://codeload.github.com/babylonhealth/Bento/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247485287,"owners_count":20946398,"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","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":["declarative","diff","diffing","ios","ios-swift","swift","uitableview"],"created_at":"2024-11-08T11:09:57.414Z","updated_at":"2025-04-06T13:10:55.692Z","avatar_url":"https://github.com/babylonhealth.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# [Bento](https://en.wikipedia.org/wiki/Bento) 🍱 弁当\n\n\u003e #### 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.\n\n**Bento** is a Swift library for building component-based interfaces on top of `UITableView`.\n\n- **Declarative:**  provides a painless approach for building `UITableView` interfaces\n- **Diffing:** reloads your UI with beautiful animations when your data changes\n- **Component-based:**  Design reusable components and share your custom UI across multiple screens of your app\n\nIn 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.\n\n## Content 📋\n\n- [Installation](#installation-)\n- [What's it like?](#whats-it-like-)\n- [How does it work?](#how-does-it-work-)\n- [Componments \u0026 StyleSheets](#components--stylesheets-)\n- [Examples](#examples-)\n- [Additional documentation](#additional-documentation-)\n- [Development installation](#development-installation-)\n- [State of the project](#state-of-the-project-%EF%B8%8F)\n- [Development Resources](#development-resources)\n- [Contributing](#contributing-%EF%B8%8F)\n\n### Installation 💾\n\n* Cocoapods\n\n```ruby\ntarget 'MyApp' do\n    pod 'Bento'\nend\n```\n\n* Carthage\n\n```\ngithub \"Babylonpartners/Bento\"\n```\n\n### What's it like? 🧐\n\nWhen building a `Box`, all you need to care about are `Sections`s and `Node`s.\n\n```swift\nlet box = Box\u003cSectionId, RowId\u003e.empty\n            |-+ Section(id: SectionId.user,header: EmptySpaceComponent(height: 24, color: .clear))\n            |---+ Node(id: RowID.user, component: IconTitleDetailsComponent(icon: image, title: patient.name))\n            |-+ Section(id: SectionId.consultantDate, header: EmptySpaceComponent(height: 24, color: .clear))\n            |---+ Node(id: RowID.loading, component: LoadingIndicatorComponent(isLoading: true))\n\n        tableView.render(box)\n```\n\n### How does it work? 🤔\n\n### Setup\n\nBento automatically performs the data source and delegate setup upon the very first time `UITableView` or `UICollectionView` is asked to render a Bento `Box`.\n\nIn 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(_:)`.\n\n| Collection View | Adapter Base Class | Required Protocol Conformances |\n| ---- | ---- | ---- |\n| `UITableView` | `TableViewAdapterBase` | `UITableViewDataSource` \u0026 `UITableViewDelegate` |\n| `UICollectionView` | `CollectionViewAdapterBase` | `UITableViewDataSource` \u0026 `UITableViewDelegate` |\n\n#### Box 📦\n\n`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\u003cSectionId, RowId\u003e` and `Node\u003cRowId\u003e`, 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.\n\n#### Sections and Nodes 🏗\n\n`Section`s and `Node`s are building blocks of a `Box`:\n\n- `Section` is an abstraction of `UITableView`'s section which defines whether a header or footer should be shown.\n- `Node` is an abstraction of `UITableView`'s row which defines how the data is rendered.\n\n```swift\nstruct Section\u003cSectionId: Hashable, RowId: Hashable\u003e {\n    let id: SectionId\n    let header: AnyRenderable?\n    let footer: AnyRenderable?\n    let rows: [Node\u003cRowId\u003e]\n}\n\npublic struct Node\u003cIdentifier: Hashable\u003e {\n    let id: Identifier\n    let component: AnyRenderable\n}\n```\n\n\n#### Identity 🎫\nIdentity, one of the key concepts, is used by the diffing algorithm to perform changes.\n\n \u003e 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.\n\n (More info [here](https://github.com/RACCommunity/FlexibleDiff).)\n\n`SectionID` and `ItemID` define the identity of sections and their items, respectively.\n\n#### Renderable 🖼\n\n`Renderable` is similar to [React](https://github.com/facebook/react)'s [Component](https://reactjs.org/docs/react-component.html)s. 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.\n\n```swift\npublic protocol Renderable: class {\n    associatedtype View: UIView\n\n    func render(in view: View)\n}\n\nclass IconTextComponent: Renderable {\n    private let title: String\n    private let image: UIImage\n\n    init(image: UIImage,\n         title: String) {\n        self.image = image\n        self.title = title\n    }\n\n    func render(in view: IconTextCell) {\n        view.titleLabel.text = title\n        view.iconView.image = image\n    }\n}\n```\n\n#### Bento's arithmetics 💡\n\nThere are several custom operators that provide syntax sugar to make it easier to build `Bento`s:\n\n```swift\nprecedencegroup ComposingPrecedence {\n    associativity: left\n    higherThan: NodeConcatenationPrecedence\n}\n\nprecedencegroup NodeConcatenationPrecedence {\n    associativity: left\n    higherThan: SectionConcatenationPrecedence\n}\n\nprecedencegroup SectionConcatenationPrecedence {\n    associativity: left\n    higherThan: AdditionPrecedence\n}\n\ninfix operator |-+: SectionConcatenationPrecedence\ninfix operator |-?: SectionConcatenationPrecedence\ninfix operator |---+: NodeConcatenationPrecedence\ninfix operator |---?: NodeConcatenationPrecedence\n\nlet bento = Box.empty\n\t|-+ Section(id: SectionID.first) // 2\n\t|---+ Node(id: RowID.someId, Component()) // 1\n```\n\nAs you might have noticed:\n* `|-+` has `SectionConcatenationPrecedence`;\n* `|---+` has `NodeConcatenationPrecedence`\n\n`NodeConcatenationPrecedence` is higher than `|-+ / SectionConcatenationPrecedence`, meaning Nodes will be computed first. \n\nThe order of the expression above is:\n\n1. `Section() |---+ Node()` =\u003e `Section`\n2. `Box() |-+ Section()` =\u003e `Box`\n\n#### Conditional operators ❓\n\nIn addition to the `|-+` and `|---+` concatenation operators, Bento has conditional concatenation operators:\n* `|-?` for `Section`\n* `|---?` for `Node`\n\nThey are used to provide a `Section` or `Node` in a closure for the `Bool` and `Optional` happy path, via the `.iff` and `.some` functions.\n\nHere are some examples:\n```swift\nlet box = Box.empty\n    |-? .iff(aBoolCondition) {\n        Section()  // \u003c-- Section only added if `boolCondition` is `true`\n    }\n```\n```swift\nlet box = Box.empty\n    |-? anOptional.map { unwrappedOptional in  // \u003c-- the value of anOptional unwrapped\n        Section()  // \u003c-- Section only added if `anOptional` is not `nil`\n    }\n```\n\n`|---?` works in exactly the same way for `Node`.\n\n### Components \u0026 StyleSheets 🎨\nBento includes set of generic components like ``Description`, `TextInput`, `EmptySpace` etc. Bento uses StyleSheets to style components.\n\nStyleSheets 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. \n\nStyleSheets support KeyPaths for easier composition.\n\n```swift\nlet styleSheet = LabelStyleSheet()\n    .compose(\\.numberOfLines, 3)\n    .compose(\\.font, UIFont.preferredFont(forTextStyle: .body))\n```\n\nStyleSheets can be used with Bento's components. All you need to do is to use correct stylesheet:\n\n```swift\nreturn .empty\n  |-+ Section(id: .first)\n  |---+ Node(\n         id: .componentId,\n         component: Component.Description(\n             text: \"Text\",\n             styleSheet: Component.Description.StyleSheet()\n                 .compose(\\.text.font, UIFont.preferredFont(forTextStyle: .body))\n         )\n   )\n```\n\n### Example 😎\n\n![](Resources/example3.gif) \n\n### Additional documentation 📙\n- [Common use cases](./Documentation/common_usecases.md)\n\n### Development Installation 🛠\n\nIf 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:\n\n```\ngit submodule update --init --recursive\n```\n\nOr, if you have Carthage installed, you can use it to do the same thing:\n\n```\t\t\ncarthage checkout\n```\n\n### State of the project 🤷‍♂️\n\nFeature | Status\n--- | ---\n`UITableView` | ✅\n`UICollectionView` | ✅\nCarthage Support | ✅\nFree functions as alternative to the operators | ❌\n\n### Development Resources\n- [Bento Component Contract](Bento/Diff/ComponentContract.md)\n\n  Define requirements that must be complied by the components from Bento, and best practices for developing a custom component.\n\n### Contributing ✍️\n\nContributions are very welcome and highly appreciated! ❤️ Here's how to do it:\n\n- If you have any questions feel free to create an issue with a `question` label;\n- If you have a feature request you can create an issue with a `Feature request` label;\n- If you found a bug feel free to create an issue with a `bug` label or open a PR with a fix.\n\n#### Image attributions\n\n[Coffee](https://pixabay.com/en/coffee-beans-coffee-beans-caffeine-3457587/)\n[Pomegranate fruit](https://pixabay.com/en/pomegranate-fruit-exotic-fruit-3383814/)\n[Cherries](https://pixabay.com/en/cherries-sweet-cherries-fruit-red-3433775/)\n[Strawberries](https://pixabay.com/en/strawberries-season-spring-fruit-3359755/) \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabylonhealth%2Fbento","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbabylonhealth%2Fbento","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabylonhealth%2Fbento/lists"}