Epoxy 是一套用于在 Swift 中构建 UIKit 应用程序的声明性 UI API。Epoxy 受 Android 中杰出的 Epoxy 框架 以及其他 Swift 中的声明性 UI 框架(如 SwiftUI )的启发。
Epoxy 是在 Airbnb 开发的,并为数百万用户的产品中的应用程序中的数千个屏幕提供动力。它已经经过多年由 数十名贡献者 开发和精炼。
以下是使用 Epoxy 构建的 Airbnb 应用程序的一些样本屏幕。我们对 Epoxy 的使用从最简单的表单和静态屏幕到最复杂和动态的特性。
目录
安装
Epoxy 可以通过 CocoaPods 或 Swift 包管理器 进行安装。
CocoaPods
要使用 Cocoapods 开始使用 Epoxy,请将以下内容添加到您的 Podfile
中,然后按照 集成说明 进行操作。
Epoxy 分别为每个 podspec 取名,以保证您只需包含所需的模块。
Swift Package Manager (SPM)
要使用 Swift Package Manager 安装 Epoxy,可以按照 Apple 发布的 教程 进行,使用当前版本 Epoxy 库的 URL。
在 Xcode 中,选择“文件”→“Swift 包”→“添加包依赖项”。
输入 https://github.com/airbnb/epoxy-ios.git
Epoxy 将每个 模块 分别分成库产品,因此您只需要包含所需的模块。
模块
Epoxy 具有模块化结构,因此您只需包含针对您的用例所需的模块。
文档和教程
有关完整文档和分步教程,请查看 wiki 。对于类型级别的文档,请参阅托管在 Swift 包索引 上的 Epoxy DocC 文档 。
此外,还有一个包含许多示例的全功能示例应用程序,您可以通过 Epoxy.xcworkspace
中的 EpoxyExample
方案运行它,或者浏览它的 源代码 。
如果您还有疑问,请随时创建一个新的问题 。
入门指南
EpoxyCollectionView
EpoxyCollectionView
提供了一个声明式 API 来驱动 UICollectionView
的内容。 CollectionViewController
是一个可子类化的 UIViewController
,它允许您轻松地使用声明式 API 初始化一个基于 UICollectionView
的 view controller。
下面的代码示例将渲染一个带有 TextRow
组件的单元格,该组件在该单元格中渲染。 TextRow
是一个简单的 UIView
,包含两个标签,符合 EpoxyableView
协议。
您可以直接使用带区段的 CollectionViewController
实例,例如此具有可选项的无序列表 view controller
源代码 结果
enum DataID {
case row
}
let viewController = CollectionViewController (
layout : UICollectionViewCompositionalLayout
.list (using : .init (appearance : .plain )),
items : {
TextRow.itemModel (
dataID : DataID.row ,
content : .init (title : " Tap me!" ),
style : .small )
.didSelect { _ in
// Handle selection
}
})
或者,您可以为更高级的场景子类化 CollectionViewController
,例如此跟踪运行计数的 view controller
源代码 结果
class CounterViewController : CollectionViewController {
init () {
let layout = UICollectionViewCompositionalLayout
.list (using : .init (appearance : .plain ))
super .init (layout : layout)
setItems (items, animated : false )
}
enum DataID {
case row
}
var count = 0 {
didSet {
setItems (items, animated : true )
}
}
@ItemModelBuilder
var items: [ItemModeling] {
TextRow.itemModel (
dataID : DataID.row ,
content : .init (
title : " Count \( count ) " ,
body : " Tap to increment" ),
style : .large )
.didSelect { [weak self ] _ in
self ? .count += 1
}
}
}
您可以在其 维基条目 中了解有关 EpoxyCollectionView
的更多信息,或者通过浏览 代码文档 。
EpoxyBars
EpoxyBars
提供了一个声明式 API,用于在 UIViewController
中渲染固定顶部、固定底部或 输入辅助 工具栏堆栈。
以下代码示例将渲染一个固定位于 UIViewController
视图底部的 ButtonRow
组件。 ButtonRow
是一个包含单个 UIButton
的简单 UIView
组件,将其约束到父视图的边距,符合 EpoxyableView
协议
源代码 结果
class BottomButtonViewController : UIViewController {
override func viewDidLoad () {
super .viewDidLoad ()
bottomBarInstaller.install ()
}
lazy var bottomBarInstaller = BottomBarInstaller (
viewController : self ,
bars : bars)
@BarModelBuilder
var bars: [BarModeling] {
ButtonRow.barModel (
content : .init (text : " Click me!" ),
behaviors : .init (didTap : {
// Handle button selection
}))
}
}
您可以在其 维基条目 中了解有关 EpoxyBars
的更多信息,或者通过浏览 代码文档 。
EpoxyNavigationController
EpoxyNavigationController
为驱动 UINavigationController
的导航堆栈提供了声明性 API。
以下代码示例展示了如何轻松驱动具有多个视图控制器流动的特征。
源代码 结果
class FormNavigationController : NavigationController {
init () {
super .init ()
setStack (stack, animated : false )
}
enum DataID {
case step1 , step2
}
var showStep2 = false {
didSet {
setStack (stack, animated : true )
}
}
@NavigationModelBuilder
var stack: [NavigationModel] {
.root (dataID : DataID.step1 ) { [weak self ] in
Step1ViewController (didTapNext : {
self ? .showStep2 = true
})
}
if showStep2 {
NavigationModel (
dataID : DataID.step2 ,
makeViewController : {
Step2ViewController (didTapNext : {
// Navigate away from this step.
})
},
remove : { [weak self ] in
self ? .showStep2 = false
})
}
}
}
您可以在其 维基页面 或通过浏览 代码文档 了解更多关于 EpoxyNavigationController
的信息。
EpoxyPresentations
EpoxyPresentations
为驱动 UIViewController
的模态显示提供了声明性 API。
以下代码示例展示了如何在使用时轻松地驱动首次出现时显示模态的特征。
源代码 结果
class PresentationViewController : UIViewController {
override func viewDidAppear (_ animated : Bool ) {
super .viewDidAppear (animated)
setPresentation (presentation, animated : true )
}
enum DataID {
case detail
}
var showDetail = true {
didSet {
setPresentation (presentation, animated : true )
}
}
@PresentationModelBuilder
var presentation: PresentationModel? {
if showDetail {
PresentationModel (
dataID : DataID.detail ,
presentation : .system ,
makeViewController : { [weak self ] in
DetailViewController (didTapDismiss : {
self ? .showDetail = false
})
},
dismiss : { [weak self ] in
self ? .showDetail = false
})
}
}
}
您可以在其 维基页面 或通过浏览 代码文档 了解更多关于 EpoxyPresentations
的信息。
EpoxyLayoutGroups
布局组是受 SwiftUI 的 自动布局 启发,类似 HStack
和 VStack
的 UIKit 容器,允许您轻松将 UIKit 元素组合成水平和垂直组。
VGroup
允许您垂直组合组件以创建如这种情况的堆叠组件
源代码 结果
// Set of dataIDs to have consistent
// and unique IDs
enum DataID {
case title
case subtitle
case action
}
// Groups are created declaratively
// just like Epoxy ItemModels
let group = VGroup (
alignment : .leading ,
spacing : 8 )
{
Label.groupItem (
dataID : DataID.title ,
content : " Title text" ,
style : .title )
Label.groupItem (
dataID : DataID.subtitle ,
content : " Subtitle text" ,
style : .subtitle )
Button.groupItem (
dataID : DataID.action ,
content : " Perform action" ,
behaviors : .init { button in
print (" Button tapped! \( button ) " )
},
style : .standard )
}
// install your group in a view
group.install (in : view)
// constrain the group like you
// would a normal subview
group.constrainToMargins ()
正如您所看到的,这与 Epoxy 中使用的其他 API 惊人地相似。一个重要的事情是要注意底部 install(in: view)
调用。既 HGroup
也 VGroup
都是用 UILayoutGuide
编写的,这可以防止有大型嵌套视图层次结构。为此,我们添加了这个 install
方法以防止用户手动添加子视图和布局指南。
使用 HGroup
几乎与 VGroup
完全相同,但组件现在是水平布局而不是垂直布局
源代码 结果
enum DataID {
case icon
case title
}
let group = HGroup (spacing : 8 ) {
ImageView.groupItem (
dataID : DataID.icon ,
content : UIImage (systemName : " person.fill" )! ,
style : .init (size : .init (width : 24 , height : 24 )))
Label.groupItem (
dataID : DataID.title ,
content : " This is an IconRow" )
}
group.install (in : view)
group.constrainToMargins ()
组也支持嵌套,因此您可以轻松创建由多个组组成的复杂布局
源代码 结果
enum DataID {
case checkbox
case titleSubtitleGroup
case title
case subtitle
}
HGroup (spacing : 8 ) {
Checkbox.groupItem (
dataID : DataID.checkbox ,
content : .init (isChecked : true ),
style : .standard )
VGroupItem (
dataID : DataID.titleSubtitleGroup ,
style : .init (spacing : 4 ))
{
Label.groupItem (
dataID : DataID.title ,
content : " Build iOS App" ,
style : .title )
Label.groupItem (
dataID : DataID.subtitle ,
content : " Use EpoxyLayoutGroups" ,
style : .subtitle )
}
}
您可以在其维基页面 或通过浏览代码文档 了解更多有关 EpoxyLayoutGroups
的信息。
常见问题解答 (FAQ)
贡献
欢迎提出 pull request!我们很乐意得到帮助,改善这个库。请随意查看开放的 问题 并寻找需要改进的地方。如果有功能请求或缺陷,请开一个新的 issue,我们可以跟踪它。贡献者应遵守行为准则 。
许可证
Epoxy 在 Apache License 2.0 下发布。有关详细信息,请参阅 LICENSE
。
鸣谢
标志设计由 Alana Hanada 和 Jonard La Rosa 完成