Epoxy 是一套用于在 Swift 中构建 UIKit 应用程序的声明性 UI API。Epoxy 受 Android 上的出色 Epoxy 框架 以及其他 Swift 声明性 UI 框架(如 SwiftUI )的启发和影响。
Epoxy 在 Airbnb 开发,并为数百万用户的应用程序中的数千个屏幕提供动力。它已经经过多年的开发和改进,由数十位贡献者 。
以下是我们在 Airbnb 应用程序中使用 Epoxy 构建的一些示例屏幕。我们对 Epoxy 的使用范围从最简单的表单和静态屏幕到最复杂和动态的功能。
目录
安装
Epoxy 可通过 CocoaPods 或 Swift 包管理器 (SPM) 进行安装。
CocoaPods
要使用Cocoapods开始使用Epoxy,请将以下内容添加到您的Podfile
中,然后按照集成说明 操作。
Epoxy分为每个模块 的,因此您只需包括所需的内容。
Swift Package Manager (SPM)
要使用Swift Package Manager 安装Epoxy,您可以按照由Apple发布的教程 操作,并使用当前版本的Epoxy仓库URL。
在Xcode中,选择“文件”→“Swift Packages”→“添加包依赖”。
输入https://github.com/airbnb/epoxy-ios.git
Epoxy为每个模块 提供了库产品 ,所以您只需包括所需的内容。
模块
Epoxy具有模块化架构,因此您只需包括您用例所需的内容即可。
文档和教程
有关完整文档和逐步教程,请查看wiki 。有关类型文档,请参阅在Swift Package Index 上托管的Epoxy DocC文档 。
还有一个完整的示例应用程序,其中包含大量的示例。您可以通过在Epoxy.xcworkspace 中的EpoxyExample 方案中运行或浏览其源代码来启动。
如果您还有疑问,请随时创建一个新的问题 。
开始使用
EpoxyCollectionView
EpoxyCollectionView
提供了一种声明性API来驱动UICollectionView
的内容。CollectionViewController
是UIViewController
的一个子类,让您可以轻松启动一个带有声明性API的带有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
}
}
}
您可以在它的wiki页面 中了解更多关于EpoxyCollectionView
的信息,或者通过浏览代码文档 。
EpoxyBars
EpoxyBars
提供了一种声明性API,用于在UIViewController
中渲染固定在顶部、底部或输入附件 条中的条堆。
以下代码示例将在UIViewController
视图的底部固定一个ButtonRow
组件。ButtonRow
是一个简单的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
}))
}
}
您可以在它的wiki页面 中了解更多关于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
布局组是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 )
}
}
您可以在其维基百科页面 中了解有关EpoxyLayoutGroups
的最新信息,或者通过浏览代码文档 来获取更多信息。
常见问题解答(FAQ)
贡献
欢迎贡献代码!我们非常乐意得到帮助以改进这个库。请自由浏览问题列表 以寻找需要修复的问题。如果您有功能请求或缺陷,请创建一个新的问题以便我们可以跟踪。贡献者应遵循行为准则 。
许可证
Epoxy 基于 Apache License 2.0 发布。有关详细信息,请参阅LICENSE
。
鸣谢
Logo 设计由Alana Hanada 和 Jonard La Rosa 完成