EpoxyCore 0.9.0

EpoxyCore 0.9.0

Eric HoracekKieraj MumickTyler Hedrick 维护。



EpoxyCore 0.9.0

  • 作者
  • 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 包管理器 (SPM) 进行安装。

CocoaPods

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

pod 'Epoxy'

Epoxy分为每个模块,因此您只需包括所需的内容。

Swift Package Manager (SPM)

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

  1. 在Xcode中,选择“文件”→“Swift Packages”→“添加包依赖”。
  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。有关类型文档,请参阅在Swift Package Index上托管的Epoxy DocC文档

还有一个完整的示例应用程序,其中包含大量的示例。您可以通过在Epoxy.xcworkspace中的EpoxyExample方案中运行或浏览其源代码来启动。

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

开始使用

EpoxyCollectionView

EpoxyCollectionView提供了一种声明性API来驱动UICollectionView的内容。CollectionViewControllerUIViewController的一个子类,让您可以轻松启动一个带有声明性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
      }
  })
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组件,其中包含一个符合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

布局组是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

您可以在其维基百科页面中了解有关EpoxyLayoutGroups的最新信息,或者通过浏览代码文档来获取更多信息。

常见问题解答(FAQ)

贡献

欢迎贡献代码!我们非常乐意得到帮助以改进这个库。请自由浏览问题列表以寻找需要修复的问题。如果您有功能请求或缺陷,请创建一个新的问题以便我们可以跟踪。贡献者应遵循行为准则

许可证

Epoxy 基于 Apache License 2.0 发布。有关详细信息,请参阅LICENSE

鸣谢

Logo 设计由Alana HanadaJonard La Rosa 完成