Epoxy 是一套用于在 Swift 中构建 UIKit 应用程序的声明式 UI API。Epoxy 受 Android 上的出色 Epoxy 框架 的启发,并受到其他 Swift 声明式 UI 框架(如 SwiftUI )的影响。
Epoxy 在 Airbnb 开发,为成千上万的屏幕提供动力,这些屏幕被发送到数百万用户。它已被数十位贡献者开发和改进多年。
以下是我们使用 Epoxy 构建的 Airbnb 应用程序的一些示例屏幕。我们对 Epoxy 的使用范围从最简单的形式和静态屏幕到最先进和动态的功能。
目录
安装
Epoxy 可以使用 CocoaPods 或 Swift 包管理器 (SPM) 进行安装。
CocoaPods
要开始使用Cocoapods集成Epoxy,请将以下内容添加到您的Podfile
中,然后按照集成说明 操作。
Epoxy被分成针对每个模块 的podspecs ,这样您只需包含所需的部分。
Swift包管理器(SPM)
要使用Swift包管理器 安装Epoxy,您可以按照Apple发布的教程 操作,使用带有当前版本的Epoxy仓库的URL。
在Xcode中,选择“文件”→“Swift Packages”→“添加包依赖”
输入https://github.com/airbnb/epoxy-ios.git
Epoxy是针对每个模块 分离的库产品 ,所以您只需包含所需的部分。
模块
Epoxy采用模块化架构,您只需包含针对您的用例所需的部分。
文档和教程
有关完整文档和逐步教程,请查看wiki 。有关类型级别的文档,请参阅托管在Swift包索引 上的Epoxy DocC文档 。
还有一个完全的示例应用,其中包含许多示例,您可以通过Epoxy.xcworkspace
中的EpoxyExample
方案运行,或通过其源代码 进行浏览。
如果您还有疑问,请随时创建一个新问题 。
入门
EpoxyCollectionView
EpoxyCollectionView
为驱动UICollectionView
的内容提供了一个声明式API。CollectionViewController
是一个可子类的UIViewController
,让您可以使用声明式API轻松创建一个带有UICollectionView
的视图控制器。
以下代码示例将在UICollectionView
中渲染一个单元格,并在该单元格中渲染一个带有TextRow
组件。 TextRow
是一个简单的UIView
,包含两个标签,并符合EpoxyableView
协议。
您可以直接创建带有部分(sections)的CollectionViewController
实例,例如这个带有可选行视图控制器。
源码 结果
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
进行子类处理,例如这个跟踪运行计数的视图控制器。
源码 结果
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
}
}
}
您可以通过其wiki条目 或通过浏览代码文档 来详细了解EpoxyCollectionView
。
EpoxyBars
EpoxyBars
为在UIViewController
中渲染固定顶部、固定底部或输入辅助 条栈提供了一个声明式API。
以下代码示例将渲染一个固定在UIViewController
视图底部的ButtonRow
组件。ButtonRow
是一个简单的UIView
组件,包含一个单行UIButton
,并符合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
}))
}
}
您可以通过其wiki条目 或通过浏览代码文档 来详细了解EpoxyBars
。
EpoxyNavigationController
EpoxyNavigationController
提供了驱动 UINavigationController
导航堆栈的声明式 API。
以下代码示例展示了如何使用此 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
})
}
}
}
您可以在其 Wiki 条目 或通过浏览 代码文档 了解更多关于 EpoxyNavigationController
的信息。
EpoxyPresentations
EpoxyPresentations
提供了驱动 UIViewController
模态显示的声明式 API。
以下代码示例展示了如何使用此 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
})
}
}
}
您可以在其 Wiki 条目 或通过查看 代码文档 获取更多关于 EpoxyPresentations
的信息。
EpoxyLayoutGroups
LayoutGroups 是UIKit的 Auto Layout 容器,灵感来自 SwiftUI的 HStack
和 VStack
,它允许您轻松地将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)
贡献(Contributing)
我们欢迎通过 pull requests 来助力改进这个库。您可以浏览开放的 问题 来寻找需要工作的地方。如果您有功能请求或错误报告,请创建一个新 issue 以便我们跟踪它们。贡献者需要遵循 行为准则 。
许可证(License)
Epoxy 在 Apache License 2.0 许可下发布。有关详细信息,请参阅 LICENSE
文件。
鸣谢(Credits)
标识设计由 Alana Hanada 和 Jonard La Rosa 完成。