Bento🍱 弁当
是一种常见的日式食盒食品,通常是将米饭或面条、鱼或肉、腌菜和熟蔬菜放在一起。
Bento 是一个 Swift 库,用于在 UITableView 上构建基于组件的界面。
- 声明式:提供一种简单的方法来构建
UITableView接口 - Diffing:当数据更改时,通过漂亮的动画重新加载您的 UI
- 基于组件的:设计可重用组件,并在应用程序的多个屏幕之间共享您自定义 UI
在我们的经验中,这使得 UI 相关代码更容易构建和维护。我们的目标是使 UI 成为状态的函数(即:UI = f(state)),这使得 Bento 成为响应式编程的完美选择。
内容📋
安装💾
- Cocoapods
target 'MyApp' do
pod 'Bento'
end- Carthage
github "Babylonpartners/Bento"
它是怎样的?🧐
当构建一个Box时,你只需要关心Sections和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)它是如何工作的?🤔
设置
Bento会自动在第一次请求UITableView或UICollectionView渲染Bento Box时执行数据源和代理的设置。
换句话说,为了使用Bento,不能使用你自己的数据源和代理。如果你想响应当地代理的消息,Bento不支持作为功能,你可以考虑使用prepareForBoxRendering(_:)提供自定义适配器。
| 集合视图 | 适配器基类 | 必需的协议实现 |
|---|---|---|
UITableView |
TableViewAdapterBase |
UITableViewDataSource & UITableViewDelegate |
UICollectionView |
CollectionViewAdapterBase |
UITableViewDataSource & UITableViewDelegate |
Box📦
Box 是该库的基本组件,本质上是对UITableView内容的虚拟表示。它有两个泛型参数 - SectionId和RowId -,这些是用于Section<SectionId, RowId>和Node<RowId>的唯一标识符,由差异引擎使用以执行UITableView内容的动画更改。Box只是一个包含多个section数组的容器。
章节和节点🏗
Section和Node是Box的构建块
Section是对UITableView中section的抽象,它定义了是否应显示头部或尾部。Node是对UITableView中row的抽象,它定义了数据的渲染方式。
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
}身份标识🎫
身份标识是差异算法执行更改的关键概念之一。
对于一般业务问题,两个实例的完全不等性并不一定意味着在身份上的不等—它仅仅意味着如果两个实例的身份相同,所持有数据已经更改。
(更多信息请参阅此处.)
SectionID和ItemID分别定义了章节及其项目的身份标识。
可渲染项🖼
Renderable类似于React的组件。它是即将显示的真实UITableViewCell的抽象。这个想法是使其能够创建小的独立组件,这些组件可以在您的应用的许多部分中重用。
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的算术💡
存在几个自定义操作符,它们提供语法糖来简化Bento的构建
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如你所注意到的
- 操作符
|-+具有SectionConcatenationPrecedence; - 操作符
|---+具有NodeConcatenationPrecedence
NodeConcatenationPrecedence高于|-+ / SectionConcatenationPrecedence,这意味着首先计算节点。
上述表达式的顺序是
Section() |---+ Node()=>SectionBox() |-+ Section()=>Box
条件运算符❓
除了 |-+ 和 |---+ 连接运算符之外,Bento 还具备条件连接运算符
|-?用于Section|---?用于Node
它们用于为闭包中的 Section 或 Node 提供在 Bool 和 Optional 幸运路径中的条件,通过 .iff 和 .some 函数实现。
以下是一些示例
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`
}|---? 对于 Node 的工作方式完全相同。
组件 & 样式表🎨
Bento 包含一系列通用组件,如 `Description,TextInput`、`EmptySpace` 等。Bento 使用样式表为组件提供样式。
样式表是一种定义特定视图应该如何渲染的方式。组件的职责是提供应显示的内容,而样式表提供一种样式,用于如何呈现它。字体、颜色、对齐方式应纳入样式表中。
样式表支持键路径,以便于组合。
let styleSheet = LabelStyleSheet()
.compose(\.numberOfLines, 3)
.compose(\.font, UIFont.preferredFont(forTextStyle: .body))样式表可以与 Bento 的组件一起使用。您需要做的只是使用正确的样式表。
return .empty
|-+ Section(id: .first)
|---+ Node(
id: .componentId,
component: Component.Description(
text: "Text",
styleSheet: Component.Description.StyleSheet()
.compose(\.text.font, UIFont.preferredFont(forTextStyle: .body))
)
)示例😎
附加文档📙
开发安装🛠
如果您想克隆存储库以供贡献使用或运行示例应用,您需要安装其依赖项,这些依赖项存储为git子模块。
git submodule update --init --recursive
或者,如果您已经安装了Carthage,您可以使用它来完成同样的操作。
carthage checkout
项目现状🤷♂️
| 功能 | 状态 |
|---|---|
UITableView |
|
UICollectionView |
|
| Carthage支持 | |
| 免费函数作为替代操作符 |
开发资源
-
定义必须由Bento组件遵守的要求,以及开发自定义组件的最佳实践。
贡献✍️
欢迎并高度重视贡献!
- 如果您有任何问题,请创建一个带有
问题标签的问题; - 如果您有功能请求,您可以创建一个带有
功能请求标签的问题; - 如果您发现了错误,请自由创建一个带有
错误标签的问题,或者提交一个修复的PR。
