环氧演示文稿 0.9.0

EpoxyPresentations 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 开发并支持了数万屏应用,这些应用被发送到数百万用户手中。它已经经过多年的开发和优化,由 数十名贡献者 开发。

以下是我们在 Airbnb 应用中使用 Epoxy 构建的几个示例屏幕。我们对 Epoxy 的使用范围从最简单的表单和静态屏幕到最复杂和动态的功能。

主页详情 主页照片 消息 注册
Home Details Home Photos Messaging Registration

目录

安装

Epoxy 可以通过 CocoaPodsSwift 包管理器 安装。

CocoaPods

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

pod 'Epoxy'

Epoxy被拆分为每个module,因此您只需包含您需要的部分。

Swift Package Manager (SPM)

要使用Swift Package Manager安装Epoxy,您可以按照Apple发布的教程使用Epoxy仓库的当前版本的URL进行操作。

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

Epoxy分成每个模块的

a href="https://swiftlang.cn/package-manager/#products" rel="nofollow">库产品,因此您只需包含您需要的部分。

模块

Epoxy采用模块化架构,您只需包含对您的用例而言必需的模块。

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

文档和教程

欲获取完整文档和分步骤教程,请查看 维基。有关类型级别文档,请参阅托管在 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
      }
  })
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

您可以在其 维基条目 中了解更多有关 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

您可以在其 维基条目 中了解更多有关 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
        })
    }
  }
}
Screenshot

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

您可以在其wiki页面或通过浏览代码文档了解更多关于环氧展示的信息。

环氧布局组

布局组是基于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页面或通过浏览代码文档了解更多关于环氧布局组的信息。

常见问题解答(FAQ)

贡献

我们欢迎贡献!我们很高兴帮助改进这个库。请随意浏览开放的 问题 来寻找需要工作的事情。如果您有任何功能请求或错误,请打开新问题,以便我们可以跟踪它。贡献者应遵循 行为准则

许可证

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

鸣谢

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