MagazineLayout
一款能够以垂直滚动网格和列表形式排列视图的集合视图布局。
介绍
MagazineLayout
是一组 对象 UICollectionViewLayout
子类,用于垂直滚动网格和项目列表的布局。与 UICollectionViewFlowLayout
相比,MagazineLayout
支持许多其他功能
- 基于总可用宽度的分数项宽度
- 列表布局的完整宽度(类似于
UITableView
) - 网格布局的半宽度、三分之一宽度等。
- 列表布局的完整宽度(类似于
- 仅在垂直维度中进行自适应尺寸
- 每个项目的自适应尺寸首选项(在任何地方进行大小调整和静态大小项目)
- 自适应头部和底部
- 基于节隐藏或显示头部和底部
- 固定(粘性)头部和底部
- 可以根据节的每个节隐藏/显示的节背景
- 为项目和相关视图自定义插入和删除动画
其他特性
- 按节指定水平项目间距
- 按节指定垂直行间距
- 按节指定节内边距
- 按节指定项目内边距
这些能力使我们能够在 Airbnb 应用中构建各种各样的屏幕,其中许多是点击量最高的屏幕。以下是一些使用 MagazineLayout
布局的屏幕示例
房屋搜索 | 体验搜索 | 愿望清单 | 主页 |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
高级主页 | 高级主页游览 | 我的旅行 | 旅行详情 |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
目录
示例应用
可供展示和测试MagazineLayout的一些功能的示例应用可用。您可以在./Example/MagazineLayoutExample.xcworkspace
中找到它。
注意:请确保使用.xcworkspace
文件,而不是.xcodeproj
文件,因为后者无法访问MagazineLayout.framework
。
使用示例应用
首次打开示例应用时,您将看到许多已预先填充的项目和部分。大多数项目都根据显示的文本自动调整大小。
如果您要删除样本内容并从一个空的集合视图中开始,可以点击导航栏中的重新加载图标。
重新加载菜单 | 无项 |
---|---|
![]() |
![]() |
从该菜单中,您还可以将应用重置回原始样本数据。
添加新项
要添加新项,请点击导航栏中的添加图标。
从添加屏幕中,您可以配置要插入到UICollectionView
中的新项。您点击导航栏中的完成按钮后,项目将带有动画效果插入。
项配置选项
- 部分索引(如果指定的索引不存在,将创建新部分)
- 项索引(在指定部分中的位置)
- 项内容/显示在项中的文本(如果使用
.dynamic
高度模式,这将改变项的高度) - 用于项背景的颜色
- 宽度模式(控制项相对于可用宽度的宽度)
- 高度模式(控制自 sizing 行为)
删除项目
要删除一个项目,只需简单地 taps 集合视图中的项目。项目将通过动画被删除。
入门
需求
- 部署目标 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
需要自己的 UICollectionViewCell
和 UICollectionReusableView
子类
MagazineLayoutCollectionViewCell
MagazineLayoutCollectionReusableView
这两种类型可以确保在使用 MagazineLayout
时,单元格和补充视图可以正确自适大小。请确保您应用程序中的自定义单元格和可重用视图类型分别从 MagazineLayoutCollectionViewCell
和 MagazineLayoutCollectionReusableView
继承。
或者,您可以在不从 MagazineLayout
提供的子类继承的情况下,复制 preferredLayoutAttributesFitting(_:)
的实现用于您的自定义单元格和可重用视图类型。
导入 MagazineLayout
在您想使用 MagazineLayout
的文件顶部(可能是 UIView
或 UIViewController
的子类),导入 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")
由于单元格、头部和尾部可以自适应大小(背景不可自适应),在示例中,MyCustomCell
、MyCustomHeader
和MyCustomFooter
必须实现正确的preferredLayoutAttributesFitting(_:)
方法。请参阅设置单元格和头部。
设置数据源
现在您已经将视图类型与集合视图注册,接下来是连接数据源。与任何集合视图集成一样,您的数据源需要遵循UICollectionViewDataSource
。如果拥有您的集合视图的同一对象也是您的数据源,您可以这样做
collectionView.dataSource = self
配置代理
最后,是时候根据需求配置布局了。类似于UICollectionViewFlowLayout
和UICollectionViewDelegateFlowLayout
,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而不是pull request来提出它;我们将把issue作为一个公开论坛来讨论这个提议是否合理。
维护者
Bryan Keller
Bryn Bodayle
如果您或您的公司发现MagazineLayout
很有用,请告诉我们!
贡献者
MagazineLayout
的实现离不开我在Airbnb的几位同事的贡献和支持。特别是Bryn Bodayle,自从MagazineLayout
启动以来,他已经审查了每一个PR,而且还帮助讨论和解决了无数个难以解决的UICollectionView
和UIKit
问题。
我还要感谢以下人员,他们为MagazineLayout
的成功铺平了道路
- Laura Skelton
- Eric Horacek
- Tyler Hedrick
- Michael Bachand
- 萧潘
- 李勇
- Luke Hiesterman
- Jordan Harband
许可证
MagazineLayout
在Apache License 2.0下发布。更多信息请见LICENSE。