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 |
📦
BoxBox
是该库的基本组件,本质上是对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()
=>Section
Box() |-+ 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。