EpoxyCollectionView 0.9.0

EpoxyCollectionView 0.9.0

由以下人员维护:Eric HoracekTyler HedrickKieraj Mumick



  • 作者:
  • Airbnb

Epoxy logo

Build Status Swift Package Manager compatible Platform Swift Versions

Epoxy 是一套用于在 Swift 中构建 UIKit 应用程序的声明式 UI API。Epoxy 受 Android 上的出色 Epoxy 框架 的启发,并受到其他 Swift 声明式 UI 框架(如 SwiftUI)的影响。

Epoxy 在 Airbnb 开发,为成千上万的屏幕提供动力,这些屏幕被发送到数百万用户。它已被数十位贡献者开发和改进多年。

以下是我们使用 Epoxy 构建的 Airbnb 应用程序的一些示例屏幕。我们对 Epoxy 的使用范围从最简单的形式和静态屏幕到最先进和动态的功能。

主页详情 房屋照片 信息 注册
Home Details Home Photos Messaging Registration

目录

安装

Epoxy 可以使用 CocoaPodsSwift 包管理器 (SPM) 进行安装。

CocoaPods

要开始使用Cocoapods集成Epoxy,请将以下内容添加到您的Podfile中,然后按照集成说明操作。

pod 'Epoxy'

Epoxy被分成针对每个模块podspecs,这样您只需包含所需的部分。

Swift包管理器(SPM)

要使用Swift包管理器安装Epoxy,您可以按照Apple发布的教程操作,使用带有当前版本的Epoxy仓库的URL。

  1. 在Xcode中,选择“文件”→“Swift Packages”→“添加包依赖”
  2. 输入https://github.com/airbnb/epoxy-ios.git

Epoxy是针对每个模块分离的库产品,所以您只需包含所需的部分。

模块

Epoxy采用模块化架构,您只需包含针对您的用例所需的部分。

模块 描述
Epoxy 包含以下所有模块,使用单个导入语句
EpoxyCollectionView Epoxy的声明式API,用于驱动UICollectionView的内容
EpoxyNavigationController Epoxy的声明式API,用于驱动UINavigationController的导航堆栈
EpoxyPresentations Epoxy的声明式API,用于驱动UIViewController的模态呈现
EpoxyBars Epoxy的声明式API,用于向UIViewController添加固定顶部/底部栏堆栈
EpoxyLayoutGroups Epoxy的声明式API,用于在UIKit中构建与Swift UI的堆栈API类似的可组合布局
EpoxyCore 用于构建所有Epoxy声明式UI API的基础API

文档和教程

有关完整文档和逐步教程,请查看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
      }
  })
Screenshot

或者您可以为更复杂的情况对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
      }
  }
}
Screenshot

您可以通过其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
      }))
  }
}
Screenshot

您可以通过其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
        })
    }
  }
}
Screenshot

您可以在其 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
        })
    }
  }
}
Screenshot

您可以在其 Wiki 条目 或通过查看 代码文档 获取更多关于 EpoxyPresentations 的信息。

EpoxyLayoutGroups

LayoutGroups 是UIKit的 Auto Layout 容器,灵感来自 SwiftUI的 HStackVStack,它允许您轻松地将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()
ActionRow screenshot

如你所见,这与 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()
IconRow screenshot

组也支持嵌套,因此您可以使用多个组轻松创建复杂布局

源码 结果
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)
  }
}
IconRow screenshot

您可以在其维基页面中了解有关 EpoxyLayoutGroups 的更多信息,或者通过浏览 代码文档

常见问题解答(FAQ)

贡献(Contributing)

我们欢迎通过 pull requests 来助力改进这个库。您可以浏览开放的 问题 来寻找需要工作的地方。如果您有功能请求或错误报告,请创建一个新 issue 以便我们跟踪它们。贡献者需要遵循 行为准则

许可证(License)

Epoxy 在 Apache License 2.0 许可下发布。有关详细信息,请参阅 LICENSE 文件。

鸣谢(Credits)

标识设计由 Alana HanadaJonard La Rosa 完成。