MagazineLayout

一个能够以垂直滚动网格和列表形式布局视图的集合视图布局。

Swift Package Manager compatible Carthage compatible Version License Platform Swift

简介

MagazineLayout 是一个 UICollectionViewLayout 子类,用于以垂直滚动网格和列表的形式布局项目。与 UICollectionViewFlowLayout 相比,MagazineLayout 支持许多附加功能。

其他功能

这些功能使我们能够在 Airbnb 应用程序中构建各种各样的屏幕,其中许多是我们流量最高的屏幕。以下是一些使用 MagazineLayout 布局的屏幕示例:

房源搜索 体验搜索 愿望清单 首页
Homes Search Experiences Search Wish list Home
Plus 房源 Plus 房源参观 旅程 旅程详情
Plus Home Plus Home Tour Trips Trip Detail

目录

示例应用

提供了一个示例应用程序来展示并使您能够测试 MagazineLayout 的一些功能。它可以在 ./Example/MagazineLayoutExample.xcworkspace 中找到。

注意:请确保使用 .xcworkspace 文件,而不是 .xcodeproj 文件,因为后者无法访问 MagazineLayout.framework

使用示例应用

首次打开示例应用程序时,您将看到许多预先填充的项目和 section。大多数项目都配置为根据它们显示的文本进行自适应大小。

Example App

如果您想删除示例内容并从空白集合视图开始,您可以点击导航栏中的重新加载图标。

重新加载菜单 没有项目
Reload Menu No Items

从此菜单中,您还可以将应用程序重置为原始示例数据。

添加新项目

要添加新项目,请点击导航栏中的添加图标。

Add Item Screen

从添加屏幕中,您可以配置一个新项目以插入到 UICollectionView 中。点击导航栏中的完成按钮后,该项目将以动画形式插入。

项目配置选项

Add Item Animation

删除项目

要删除项目,只需在集合视图中点击该项目即可。该项目将以动画形式删除。

Delete Item Animation

开始使用

要求

安装

Carthage

要使用 Carthage 安装 MagazineLayout,请将 github "airbnb/MagazineLayout" 添加到您的 Cartfile,然后按照 此处的 集成教程进行操作。

CocoaPods

要使用 CocoaPods 安装 MagazineLayout,请将 pod 'MagazineLayout' 添加到您的 Podfile,然后按照 此处的 集成教程进行操作。

用法

一旦您将 MagazineLayout 集成到您的项目中,就可以轻松地将其与集合视图一起使用。

设置单元格和页眉

由于 UIKit 中的缺陷MagazineLayout 需要其自己的 UICollectionViewCellUICollectionReusableView 子类。

这两种类型使单元格和补充视图在使用 MagazineLayout 时可以正确地自适应大小。**请确保您的应用程序中的自定义单元格和可重用视图类型分别继承自 MagazineLayoutCollectionViewCellMagazineLayoutCollectionReusableView。**

或者,您可以复制 preferredLayoutAttributesFitting(_:) 的实现以在您的自定义单元格和可重用视图类型中使用,而无需从 MagazineLayout 提供的类型继承。

导入 MagazineLayout

在您想要使用 MagazineLayout 的文件的顶部(可能是 UIViewUIViewController 子类),导入 MagazineLayout

import MagazineLayout 

设置集合视图

创建您的 UICollectionView 实例,并传入 MagazineLayout 实例作为布局参数。

let layout = MagazineLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)

确保将 collectionView 添加为子视图,然后使用自动布局正确约束它,或者手动设置其 frame 属性。

view.addSubview(collectionView)

collectionView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
  collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  collectionView.topAnchor.constraint(equalTo: view.topAnchor),
  collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])

注册单元格和补充视图

向您的集合视图注册您的单元格和可重用视图类型。

collectionView.register(MyCustomCell.self, forCellWithReuseIdentifier: "MyCustomCellReuseIdentifier")

// Only necessary if you want section headers
collectionView.register(MyCustomHeader.self, forSupplementaryViewOfKind: MagazineLayout.SupplementaryViewKind.sectionHeader, withReuseIdentifier: "MyCustomHeaderReuseIdentifier")

// Only necessary if you want section footers
collectionView.register(MyCustomFooter.self, forSupplementaryViewOfKind: MagazineLayout.SupplementaryViewKind.sectionFooter, withReuseIdentifier: "MyCustomFooterReuseIdentifier")

// Only necessary if you want section backgrounds
collectionView.register(MyCustomBackground.self, forSupplementaryViewOfKind: MagazineLayout.SupplementaryViewKind.sectionBackground, withReuseIdentifier: "MyCustomBackgroundReuseIdentifier")

因为单元格、页眉和页脚可以自适应大小(背景不自适应大小),所以在本例中,MyCustomCellMyCustomHeaderMyCustomFooter **必须**具有 preferredLayoutAttributesFitting(_:) 的正确实现。 请参阅设置单元格和页眉

设置数据源

现在您已经向您的集合视图注册了视图类型,现在是连接数据源的时候了。与任何集合视图集成一样,您的数据源需要符合 UICollectionViewDataSource。如果拥有您的集合视图的同一对象也是您的数据源,您可以简单地这样做:

collectionView.dataSource = self

配置代理

最后,现在是配置布局以满足您的需求的时候了。与 UICollectionViewFlowLayoutUICollectionViewDelegateFlowLayout 一样,MagazineLayout 通过其 UICollectionViewDelegateMagazineLayout 配置其布局。

要开始配置 MagazineLayout,请将集合视图的 delegate 属性设置为符合 UICollectionViewDelegateMagazineLayout 的对象。 如果拥有您的集合视图的同一对象也是您的代理,您可以简单地这样做:

collectionView.delegate = self

这是一个代理实现的示例:

extension ViewController: UICollectionViewDelegateMagazineLayout {

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeModeForItemAt indexPath: IndexPath) -> MagazineLayoutItemSizeMode {
    let widthMode = MagazineLayoutItemWidthMode.halfWidth
    let heightMode = MagazineLayoutItemHeightMode.dynamic
    return MagazineLayoutItemSizeMode(widthMode: widthMode, heightMode: heightMode)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, visibilityModeForHeaderInSectionAtIndex index: Int) -> MagazineLayoutSupplementaryViewVisibilityMode {
    return .visible(heightMode: .dynamic, pinToVisibleBounds: true)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, visibilityModeForFooterInSectionAtIndex index: Int) -> MagazineLayoutSupplementaryViewVisibilityMode {
    return .visible(heightMode: .dynamic, pinToVisibleBounds: false)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, visibilityModeForBackgroundInSectionAtIndex index: Int) -> MagazineLayoutBackgroundVisibilityMode {
    return .hidden
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, horizontalSpacingForItemsInSectionAtIndex index: Int) -> CGFloat {
    return  12
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, verticalSpacingForElementsInSectionAtIndex index: Int) -> CGFloat {
    return  12
  }
  
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetsForSectionAtIndex index: Int) -> UIEdgeInsets {
    return UIEdgeInsets(top: 0, left: 8, bottom: 24, right: 8)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetsForItemsInSectionAtIndex index: Int) -> UIEdgeInsets {
    return UIEdgeInsets(top: 24, left: 0, bottom: 24, right: 0)
  }
  
}

如果您按照上述步骤操作,您应该有一个使用 MagazineLayout 的工作正常的 UICollectionView!如果您想使用预先制作的示例,请查看包含的示例项目,以及使用它的说明

贡献

MagazineLayout 欢迎修复、改进和功能添加。如果您想贡献,请打开一个包含您的更改的详细描述的拉取请求。

作为经验法则,如果您要提出一个 API 破坏性更改或对现有功能的更改,请考虑通过打开一个 issue 来提出它,而不是一个拉取请求;我们将使用该 issue 作为公共论坛来讨论该提案是否有意义。

维护者

Bryan Keller

Bryn Bodayle

如果您或您的公司发现 MagazineLayout 有用,请告诉我们!

贡献者

如果没有我在 Airbnb 的几位同事的贡献和支持,MagazineLayout 是不可能实现的。特别是 Bryn Bodayle,自 MagazineLayout 成立以来,审查了每个 PR,并帮助讨论和解决了无数棘手的 UICollectionViewUIKit 问题。

我也要感谢以下人员,他们都为 MagazineLayout 的成功铺平了道路:

许可证

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