版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.11.05 星期二 |
前言
iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
1. UIKit框架(一) —— UIKit动力学和移动效果(一)
2. UIKit框架(二) —— UIKit动力学和移动效果(二)
3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)
6. UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)
7. UIKit框架(七) —— 动态尺寸UITableViewCell的实现(一)
8. UIKit框架(八) —— 动态尺寸UITableViewCell的实现(二)
9. UIKit框架(九) —— UICollectionView的数据异步预加载(一)
10. UIKit框架(十) —— UICollectionView的数据异步预加载(二)
11. UIKit框架(十一) —— UICollectionView的重用、选择和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)
13. UIKit框架(十三) —— 如何创建自己的侧滑式面板导航(一)
14. UIKit框架(十四) —— 如何创建自己的侧滑式面板导航(二)
15. UIKit框架(十五) —— 基于自定义UICollectionViewLayout布局的简单示例(一)
16. UIKit框架(十六) —— 基于自定义UICollectionViewLayout布局的简单示例(二)
17. UIKit框架(十七) —— 基于自定义UICollectionViewLayout布局的简单示例(三)
18. UIKit框架(十八) —— 基于CALayer属性的一种3D边栏动画的实现(一)
19. UIKit框架(十九) —— 基于CALayer属性的一种3D边栏动画的实现(二)
20. UIKit框架(二十) —— 基于UILabel跑马灯类似效果的实现(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定义viewController的转场和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定义viewController的转场和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定义布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定义布局 (二)
28. UIKit框架(二十八) —— 一个UISplitViewController的简单实用示例 (一)
29. UIKit框架(二十九) —— 一个UISplitViewController的简单实用示例 (二)
开始
首先看下主要内容
主要内容:在本教程中,您将学习如何使用iOS 13的新的声明性
UICollectionViewCompositionalLayout
API构建美观,现代的UICollectionView
布局。
下面看一下写作环境
Swift 5, iOS 13, Xcode 11
iOS Photos
和App Store
应用程序具有一些复杂的布局,这些布局是UICollectionViewFlowLayout
所无法提供的,具有多个滚动部分和可变大小的平铺布局。 您将学习如何在自己的照片浏览应用程序中创建这些功能!
注意:本教程中的项目屏幕截图使用
iOS 13
的新的黑暗模式。 无需执行相同操作即可使用本教程,但如果您不这样做,则您的应用看上去将与屏幕截图有所不同。
在Xcode中打开启动项目。 构建并运行。
您会看到一个功能正常但非常简单的应用程序,它显示相册。 您可以滚动列表以查看相册中的照片,然后点击任意一张照片以更详细地查看图像。
切换到Xcode
并快速浏览该项目。打开AppDelegate.swift
。当应用启动时,它会将AlbumDetailViewController
设置为初始视图控制器。 AlbumDetailViewController
的初始化程序将URL
指向包含图像的文件夹。
打开AlbumDetailViewController.swift
。 viewDidLoad()
通过调用configureCollectionView()
设置基本的UICollectionView
来显示PhotoItemCells
,后者使用iOS 13
的新UICollectionViewDiffableDataSource
配置集合视图数据源。
如果您之前没有遇到过UICollectionViewDiffableDataSource
,请不要担心!需要注意的重要部分是configureDataSource()
调用snapshotForCurrentState()
,后者从照片URL列表构建数据源快照。然后,它将快照应用于集合视图数据源。
最后,在文件底部的UICollectionViewDelegate
扩展中,您可以看到当用户选择一个项目时,该应用程序导航到PhotoDetailViewController
。
如果您有兴趣,请打开PhotoDetailViewController.swift
。这是一个非常简单的视图控制器类,用于显示图像,没有什么令人兴奋的事情。
Why UICollectionViews? A Brief Recap
请记住,UIKit提供了两个视图类来有效显示大量相似项:UITableView
和UICollectionView
。 乍一看,它们看起来非常相似-两者都显示一个项目列表,可以选择将其分组。
但是,table view
只能显示垂直列表中的项目,但是集合视图具有UICollectionViewLayout
,它控制项目在屏幕上的显示方式。
自从iOS 6
引入以来,UIKit提供了单个集合视图布局实现UICollectionViewFlowLayout
。 更复杂的布局是可能的,但不一定易于制作。
UICollectionViewCompositionalLayout
通过提供用于构建复杂布局的简单,灵活,声明性的API来进行更改。
Breaking Down a Layout
在开始构建Photos
应用之前,您需要了解一些核心布局概念。
集合视图显示重复的项目。 例如,Contacts
应用中的联系人。
许多应用程序将项目分组为Sections
,这些部分在逻辑上属于同一项目。 对于Contacts
应用程序,不同部分包含姓氏以相同字母开头的所有联系人。
在Contacts
应用中,情况变得如此复杂。 但是可以创建更复杂的布局。 例如,当在文件夹中查看文件时,WWDC视频应用程序在更宽的显示屏上使用两列布局,而Dropbox
应用程序在三列布局上使用三列布局。
在iOS 13
之前,UIKit附带了一个布局类:UICollectionViewFlowLayout
。 任何超出系统内置的布局能力的布局都需要构建自定义布局类,通常需要数百行代码。
现在,Apple
通过UICollectionViewCompositionalLayout
引入了Groups的概念。 这样可以用最少的代码实现更复杂的布局。
考虑内置的Photos
应用中的上述布局。 乍一看,这看起来像一个三列布局。 但是,在仔细检查时,请注意大图像如何从三列布局中获取四张照片的空间。 显然,这里发生了更复杂的事情,而答案是Groups!
Groups位于Items
和Sections
之间,并允许您在Sections
中应用不同的布局。
Building Your First Layout
好的,理论就足够了。 是时候建立布局了!
打开AlbumDetailViewController.swift
并导航到generateLayout()
。 此方法负责生成UICollectionViewCompositionalLayout
的实例,您的集合视图用于布置其项目。
这是入门项目中的方法:
func generateLayout() -> UICollectionViewLayout {
//1
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let fullPhotoItem = NSCollectionLayoutItem(layoutSize: itemSize)
//2
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(2/3))
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subitem: fullPhotoItem,
count: 1)
//3
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
这是上面的代码中发生的事情:
- 1)
fullPhotoItem
是一个NSCollectionLayoutItem
,其宽度和高度的分数为1
,这意味着它将填充包含它的组。 - 2) 接下来,创建一个
NSCollectionLayoutGroup
,其分数宽度为1,但高度为宽度的2/3(照片的标准长宽比)。 该组包含一个水平item
。 - 3) 布局的最后一部分是
NSCollectionLayoutSection
,在这种情况下,它包含在上一行中创建的单个group
。
这与您在应用程序中看到的布局相匹配。 每张照片均为全宽度,高度为宽度的2/3。
但是,如果您想要两列布局怎么办? 替换下面的组Group
布局代码,更新Group
大小的高度和布局组中的项目items
数:
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(1/3))
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subitem: fullPhotoItem,
count: 2
)
现在,您已将组group
设置为其原始高度的一半,但使它包含两个项目。 构建并运行项目,并查看布局如何更改。 容易吧?
这看起来已经开始好起来了,但是如果每张照片都有一个可以自己调用的空间会更好吗? UICollectionViewCompositionalLayout
支持使用content insets
来实现这一点。 创建fullPhotoItem
之后,将以下内容添加到generateLayout()
中:
fullPhotoItem.contentInsets = NSDirectionalEdgeInsets(
top: 2,
leading: 2,
bottom: 2,
trailing: 2)
构建并运行您的应用程序。
The Power of Groups
关于现在,您可能正在考虑:Columns and insets
都可以做到,但是使用UICollectionViewFlowLayout
已经很容易了。 给我展示一些很棒的东西! 不用担心,UICollectionViewCompositionalLayout
可以帮到您。
Photos
应用程序中相册详细信息视图中最奇妙的部分之一是不同大小的照片的效果。 一旦意识到可以将组嵌套在其他组中,构建此布局非常容易。 这就是您要的效果。
乍一看,这看起来非常复杂,但是您可以将其分解为四个不同的布局,其中两个只是彼此的镜像示例:
- 1) 全宽度照片。
- 2) 一张“主”照片,其中包含一对垂直堆叠的较小照片。
- 3) 一行连续三张较小的照片。
- 4) 第二种风格的相反。
用以下将在generateLayout()
中的let groupSize = ...
之前的所有内容替换为:
// We have three row styles
// Style 1: 'Full'
// A full width photo
// Style 2: 'Main with pair'
// A 2/3 width photo with two 1/3 width photos stacked vertically
// Style 3: 'Triplet'
// Three 1/3 width photos stacked horizontally
// First type. Full
let fullPhotoItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(2/3)))
fullPhotoItem.contentInsets = NSDirectionalEdgeInsets(
top: 2,
leading: 2,
bottom: 2,
trailing: 2)
第一种布局类型很简单:单个图像即屏幕的整个宽度。 通过创建宽度为1.0(全宽)且高度为宽度的2/3的单个项目来实现此目的。
在此下面,添加第二种布局类型:
// Second type: Main with pair
// 3
let mainItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(2/3),
heightDimension: .fractionalHeight(1.0)))
mainItem.contentInsets = NSDirectionalEdgeInsets(
top: 2,
leading: 2,
bottom: 2,
trailing: 2)
// 2
let pairItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.5)))
pairItem.contentInsets = NSDirectionalEdgeInsets(
top: 2,
leading: 2,
bottom: 2,
trailing: 2)
let trailingGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1/3),
heightDimension: .fractionalHeight(1.0)),
subitem: pairItem,
count: 2)
// 1
let mainWithPairGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(4/9)),
subitems: [mainItem, trailingGroup])
第二组由一个mainItem
和一对较小的项组成。较小的项目本身是垂直放置的组,包含在由主要项目组成的水平放置的组和包含一对较小项目的组中。
这里的数学有些棘手。您必须记住,每个尺寸都是相对于其父级的。您可能会发现从外层开始并向后进行工作比较容易-代码底部到顶部。
- 1) 外部组
mainWithPairGroup
应该为全宽度,因此其分数宽度为1.0
。主要项目的高度决定了其高度。这是屏幕宽度的2/3
,因此高度需要为第一个布局中全宽照片高度的2/3。如果您还记得高中数学,就会知道2/3
中的2/3
是4/9
! - 2) 然后,包含两个垂直堆叠的较小项目的
trailing
组应为其包含组的宽度和全高的1/3。每个较小的项目应为trailing
组的全宽及其高度的一半。 - 3) 最后,主要项目的宽度应为其包含组的宽度的
2/3
。
接下来,将三元triplet
组添加到第二组下面:
// Third type. Triplet
let tripletItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1/3),
heightDimension: .fractionalHeight(1.0)))
tripletItem.contentInsets = NSDirectionalEdgeInsets(
top: 2,
leading: 2,
bottom: 2,
trailing: 2)
let tripletGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(2/9)),
subitems: [tripletItem, tripletItem, tripletItem])
第三组包含三张水平放置的照片。 这意味着该组应具有完整的分数宽度,但高度应为全尺寸照片的1/3
或2/9
。 在组内,每个项目应为全高和宽度的1/3。
现在,添加第四个也是最后一个样式:
// Fourth type. Reversed main with pair
let mainWithPairReversedGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(4/9)),
subitems: [trailingGroup, mainItem])
鉴于第四个布局与第二个布局相反,您可以通过更改组中子项目的顺序轻松实现此目的。
要完成新的布局,请使用以下内容替换groupSize
,group
和section
定义:
let nestedGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(16/9)),
subitems: [
fullPhotoItem,
mainWithPairGroup,
tripletGroup,
mainWithPairReversedGroup
]
)
let section = NSCollectionLayoutSection(group: nestedGroup)
在这里,您将四种“组”类型添加到垂直堆叠项目的容器Group
中。 它们应该占据整个宽度,并且如果您遍历数字,则其高度应等于正常高度的1和7/9或16/9。
很多布局。 但是,当您分解每个部分时,它们都很简单,并且您无需为此布局构建任何复杂的逻辑即可支持多种设备或方向。 继续,构建并运行项目。 旋转一下并尝试一下。 也尝试旋转设备!
Adding Supplementary Items
现代应用程序的一个常见功能是在Item or Section
级别的集合视图中向项目添加其他context
。 例如,在您的主屏幕上代表一个应用的项目可能在右上角带有一个红色的小徽章,显示未读通知的数量。 或者,Contacts
应用程序中的某个部分可能具有标题,指示该部分正在显示哪个字母。
UICollectionViewCompositionalLayout
为这些类型的附加项目提供了Supplementary Items
API。 现在,您将使用它在照片上添加badge
,以表明它正在与云存储系统同步。
在AlbumDetailViewController.swift
中,导航到configureCollectionView()
并在注册照片项单元格后添加以下内容:
collectionView.register(
SyncingBadgeView.self,
forSupplementaryViewOfKind: AlbumDetailViewController.syncingBadgeKind,
withReuseIdentifier: SyncingBadgeView.reuseIdentifier)
这段代码告诉collection view
,当请求添加具有相关重用标识符的某种supplementary
视图时,它应该使用SyncingBadgeView
类。 您以前可能曾经遇到过重用标识符,但是supplementary view kind
可能对您来说是新的。 就像重用标识符一样,这只是一个字符串,用作告诉UIKit
您要添加哪种视图类型的键。
如果您有兴趣签出同步badge view
的实现,请在SharedViews
组中打开SyncingBadgeView.swift
。 这只是一个显示图像的简单UICollectionReusableView
子类。
回到AlbumDetailViewController.swift
。 接下来,您需要使用SupplementaryViewProvider
配置数据源。 这是一个简单的方法,当传递collection view
,kind and index path
时,它返回一个可选视图。 在创建数据源之后,将以下内容直接添加到configureDataSource()
:
dataSource.supplementaryViewProvider = {
(
collectionView: UICollectionView,
kind: String,
indexPath: IndexPath)
-> UICollectionReusableView? in
// 1
if let badgeView = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: SyncingBadgeView.reuseIdentifier,
for: indexPath) as? SyncingBadgeView {
// 2
let hasSyncBadge = indexPath.row % Int.random(in: 1...6) == 0
badgeView.isHidden = !hasSyncBadge
return badgeView
} else {
fatalError("Cannot create new supplementary")
}
}
这段代码:
- 1) 要求
collection view
出队正确的supplementary view
。 - 2) 确定照片是否正在同步(在这种情况下是随机的),如果不想显示,则隐藏徽章。
现在,您需要添加另一部分来显示同步视图。 您需要告诉collection view
在哪里显示它!
在generateLayout()
的顶部,添加以下代码:
// Syncing badge
let syncingBadgeAnchor = NSCollectionLayoutAnchor(
edges: [.top, .trailing],
fractionalOffset: CGPoint(x: -0.3, y: 0.3))
let syncingBadge = NSCollectionLayoutSupplementaryItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .absolute(20),
heightDimension: .absolute(20)),
elementKind: AlbumDetailViewController.syncingBadgeKind,
containerAnchor: syncingBadgeAnchor)
该代码将同步视图的布局定义为从顶部和trailing
锚定30%
,绝对宽度和高度为20点。
最后,您需要将此布局添加到要显示同步标志的任何item
。 在我们的情况下,就是代表照片的任何项目。 NSCollectionLayoutItem
有一个方便的初始化程序,可以接受一系列supplementary items
。 更新fullPhotoItem
以利用此初始化程序,如下所示:
let fullPhotoItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(2/3)),
supplementaryItems: [syncingBadge])
构建并运行项目。 请记住,supplementary
同步项目是随机可见的,因此您可能不会每次都看到它!
Browsing Albums
album
详细信息视图开始看起来不错。 当然,大多数人都有一个以上的相册,因此,在下一部分中,您将向该应用添加相册查看视图。
打开AppDelegate.swift
并替换获取捆绑包的四行,并使用以下代码设置初始视图控制器:
guard let bundleURL = Bundle.main.url(
forResource: "PhotoData",
withExtension: "bundle") else {
return false
}
let initialViewController =
AlbumsViewController(withAlbumsFromDirectory: bundleURL)
这会将AlbumsViewController
设置为初始视图控制器。 打开AlbumsViewController.swift
。 从结构上讲,该类与您已经修改的AlbumDetailViewController
类非常相似。
主要区别是该类显示三个部分,而不只是一个部分。 snapshotForCurrentState()
处理用于添加每个部分和相关项目的逻辑。 构建并运行项目。
初始视图控制器显示了六个相册的列表,每个相册代表PhotoData
捆绑包中的一个文件夹。 一些专辑会显示在多个部分中。 轻按缩略图即可打开专辑详细信息视图。
按照目前的情况,无法分辨每个部分代表什么。 要解决此问题,您将在每个部分添加header
。 在configureCollectionView()
中,通过在注册AlbumItemCell
之后添加以下代码来注册HeaderView
类:
collectionView.register(
HeaderView.self,
forSupplementaryViewOfKind: AlbumsViewController.sectionHeaderElementKind,
withReuseIdentifier: HeaderView.reuseIdentifier)
和以前一样,在补充同步视图中,您需要告诉集合视图的数据源如何生成header
。 初始化后添加以下内容到configureDataSource()
:
dataSource.supplementaryViewProvider = { (
collectionView: UICollectionView,
kind: String,
indexPath: IndexPath)
-> UICollectionReusableView? in
guard let supplementaryView = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: HeaderView.reuseIdentifier,
for: indexPath) as? HeaderView else {
fatalError("Cannot create header view")
}
supplementaryView.label.text = Section.allCases[indexPath.section].rawValue
return supplementaryView
}
和以前一样,您将supplementary view provider
设置为闭包,该闭包在给定collection view, a kind and an index path
的情况下返回supplementary view
。 在这种情况下,它将返回HeaderView
-入门项目中的另一个简单视图。
最后,将generateMyAlbumsLayout()
中的section
初始化代码替换为:
let headerSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerSize,
elementKind: AlbumsViewController.sectionHeaderElementKind,
alignment: .top)
let section = NSCollectionLayoutSection(group: group)
section.boundarySupplementaryItems = [sectionHeader]
此代码为您的标题设置了全宽度的布局大小,估计为44点高。 您使用估计的高度,因为动态类型设置意味着用户看到的标题文本可能比您预期的大。
接下来,代码定义一个section header
,最后将其设置为该节的supplementary item
。 构建并运行项目,以查看添加到每个部分顶部的header
:
More Complex Layouts
让您回想起本教程的开始,您会记得相册浏览视图的每个部分使用不同的布局。 是时候添加了!
在注册AlbumItemCell
之后,立即在configureCollectionView()
中注册两个新的单元格类开始。
collectionView.register(
FeaturedAlbumItemCell.self,
forCellWithReuseIdentifier: FeaturedAlbumItemCell.reuseIdentifer)
collectionView.register(
SharedAlbumItemCell.self,
forCellWithReuseIdentifier: SharedAlbumItemCell.reuseIdentifer)
用以下代码替换configureDataSource()
中数据源尾随闭包的内容:
let sectionType = Section.allCases[indexPath.section]
switch sectionType {
case .featuredAlbums:
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: FeaturedAlbumItemCell.reuseIdentifer,
for: indexPath) as? FeaturedAlbumItemCell
else { fatalError("Could not create new cell") }
cell.featuredPhotoURL = albumItem.imageItems[0].thumbnailURL
cell.title = albumItem.albumTitle
cell.totalNumberOfImages = albumItem.imageItems.count
return cell
case .sharedAlbums:
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: SharedAlbumItemCell.reuseIdentifer,
for: indexPath) as? SharedAlbumItemCell
else { fatalError("Could not create new cell") }
cell.featuredPhotoURL = albumItem.imageItems[0].thumbnailURL
cell.title = albumItem.albumTitle
return cell
case .myAlbums:
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: AlbumItemCell.reuseIdentifer,
for: indexPath) as? AlbumItemCell
else { fatalError("Could not create new cell") }
cell.featuredPhotoURL = albumItem.imageItems[0].thumbnailURL
cell.title = albumItem.albumTitle
return cell
}
这看起来像很多代码,但实际上,这非常简单。 switch
语句独立对待每个节section
,在出队和配置它之后返回适当的单元格。
接下来,在generateMyAlbumsLayout()
下添加一种方法来为特色专辑生成第一部分布局:
func generateFeaturedAlbumsLayout(isWide: Bool) -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(2/3))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// Show one item plus peek on narrow screens,
// two items plus peek on wider screens
let groupFractionalWidth = isWide ? 0.475 : 0.95
let groupFractionalHeight: Float = isWide ? 1/3 : 2/3
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(CGFloat(groupFractionalWidth)),
heightDimension: .fractionalWidth(CGFloat(groupFractionalHeight)))
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subitem: item,
count: 1)
group.contentInsets = NSDirectionalEdgeInsets(
top: 5,
leading: 5,
bottom: 5,
trailing: 5)
let headerSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerSize,
elementKind: AlbumsViewController.sectionHeaderElementKind,
alignment: .top)
let section = NSCollectionLayoutSection(group: group)
section.boundarySupplementaryItems = [sectionHeader]
section.orthogonalScrollingBehavior = .groupPaging
return section
}
该方法的前半部分可能看起来很熟悉。 该代码设置了一个item
和一个group
,并具有附加功能,可根据传递给方法的isWide
参数更改组的宽度。 然后添加一个标题。
该方法最有趣的部分就在最后。 集合视图具有确定其滚动方向的主轴。 在我们的情况下,垂直。 正交轴则成直角-在这种情况下为水平。 您可以通过在正交轴上滚动行为来配置此部分布局,该行为在各组之间进行浏览。 在配置第三部分也是最后一部分之后,将在后面进行详细介绍。
添加以下方法来生成共享相册的布局:
func generateSharedlbumsLayout() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .absolute(140),
heightDimension: .absolute(186))
let group = NSCollectionLayoutGroup.vertical(
layoutSize: groupSize,
subitem: item,
count: 1)
group.contentInsets = NSDirectionalEdgeInsets(
top: 5,
leading: 5,
bottom: 5,
trailing: 5)
let headerSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerSize,
elementKind: AlbumsViewController.sectionHeaderElementKind,
alignment: .top)
let section = NSCollectionLayoutSection(group: group)
section.boundarySupplementaryItems = [sectionHeader]
section.orthogonalScrollingBehavior = .groupPaging
return section
}
共享相册的布局相当简单,展现出与精选相册相同的分页行为,但项目尺寸较小。
最后,替换generateLayout()
以调用刚刚添加的两个新方法:
func generateLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int,
layoutEnvironment: NSCollectionLayoutEnvironment)
-> NSCollectionLayoutSection? in
let isWideView = layoutEnvironment.container.effectiveContentSize.width > 500
let sectionLayoutKind = Section.allCases[sectionIndex]
switch (sectionLayoutKind) {
case .featuredAlbums: return self.generateFeaturedAlbumsLayout(
isWide: isWideView)
case .sharedAlbums: return self.generateSharedlbumsLayout()
case .myAlbums: return self.generateMyAlbumsLayout(isWide: isWideView)
}
}
return layout
}
请注意,使用layoutEnvironment
变量可根据内容的宽度修改布局。
构建并运行项目。
旋转设备以查看布局如何随着不同的宽度而变化:
还记得添加到精选和共享相册中的页面调度正交滚动行为吗? 尝试从左向右滚动这些部分。
在UICollectionViewCompositionalLayout
之前,要实现这种功能,需要在主视图中添加另一个collection view
,并处理各种复杂的交互逻辑。 现在,只需一行代码!
希望您喜欢iOS 13上提供的新的compositional layout API
。您可以在Apple官方文档official Apple documentation以及2019年WWDC的精彩演讲excellent session中更深入地了解它们。
后记
本篇主要讲述了基于UICollectionViewCompositionalLayout API的UICollectionViews布局,感兴趣的给个赞或者关注~~~