Epoxy 0.9.0

Epoxy 0.9.0

Eric HoracekKieraj MumickTyler Hedrick维护。



 
依赖
EpoxyCore= 0.9.0
EpoxyLayoutGroups= 0.9.0
EpoxyCollectionView= 0.9.0
EpoxyBars= 0.9.0
EpoxyNavigationController= 0.9.0
EpoxyPresentations= 0.9.0
 

Epoxy 0.9.0

  • Airbnb

Epoxy logo

Build Status Swift Package Manager compatible Platform Swift Versions

Epoxy是一套用于在Swift中构建< різнёнка href="https://developer.apple.com/documentation/uikit" rel="nofollow">UIKit应用程序的声明式UI API。Epoxy受到由Android上的Epoxy框架以及Swift中的其他声明式UI框架(如SwiftUI)启发和影响。

Epoxy是在Airbnb开发并由数十位贡献者开发和完善多年的。它为数千个屏幕提供了动力,这些屏幕被部署给数百万用户。

以下是我们使用Epoxy在Airbnb应用程序中构建的一些示例屏幕截图。我们对Epoxy的使用从我们最简单的表单和静态屏幕到我们最复杂和动态的功能。

首页详情 首页照片 消息传递 注册
Home Details Home Photos Messaging Registration

目录

安装

Epoxy可以使用CocoaPodsSwift Package Manager进行安装。

CocoaPods

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

pod 'Epoxy'

Epoxy被分成每个podspec的模块,因此您只需包括您需要的部分。

Swift包管理器(SPM)

要使用Swift包管理器安装Epoxy,请遵循Apple发布的教程并使用Epoxy仓库的当前版本URL。

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

Epoxy被分成每个模块库产品,因此您只需包括您需要的部分。

模块

Epoxy有模块化架构,因此您只需包含您用例所必需的部分。

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

文档和教程

请查看wiki以获取完整文档和逐步教程。对于类型级文档,请参阅Epoxy DocC文档,该文档托管在Swift包索引上。

还有一个包含许多示例的完整样例应用,您可以通过在 Epoxy.xcworkspace 中运行 EpoxyExample 方案,或者浏览其 源代码 来使用。

如果您还有疑问,请随时创建一个新的 问题

入门指引

EpoxyCollectionView

EpoxyCollectionView 提供了一个声明式 API,用于驱动 UICollectionView 的内容。重写 CollectionViewController 可无限期地创建含有声明式 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
      }
  })
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 提供了一个声明式 API,用于在 UIViewController 中渲染固定顶部、固定底部或输入栏堆栈。

以下代码示例将在 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。

以下代码示例展示了如何使用它轻松地驱动包含多个视图控制器流的功能。

来源 结果
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

您可以在其 维基百科 页面或通过浏览 代码文档 了解更多关于 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
        })
    }
  }
}
Screenshot

您可以在其 维基百科 页面或通过浏览 代码文档 了解更多关于 EpoxyPresentations 的信息。

EpoxyLayoutGroups

布局组是受 SwiftUI 的 Auto Layout 容器启发而创建的 UIKit,类似于 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

您可以在其wiki条目中了解更多关于EpoxyLayoutGroups的信息,或者通过浏览代码文档

常见问题解答(FAQ)

贡献

欢迎Pull requests!我们很乐意看到这个库的改进。欢迎浏览开放问题,看看哪些地方需要工作。如果您有功能请求或bug,请创建一个新的问题,以便我们可以跟踪。贡献者应遵循代码行为指南

许可

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

致谢

Logo设计由Alana HanadaJonard La Rosa完成。