Epoxy 是一套用于在 Swift 中构建 UIKit 应用的声明性 UI API。Epoxy 受到来自 Android 上优秀的 Epoxy 框架 以及 Swift 中的其他声明性 UI 框架,如 SwiftUI 的启发。
Epoxy 在 Airbnb 开发并支持了数万屏应用,这些应用被发送到数百万用户手中。它已经经过多年的开发和优化,由 数十名贡献者 开发。
以下是我们在 Airbnb 应用中使用 Epoxy 构建的几个示例屏幕。我们对 Epoxy 的使用范围从最简单的表单和静态屏幕到最复杂和动态的功能。
目录
安装
Epoxy 可以通过 CocoaPods 或 Swift 包管理器 安装。
CocoaPods
要开始使用CocoaPods使用Epoxy,请将以下内容添加到您的Podfile
中,然后按照集成指南 进行操作。
Epoxy被拆分为每个module
的,因此您只需包含您需要的部分。
Swift Package Manager (SPM)
要使用Swift Package Manager 安装Epoxy,您可以按照Apple发布的教程 使用Epoxy仓库的当前版本的URL进行操作。
在Xcode中,选择“文件”→“Swift包”→“添加包依赖”
输入https://github.com/airbnb/epoxy-ios.git
Epoxy分成每个模块的
a href="https://swiftlang.cn/package-manager/#products" rel="nofollow">库产品,因此您只需包含您需要的部分。
模块
Epoxy采用模块化架构,您只需包含对您的用例而言必需的模块。
文档和教程
欲获取完整文档和分步骤教程,请查看 维基 。有关类型级别文档,请参阅托管在 Swift Package Index 上的 Epoxy DocC 文档 。
还有一个包含大量示例的完整示例应用程序,您可以通过 Epoxy.xcworkspace
中的 EpoxyExample
方案运行它,或浏览其 源代码 。
如果您仍有问题,请随时创建一个新的 问题 。
开始使用
EpoxyCollectionView
EpoxyCollectionView
提供了一个用于驱动 UICollectionView
内容的声明式 API。《CollectionViewController》是 UIViewController
的一个可继承的子类,它能让您轻松通过声明式 API 创建一个基于 UICollectionView
的视图控制器。
以下代码示例将在 UICollectionView
中渲染一个单元格,该单元格包含一个渲染在其中的 TextRow
组件。TextRow
是一个简单的 UIView
,包含两个标签,并符合 EpoxyableView
协议。
您可以直接使用部分来创建 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
}
}
}
您可以在其 维基条目 中了解更多有关 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
}))
}
}
您可以在其 维基条目 中了解更多有关 EpoxyBars
的信息,或通过浏览 代码文档 。
环氧导航控制器
环氧导航控制器
提供了一种声明式API,用于驱动UINavigationController
的导航栈。
以下代码示例展示了如何使用它轻松地驱动一个具有多个视图控制器流程的功能。
源代码 结果
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页面 或通过浏览代码文档 了解更多关于环氧导航控制器
的信息。
环氧展示
环氧展示
提供了一种声明式API,用于驱动UIViewController
的模态展示。
以下代码示例展示了如何使用它轻松地驱动一个首次出现时显示模态的功能。
源代码 结果
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页面 或通过浏览代码文档 了解更多关于环氧展示
的信息。
环氧布局组
布局组是基于UIKit自动布局 容器的结构,受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 )
}
}
您可以在其wiki页面 或通过浏览代码文档 了解更多关于环氧布局组
的信息。
常见问题解答(FAQ)
贡献
我们欢迎贡献!我们很高兴帮助改进这个库。请随意浏览开放的 问题 来寻找需要工作的事情。如果您有任何功能请求或错误,请打开新问题,以便我们可以跟踪它。贡献者应遵循 行为准则 。
许可证
Epoxy 在 Apache 许可证 2.0 之下发布。有关详细信息,请参阅 LICENSE
。
鸣谢
标志设计由 Alana Hanada 和 Jonard La Rosa 完成。