UIKit框架(五十一) —— 基于iOS14的UICollectionView List的创建(一)

版本记录

版本号 时间
V1.0 2021.01.25 星期一

前言

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的简单实用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(二)
32. UIKit框架(三十二) —— 替换Peek and Pop交互的基于iOS13的Context Menus(一)
33. UIKit框架(三十三) —— 替换Peek and Pop交互的基于iOS13的Context Menus(二)
34. UIKit框架(三十四) —— Accessibility的使用(一)
35. UIKit框架(三十五) —— Accessibility的使用(二)
36. UIKit框架(三十六) —— UICollectionView UICollectionViewDiffableDataSource的使用(一)
37. UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)
38. UIKit框架(三十八) —— 基于CollectionView转盘效果的实现(一)
39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)
40. UIKit框架(四十) —— iOS 13中UISearchController 和 UISearchBar的新更改(二)
41. UIKit框架(四十一) —— 使用协议构建自定义Collection(一)
42. UIKit框架(四十二) —— 使用协议构建自定义Collection(二)
43. UIKit框架(四十三) —— CALayer的简单实用示例(一)
44. UIKit框架(四十四) —— CALayer的简单实用示例(二)
45. UIKit框架(四十五) —— 支持DarkMode的简单示例(一)
46. UIKit框架(四十六) —— 支持DarkMode的简单示例(二)
47. UIKit框架(四十七) —— 自定义Calendar Control的简单示例(一)
48. UIKit框架(四十八) —— 自定义Calendar Control的简单示例(二)
49. UIKit框架(四十九) —— UIVisualEffectView原理和简单使用(一)
50. UIKit框架(五十) —— UIVisualEffectView原理和简单使用(二)

开始

首先看下主要内容:

在本教程中,您将学习如何在单个collection view中创建列表,使用现代单元配置(cell configuration)以及配置多个section snapshot。内容来自翻译。

接着看下写作环境:

Swift 5, iOS 14, Xcode 12

下面就一起来看正文啦。

在iOS 14中,Apple为UICollectionView引入了新特性。Lists使您可以在UICollectionView中包括类似UITableView的部分。现代单元配置Modern Cell Configuration使注册和配置collection view cell更加容易。并且,Section Snapshots允许UICollectionView中包含多个section,其中每个section可以具有不同的布局。

在本教程中,您将学习如何:

  • 使用UICollectionLayoutListConfiguration创建一个可扩展列表list
  • 使用Modern Cell Configuration来配置UICollectionView cell
  • 使用Section Snapshots可将多个节(section)添加到UICollectionView

注意:本教程假定您熟悉Apple在iOS 13中引入的UICollectionViewDiffableDataSourceUICollectionViewCompositionalLayout。如果您以前从未使用过它们,请查看Collection View and Diffable Data Source and Modern Collection Views with Compositional Layouts。

事不宜迟,该开始了!

Xcode中打开启动项目。构建并运行。

您会看到一个空的Pet Explorer屏幕。 它是Get a Pet应用程序的一部分,该应用程序显示可供收养的宠物。 您将基于此应用程序构建。 在最终版本中,您可以浏览宠物类别并选择一个宠物以查看其详细信息。 然后,当您找到喜欢的宠物时,可以点击Adopt以领养该宠物。

完整的应用程序的Pet Explorer屏幕显示可用和已收养的宠物:

注意可爱的狗Diego。完成本教程后,您将成为该虚拟小狗的骄傲拥有者。

打开Xcode。浏览项目。当应用启动时,它使用PetExplorerViewController作为根视图控制器,将导航控制器设置为初始视图控制器。打开Main.storyboard选中该设置。

打开PetExplorerViewController.swift以浏览此文件。 PetExplorerViewControllercollectionView为空。稍后,您将使用代表宠物和宠物类别的列表项来填充它。

Pet.swift具有与宠物有关的所有数据。

DataSource类型别名是为了方便起见。稍后在配置UICollectionView数据源时将使用它。

枚举Section代表.availablePets.adoptedPetsUICollectionView部分。

最后,在PetExplorerViewController扩展程序中,您会找到pushDetailForPet(_:withAdoptionStatus :)。当用户选择一个项目时,此方法将显示PetDetailViewController

打开PetDetailViewController.swift。这是一个简单的视图控制器类,可显示宠物的图像,姓名和出生年月。

现在,您已经探索了应用程序的结构,接下来是时候了解UICollectionView列表了。


What is a List?

listUICollectionView中的table view类似的视图。您可以通过仅在少量代码下将可配置的UICollectionViewCompositionalLayout应用于UICollectionView的一section来创建列表。

您可以配置列表以显示分层数据,并可以折叠和展开列表项或使其看起来类似于传统的table view。如果您需要在应用程序中使用table view,则可以使用带有UICollectionView API的列表,也可以使用传统的UITableView

在大多数情况下,列表list更易于创建和配置。

现在该创建您的第一个列表list了。


Creating a List

您将创建一个显示宠物类别的平面列表。这将是您不使用UITableView的第一个table view。对于平面列表,与UITableView相比,UICollectionView list的优势可能不会立即显现。稍后,当您使列表可扩展时,您会发现使用UICollectionView list的真正好处。

注意:UICollectionView体系结构在布局,表示形式和数据之间有清晰的分隔。本教程的示例代码遵循这种模式。每次向Get a Pet添加新功能时,您都会添加一段代码,首先用于布局,然后用于演示,最后用于数据。

1. Configuring the Layout

iOS 13中,Apple引入了UICollectionViewCompositionalLayout,这是一种用于构建复杂布局的新API。 在iOS 14中,Apple添加了:

static func list(using configuration: UICollectionLayoutListConfiguration) -> 
  UICollectionViewCompositionalLayout

这使您可以在一行代码中创建list layout,而无需了解UICollectionViewCompositionalLayout API的详细知识。 您可以使用UICollectionLayoutListConfiguration配置列表的外观,颜色,分隔符,页眉和页脚。

现在该将其应用于您的代码了:

打开PetExplorerViewController.swift。 在带有// MARK: - Functions的行下方添加以下方法:

func configureLayout() {
  // 1
  let configuration = UICollectionLayoutListConfiguration(appearance: .grouped)
  // 2
  collectionView.collectionViewLayout =
    UICollectionViewCompositionalLayout.list(using: configuration)
}

这将配置collectionView的布局。 在这里,您:

  • 1) 创建具有.grouped外观的configuration。 这为您提供了一个看起来像table view的布局配置。
  • 2) 接下来,您将创建一个具有list sectionUICollectionViewCompositionalLayout,该节section将使用该configuration。 您需要将此布局应用于collectionView

如您所见,整个布局配置只有两行代码。

通过添加以下内容在viewDidLoad()的末尾调用此方法:

configureLayout()

2. Configuring the Presentation

现在是时候为列表list创建一个collection view cell了。 单元格显示宠物类别。 您将了解注册单元格的新方法。

在第一个PetExplorerViewController扩展块内,添加:

// 1
func categoryCellregistration() ->
  UICollectionView.CellRegistration {
  // 2
    return .init { cell, _, item in
      // 3
      var configuration = cell.defaultContentConfiguration()
      configuration.text = item.title
      cell.contentConfiguration = configuration
  }
}

这是您首次接触modern cell registration and configuration。 代码是这样的:

  • 1) categoryCellregistration()UICollectionViewListCell类型的单元格和Item类型的数据项创建单元格注册。 这是注册collection view cell的现代方法。
  • 2) 您创建单元格注册,并传入一个闭包来配置单元格。 当cell需要渲染时调用闭包。
  • 3) 然后配置cell。 宠物类别在item.title中可用。 如果您不了解正在发生的事情,请不要担心。 本教程的整个章节都涉及现代单元配置(modern cell configuration)

在配置数据源时,您将调用categoryCellregistration()

3. Configuring the Data

您已为collection view配置了布局和单元格。 现在,您需要一种机制,可以基于collection view的基础数据创建这些单元格。 这就是数据源的来源。

将以下方法添加到PetExplorerViewController中:

func makeDataSource() -> DataSource {
  // 1
  return DataSource(collectionView: collectionView) {
    collectionView, indexPath, item -> UICollectionViewCell? in
    // 2
    return collectionView.dequeueConfiguredReusableCell(
      using: self.categoryCellregistration(), for: indexPath, item: item)
  }
}

这是您所做的:

  • 1) 您创建并返回一个DataSource,传入collectionView和一个闭包,该闭包将UICollectionViewCell提供给数据源。
  • 2) 在闭包内部,您要求collectionView使UICollectionViewCelldequeue。 然后,您将单元格注册作为参数传递,因此collectionView将知道其必须出队的单元格类型。 您刚才创建的categoryCellregistration()包含单元配置的逻辑。

将以下属性添加到PetExplorerViewController

lazy var dataSource = makeDataSource()

由于您在声明中使用了lazy,因此在第一次需要collectionView时会为其创建数据源。

您配置了collectionView的布局,展示和数据。 现在,您将使用数据项填充collectionView

仍在PetExplorerViewController.swift中,将以下方法添加到PetExplorerViewController中:

func applyInitialSnapshots() {
  // 1
  var categorySnapshot = NSDiffableDataSourceSnapshot()
  // 2
  let categories = Pet.Category.allCases.map { category in
    return Item(title: String(describing: category))
  }
  // 3
  categorySnapshot.appendSections([.availablePets])
  // 4
  categorySnapshot.appendItems(categories, toSection: .availablePets)
  // 5
  dataSource.apply(categorySnapshot, animatingDifferences: false)
}

此代码使用可扩散的数据源来更新list的内容。 Apple在iOS 13中引入了diffable data source。该代码尚无任何新的iOS 14功能。 当您使列表可扩展并将section snapshots添加到列表时,情况将会改变。

使用applyInitialSnapshots()可以:

  • 1) 创建一个categorySnapshot,其中包含宠物类别名称。
  • 2) 然后为每个类别category创建一个项目Item并将其添加到categories中。
  • 3) 将.availablePets附加到categorySnapshot
  • 4) 然后将类别categories中的项目附加到categorySnapshot.availablePets
  • 5) 将categorySnapshot应用于dataSource

您已经添加了一个部分section,并指明了属于该部分的所有元素。

现在,在viewDidLoad()的末尾添加对applyInitialSnapshots()的调用:

applyInitialSnapshots()

构建并运行

恭喜你! 这是您的第一个带有列表的UICollectionView

列表list支持与UITableView样式匹配的外观:.plain,.grouped.insetGrouped。 您创建的列表具有.grouped外观。

iOS 14具有用于将列表显示为边栏的新外观:.sidebar.sidebarPlain。 它们通常在拆分视图(split view)中用作主视图。

现在,您可以扩展列表。


Making the List Expandable

现在是时候将pets添加到类别中了。

在这里,您将发现UICollectionView列表的强大功能。 使用UITableView,您将不得不处理category cells and pet cell上的点击tap,保持单元格的可见状态和展开状态,并编写显示或隐藏宠物单元格的代码。

使用UICollectionView列表,您只需要提供类别和宠物的分层数据结构。 该列表将处理其余的内容。 您很快就会发现,仅需几行代码就可以实现很多目标。

Pet.swift包含所有宠物及其所属类别的数据。 无需更改布局中的任何内容,因此您将从展示开始。

1. Configuring the Presentation

之前,您为pet category创建了一个单元格。 您了解了注册单元格的新方法。 在这里,您将执行相同的操作,这次将为宠物创建一个cell。 单元格将显示宠物的名字。

PetExplorerViewController.swift中,添加:

func petCellRegistration() ->
  UICollectionView.CellRegistration {
    return .init { cell, _, item in
      guard let pet = item.pet else {
        return
      }
      var configuration = cell.defaultContentConfiguration()
      configuration.text = pet.name
      cell.contentConfiguration = configuration
  }
}

petCellRegistration()与您之前添加的categoryCellregistration()类似。 您创建一个单元注册并使用现代单元配置来配置该单元。 在这里,您使用defaultContentConfiguration(),然后将宠物名称分配为要显示的文本。

在配置数据源时,您将调用petCellRegistration()

接下来,您可以通过向类别单元格添加outline disclosure accessory来扩展列表。 这表明一个项目可以展开和折叠。 当您点击一个类别时,列表将展开并显示该类别的宠物。

categoryCellregistration()中,在cell.contentConfiguration = configuration下方,添加:

// 1
let options = UICellAccessory.OutlineDisclosureOptions(style: .header)
// 2
let disclosureAccessory = UICellAccessory.outlineDisclosure(options: options)
// 3
cell.accessories = [disclosureAccessory]

在这里,您:

  • 1) 创建要应用于disclosureAccessoryoptions。 您使用.header样式使单元格可扩展。
  • 2) 然后,使用配置的options创建一个disclosureAccessory
  • 3) 将accessory应用于cell。 单元cell可以具有多个附件,因此您可以在一个数组中添加discoveryAccessory

构建并运行。

outline disclosure是可见的,但是当您点击一个单元格时,什么也不会发生。 为什么? 您尚未将宠物添加到其类别中。 接下来,您将要执行此操作。

2. Configuring the Data

接下来,您将学习如何将分层数据添加到列表中。 完成后,您会看到列表自动支持折叠和展开单元格。

现在,调整数据源以将宠物细胞添加到其类别中。

makeDatasource()中,替换为:

return collectionView.dequeueConfiguredReusableCell(
  using: self.categoryCellregistration(), for: indexPath, item: item)

if item.pet != nil {
  // 1
  return collectionView.dequeueConfiguredReusableCell(
    using: self.petCellRegistration(), for: indexPath, item: item)
} else {
  // 2
  return collectionView.dequeueConfiguredReusableCell(
    using: self.categoryCellregistration(), for: indexPath, item: item)
}

一个item可以代表一个类别或一个宠物。 这取决于pet的值。 在此代码中,collectionViewdequeue

  • 1) 如果item.pet不为nil,则为petcell
  • 2) 如果item.petnil,则为categorycell

您已配置了展示宠物所需的一切,但尚未添加任何宠物。 为此,您必须更新数据的初始快照。

body替换为applyInitialSnapshots()

// 1
var categorySnapshot = NSDiffableDataSourceSectionSnapshot()
// 2
for category in Pet.Category.allCases {
  // 3
  let categoryItem = Item(title: String(describing: category))
  // 4
  categorySnapshot.append([categoryItem])
  // 5
  let petItems = category.pets.map { Item(pet: $0, title: $0.name) }
  // 6
  categorySnapshot.append(petItems, to: categoryItem)
}
// 7
dataSource.apply(
  categorySnapshot,
  to: .availablePets,
  animatingDifferences: false)

要在类别和宠物之间建立层次关系,请执行以下操作:

  • 1) 创建类型为NSDiffableDataSourceSectionSnapshotcategorySnapshot。 这是一个section snapshot。 使用section snapshot,可以用分层结构表示数据,例如带有可扩展项的轮廓。目前,这是有关section snapshot的全部知识。 在本教程的后面,您将了解有关section snapshot的更多信息。
  • 2) 然后,遍历Pet.Category.allCases中的类别。 在循环中,将宠物添加到其类别中。
  • 3) 创建一个categoryItem
  • 4) 将categoryItem追加到categorySnapshot
  • 5) 然后,创建一个数组petItems,其中包含属于当前类别category的所有宠物。
  • 6) 通过将petItems附加到当前categoryItem来创建类别和宠物之间的层次关系。
  • 7) 将categorySnapshot应用于dataSource.availablePets

构建并运行。

点击一个类别。列表将展开并显示宠物名称。很好!

现在是时候让cell看起来更好一点了。


What is Modern Cell Configuration?

如果您使用过UITableViewUICollectionView,则习惯于通过直接设置单元格的属性来对其进行配置。在iOS 14中,单元配置可以完全与单元本身分离。

您创建类型为UIContentConfigurationcell content configuration。然后,根据需要设置此内容配置的属性。同样,您可以创建类型为UIBackgroundConfigurationcell background configuration

结果是可重用的配置,您可以将其应用于您喜欢的任何cell

现在该看看它是如何工作的!


Configuring the Cells

您刚刚学习了modern cell configuration的理论。现在,您将通过添加代码来更新pet cell以显示宠物的图像和年龄,从而实施单元格内容配置。在下一部分中,您将应用cell background configuration

petCellRegistration()中,替换:

var configuration = cell.defaultContentConfiguration()
configuration.text = pet.name
cell.contentConfiguration = configuration

// 1
var configuration = cell.defaultContentConfiguration()
// 2
configuration.text = pet.name
configuration.secondaryText = "\(pet.age) years old"
configuration.image = UIImage(named: pet.imageName)
// 3
configuration.imageProperties.maximumSize = CGSize(width: 40, height: 40)
// 4
cell.contentConfiguration = configuration

在这里,您将看到实际的单元格内容配置。 您:

  • 1) 使用默认样式创建类型UIListContentConfiguration的配置。使用此配置,您将在一个单元格中设置图像,文本和辅助文本。
  • 2) 将宠物的数据应用于配置,包括宠物的图像。
  • 3) 设置图像的尺寸。
  • 4) 将configuration应用于单元的contentConfiguration

构建并运行。

突然,宠物看起来更可爱了。 您是否有动机领养个?


Adopting a Pet

最后,您领养一只宠物。 Diego正在等你接他!

首先,您将学习如何为单元创建和应用后台配置。 养有宠物的牢Cells将具有背景色。 您将使用iOS 14中引入的UIBackgroundConfiguration,它是现代单元配置的一部分。

入门项目已经具有存储被收养宠物的属性:adoptions

petCellRegistration()cell.contentConfiguration = configuration下,添加:

// 1
if self.adoptions.contains(pet) {
  // 2
  var backgroundConfig = UIBackgroundConfiguration.listPlainCell()
  // 3
  backgroundConfig.backgroundColor = .systemBlue
  backgroundConfig.cornerRadius = 5
  backgroundConfig.backgroundInsets = NSDirectionalEdgeInsets(
    top: 5, leading: 5, bottom: 5, trailing: 5)
  // 4
  cell.backgroundConfiguration = backgroundConfig
}

要为单元格提供彩色背景,请执行以下操作:

  • 1) 检查宠物是否被收养。 只有收养的宠物才会有彩色背景。
  • 2) 创建一个UIBackgroundConfiguration,为listPlainCell配置默认属性。 将其分配给backgroundConfig
  • 3) 接下来,根据您的喜好修改backgroundConfig
  • 4) 将backgroundConfig分配给cell.backgroundConfiguration

您尚无法测试。 您需要先养宠物。

入门项目有一个PetDetailViewController。 该视图控制器具有Adopt按钮。 但是,如何导航到PetDetailViewController

您向宠物cell添加一个disclosure indicator。 在petCellRegistration()cell.contentConfiguration = configuration下,添加:

cell.accessories = [.disclosureIndicator()]

在这里设置单元格的disclosure indicator

现在,当您点击宠物单元格时,您需要导航到Pet DetailViewController

将以下代码添加到collectionView(_:didSelectItemAt :)

// 1
guard let item = dataSource.itemIdentifier(for: indexPath) else {
  collectionView.deselectItem(at: indexPath, animated: true)
  return
}
// 2
guard let pet = item.pet else {
  return
}
// 3
pushDetailForPet(pet, withAdoptionStatus: adoptions.contains(pet))

当您点击pet cell时,将调用collectionView(_:didSelectItemAt :)。 在此代码中,您:

  • 1) 检查所选indexPath处的item是否存在。
  • 2) 安全解包pet
  • 3) 然后,将PetDetailViewController推入导航堆栈。 pushDetailForPet()是入门项目的一部分。

构建并运行。 寻找Diego,然后点按单元格。

这是你的朋友Diego! 点击Adopt按钮。

您已采用Diego,然后导航回Pet Explorer。 您会期望Diego的单元格具有蓝色背景,但背景并非如此。 发生了什么?

数据源尚未更新。 您现在就要做。

将以下方法添加到PetExplorerViewController中:

func updateDataSource(for pet: Pet) {
  // 1
  var snapshot = dataSource.snapshot()
  let items = snapshot.itemIdentifiers
  // 2
  let petItem = items.first { item in
    item.pet == pet
  }
  if let petItem = petItem {
    // 3
    snapshot.reloadItems([petItem])
    // 4
    dataSource.apply(snapshot, animatingDifferences: true, completion: nil)
  }
}

在此代码中,您:

  • 1) 从dataSource.snapshot()中检索所有items
  • 2) 查找代表petitem并将其分配给petItem
  • 3) 在snapshot中重新加载petItem
  • 4) 然后将更新的snapshot应用于dataSource

现在确保收养宠物时调用updateDataSource(for :)

petDetailViewController(_:didAdoptPet :)中,添加:

// 1
adoptions.insert(pet)
// 2
updateDataSource(for: pet)

用户收养宠物时,将调用此代码。 在这里:

  • 1) 将收养的pet插入adoptions
  • 2) 调用updateDataSource(for :)。 这是您刚创建的方法。

构建并运行。 点击Diego。 然后,在详细信息屏幕上,点击Adopt。 向后浏览后,您会看到以下屏幕。

Diego有蓝色背景。 他现在是你的。


What is a Section Snapshot?

section snapshotUICollectionView中单个section的数据封装起来。 这有两个重要的好处:

  • 1) Section snapshot使model hierarchical data成为可能。 在实施带有宠物类别的列表时,您已经应用了此功能。
  • 2) UICollectionView数据源每个section可以有一个snapshot,而不是整个collection view的单个快照。 这使您可以将多个sections添加到collection view中,其中每个部分可以具有不同的布局和行为。

您将为收养的宠物添加一个section,以了解其工作原理。


Adding a Section for Adopted Pets

您想在collectionView中的单独section中创建收养的宠物列表,其中包含您之前创建的宠物类别的可扩展列表下方。

1. Configuring the Layout

用以下命令替换configureLayout()body

// 1
let provider =
  {(_: Int, layoutEnv: NSCollectionLayoutEnvironment) ->
    NSCollectionLayoutSection? in
  // 2
  let configuration = UICollectionLayoutListConfiguration(
    appearance: .grouped)
  // 3
  return NSCollectionLayoutSection.list(
    using: configuration,
    layoutEnvironment: layoutEnv)
}
// 4
collectionView.collectionViewLayout =
  UICollectionViewCompositionalLayout(sectionProvider: provider)

这样可以按部分section配置collectionView的布局。 在此代码中,您:

  • 1) 创建一个返回NSCollectionLayoutSection的闭包。 现在您有多个sections,并且此闭包可以根据sectionIndex分别返回每个section的布局。 在这种情况下,各section的布局相同,因此您无需使用sectionIndex。您将闭包分配给providerlayoutEnv提供有关布局环境的信息。
  • 2) 为具有.grouped外观的列表创建配置。
  • 3) 返回具有给定configurationsectionNSCollectionLayoutSection.list
  • 4) 使用provider作为sectionProvider创建UICollectionViewCompositionalLayout。 您将布局分配给collectionView.collectionViewLayout

接下来,您将配置展示。

2. Configuring the Presentation

将以下方法添加到第一个PetExplorerViewController扩展块中:

func adoptedPetCellRegistration() 
  -> UICollectionView.CellRegistration {
  return .init { cell, _, item in
    guard let pet = item.pet else {
      return
    }
    var configuration = cell.defaultContentConfiguration()
    configuration.text = "Your pet: \(pet.name)"
    configuration.secondaryText = "\(pet.age) years old"
    configuration.image = UIImage(named: pet.imageName)
    configuration.imageProperties.maximumSize = CGSize(width: 40, height: 40)
    cell.contentConfiguration = configuration
    cell.accessories =  [.disclosureIndicator()]
  }
}

此代码将应用于.adoptedPets中的单元格。 它对您应该看起来很熟悉。 与您在Configuring the Cells中添加的petCellRegistration()类似。 现在,您将配置数据。

3. Configuring the Data

makeDatasource()中,替换:

return collectionView.dequeueConfiguredReusableCell(
  using: self.petCellRegistration(), for: indexPath, item: item)

// 1
guard let section = Section(rawValue: indexPath.section) else {
  return nil
}
switch section {
// 2
case .availablePets:
  return collectionView.dequeueConfiguredReusableCell(
    using: self.petCellRegistration(), for: indexPath, item: item)
// 3
case .adoptedPets:
  return collectionView.dequeueConfiguredReusableCell(
    using: self.adoptedPetCellRegistration(), for: indexPath, item: item)
}

使用此代码,可以使数据源返回的单元格依赖于该section。 在这里:

  • 1) 安全地解包section
  • 2) 返回.availablePetspetCellRegistration()
  • 3) 返回.adoptedPetsenabledPetCellRegistration()

是时候将这些sections添加到数据源中了。

applyInitialSnapshots()中,在方法的开头插入以下代码:

// 1
var snapshot = NSDiffableDataSourceSnapshot()
// 2
snapshot.appendSections(Section.allCases)
// 3
dataSource.apply(snapshot, animatingDifferences: false)

在此代码中,您:

  • 1) 创建一个新的snapshot
  • 2) 将所有sections追加到snapshot
  • 3) 将snapshot应用于dataSource

构建并运行。 领养Diego

Diego的背景是蓝色,所以您知道收养成功了。 但是您添加的section在哪里?

section在那里,但为空。 您已将Diego添加到了收养的宠物中,但尚未将他插入数据源中。 这就是您现在要做的。

petDetailViewController(_:didAdoptPet :)中,在options.insert(pet)的正下方,添加:

// 1
var adoptedPetsSnapshot = dataSource.snapshot(for: .adoptedPets)
// 2
let newItem = Item(pet: pet, title: pet.name)
// 3
adoptedPetsSnapshot.append([newItem])
// 4
dataSource.apply(
  adoptedPetsSnapshot,
  to: .adoptedPets,
  animatingDifferences: true,
  completion: nil)

使用此代码,您:

  • 1) 从数据源中检索.adoptedPetssnapshot。 您将其分配给adoptedPetsSnapshot
  • 2) 为收养的pet创建一个新Item,并将其分配给newItem
  • 3) 将newItem追加到adoptedPetsSnapshot
  • 4) 您将修改后的acceptedPetsSnapshot应用于dataSource.adoptedPets

构建并运行。

起作用了! Diego在收养宠物部分。

您已经了解了iOS 14UICollectionView的许多改进,其中包括:

  • 1) 使用UICollectionLayoutListConfiguration创建可扩展list
  • 2) 使用Modern Cell Configuration来配置UICollectionView单元。
  • 3) 使用Section Snapshots可将多个sections添加到UICollectionView

而您只接触了表面。 有关更多详细信息,请查看WWDC 2020中的Advances in UICollectionView。

后记

本篇主要讲述了基于iOS14UICollectionView List的创建,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(UIKit框架(五十一) —— 基于iOS14的UICollectionView List的创建(一))