MagazineLayout 1.7.0

MagazineLayout 1.7.0

Bryan KellerBryn Bodayle 维护。



  • Airbnb

MagazineLayout

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

Swift Package Manager compatible Carthage compatible Version License Platform Swift

介绍

MagazineLayout 是一组 对象 UICollectionViewLayout 子类,用于垂直滚动网格和项目列表的布局。与 UICollectionViewFlowLayout 相比,MagazineLayout 支持许多其他功能

  • 基于总可用宽度的分数项宽度
    • 列表布局的完整宽度(类似于 UITableView
    • 网格布局的半宽度、三分之一宽度等。
  • 仅在垂直维度中进行自适应尺寸
  • 每个项目的自适应尺寸首选项(在任何地方进行大小调整和静态大小项目)
  • 自适应头部和底部
  • 基于节隐藏或显示头部和底部
  • 固定(粘性)头部和底部
  • 可以根据节的每个节隐藏/显示的节背景
  • 为项目和相关视图自定义插入和删除动画

其他特性

  • 按节指定水平项目间距
  • 按节指定垂直行间距
  • 按节指定节内边距
  • 按节指定项目内边距

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

房屋搜索 体验搜索 愿望清单 主页
Homes Search Experiences Search Wish list Home
高级主页 高级主页游览 我的旅行 旅行详情
Plus Home Plus Home Tour Trips Trip Detail

目录

示例应用

可供展示和测试MagazineLayout的一些功能的示例应用可用。您可以在./Example/MagazineLayoutExample.xcworkspace中找到它。

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

使用示例应用

首次打开示例应用时,您将看到许多已预先填充的项目和部分。大多数项目都根据显示的文本自动调整大小。

Example App

如果您要删除样本内容并从一个空的集合视图中开始,可以点击导航栏中的重新加载图标。

重新加载菜单 无项
Reload Menu No Items

从该菜单中,您还可以将应用重置回原始样本数据。

添加新项

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

Add Item Screen

从添加屏幕中,您可以配置要插入到UICollectionView中的新项。您点击导航栏中的完成按钮后,项目将带有动画效果插入。

项配置选项

  • 部分索引(如果指定的索引不存在,将创建新部分)
  • 项索引(在指定部分中的位置)
  • 项内容/显示在项中的文本(如果使用.dynamic高度模式,这将改变项的高度)
  • 用于项背景的颜色
  • 宽度模式(控制项相对于可用宽度的宽度)
  • 高度模式(控制自 sizing 行为)

Add Item Animation

删除项目

要删除一个项目,只需简单地 taps 集合视图中的项目。项目将通过动画被删除。

Delete Item Animation

入门

需求

  • 部署目标 iOS 10.0+,tvOS 10.0+
  • Swift 4+
  • Xcode 10+

安装

Carthage

要使用 Carthage 安装 MagazineLayout,请在 Cartfile 中添加 github "airbnb/MagazineLayout",然后按照提供的集成教程操作 这里

CocoaPods

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

使用方法

MagazineLayout 集成到您的项目中后,使用它与集合视图非常简单。

配置单元格和头部视图

由于 UIKit 的不足MagazineLayout 需要自己的 UICollectionViewCellUICollectionReusableView 子类

  • MagazineLayoutCollectionViewCell
  • MagazineLayoutCollectionReusableView

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

或者,您可以在不从 MagazineLayout 提供的子类继承的情况下,复制 preferredLayoutAttributesFitting(_:) 的实现用于您的自定义单元格和可重用视图类型。

导入 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

配置代理

最后,是时候根据需求配置布局了。类似于UICollectionViewFlowLayoutUICollectionViewDelegateFlowLayoutMagazineLayout通过其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)
  }
  
}

如果您已经按照以上步骤操作,您应该已经拥有一个使用MagazineLayoutUICollectionView!如果您想使用现有的示例,请查看包含的示例项目,并参看使用说明

贡献

MagazineLayout欢迎修复、改进和功能扩展。如果您想做出贡献,请创建一个包含详细更改描述的拉取请求。

一般来说,如果您提议一个API重大更改或对现有功能的更改,请考虑通过提交issue而不是pull request来提出它;我们将把issue作为一个公开论坛来讨论这个提议是否合理。

维护者

Bryan Keller

Bryn Bodayle

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

贡献者

MagazineLayout的实现离不开我在Airbnb的几位同事的贡献和支持。特别是Bryn Bodayle,自从MagazineLayout启动以来,他已经审查了每一个PR,而且还帮助讨论和解决了无数个难以解决的UICollectionViewUIKit问题。

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

  • Laura Skelton
  • Eric Horacek
  • Tyler Hedrick
  • Michael Bachand
  • 萧潘
  • 李勇
  • Luke Hiesterman
  • Jordan Harband

许可证

MagazineLayout在Apache License 2.0下发布。更多信息请见LICENSE