服务器端迅速与蒸气 - 更新为蒸气4!

建立Web应用程序和API的最佳书籍,由蒸气创造者写。
为蒸气4完全更新 - 包括新章节。
自由开始阅读 - 今天!

首页 iOS.& Swift Tutorials

iOS.14教程:UICollectionView列表

在本教程中,您将学习如何创建列表,使用现代单元配置并在单个集合视图上配置多个部分快照。

4.6/5 5个评分

版本

  • Swift 5,iOS 14,Xcode 12

在 iOS 14, Apple introduced new features for UICollectionView. 清单 let you include UITableView-like sections in a UICollectionView. 现代电池配置 使注册和配置收集视图单元格更容易。和, 截面快照 allow for multiple sections in a UICollectionView, where each section can have a different layout.

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

  • 创建一个可扩展的 列表 使用 UICollectionLayoutListConfiguration.
  • 采用 现代电池配置 to configure UICollectionView cells.
  • 采用 截面快照 to add multiple sections to a UICollectionView.
笔记: This tutorial assumes you’re familiar with UICollectionViewDiffableDataSourceUICollectionViewCompositionalLayout, introduced by Apple in iOS 13. If you haven’t used these before, check out 集合视图和可从数据源现代收集视图与组成布局.

没有进一步的ADO,现在是时候开始了!

入门

使用该项目下载项目材料 下载材料 本教程顶部或底部的按钮。打开 初学项目 在Xcode中。建立和运行。

宠物探险家空屏幕

你会看到一个空的 宠物探险家 屏幕。这是 获得宠物,一个显示可用于采用的宠物的应用程序。您将建立在此应用的顶部。在最终版本中,您可以通过宠物类别浏览并选择宠物以查看其详细信息。然后,当你找到一个你喜欢的宠物时,你可以点击 采纳 to adopt the pet.

已完成的应用程序的宠物资源管理器屏幕显示可用和采用的宠物:

宠物探险家最终屏幕

注意可爱的狗迭戈。完成本教程时,您将成为此虚拟小狗的骄傲所有者。 :]

打开 Xcode.. Browse around the project. When the app starts, it sets a navigation controller as the initial view controller using a PetExplorerViewController as the root view controller. Open main.storyboard. 查看此设置。

打开 petexplorerviewcontroller.swift. to explore this file. PetExplorerViewController‘s collectionView is empty. Later, you’ll populate it with list items that represent pets and pet categories.

宠物 所有与宠物有关的数据。

The DataSource typealias is for convenience. You’ll use it later when configuring the UICollectionView data source.

The enum Section represents sections of the UICollectionView for .availablePets.adoptedPets.

Finally, in the PetExplorerViewController extension, you’ll find pushDetailForPet(_:withAdoptionStatus:). This method presents PetDetailViewController when the user selects an item.

打开 petdetailviewcontroller.swift.。这是一个简单的视图控制器类,显示宠物的图像,名称和诞生年。

Now that you’ve explored the app’s structure, it’s time to learn about UICollectionView lists next.

什么是列表?

A 列表 is a table view lookalike in a UICollectionView. You can create a list by applying a configurable UICollectionViewCompositionalLayout to a section of a UICollectionView while using only a small amount of code.

You can configure a list to display hierarchical data, with the possibility to collapse and expand list items or to look similar to a traditional table view. If you need a table view in your app you can either use a list with the UICollectionView API or use the traditional UITableView.

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

现在是时候创建第一个列表了。

创建列表

You’ll create a flat list that shows the pet categories. This will be your first table view without using UITableView. For a flat list, the advantages of UICollectionView 列表 over UITableView may not be immediately apparent. Later, when you’ll make the list expandable, you’ll discover the real benefits of using UICollectionView list.

笔记: The UICollectionView architecture has a clean separation between layout, presentation and data. The sample code for this tutorial follows this pattern. Every time you add a new feature to 获得宠物,您将首先添加一个代码块,然后为演示而添加,最后进行数据。

配置布局

With iOS 13, Apple introduced UICollectionViewCompositionalLayout, a new API for building complex layouts. In iOS 14, Apple has added:

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

This enables you to create a list layout in one line of code, without the need for detailed knowledge of the UICollectionViewCompositionalLayout API. You can configure the appearance, colors, separators, headers and footers of the list with UICollectionLayoutListConfiguration.

是时候将其应用于您的代码了:

打开 petexplorerviewcontroller.swift.. Add the following method below the line with // MARK: - Functions:

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

This configures the layout of the collectionView. Here, you:

  1. Create a configuration with .grouped appearance. This gives you a layout configuration that looks like a table view.
  2. Next, you create a UICollectionViewCompositionalLayout with list sections, that uses the configuration. You’ll apply this layout to the collectionView.

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

Call this method at the end of viewDidLoad() by adding:

configureLayout()

配置演示文稿

现在是时候为列表创建一个集合视图单元格了。该单元格显示宠物类别。您将了解注册单元格的新方法。

在side the first PetExplorerViewController extension block, add:

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

这是您第一次与现代电池注册和配置遇到的遇到。这是代码的所作所为:

  1. categoryCellregistration() creates a cell registration for a cell of type UICollectionViewListCell 和 a data item of type Item. This is the modern way of registering collection view cells.
  2. 您可以创建单元格注册,传递关闭以配置单元格。当单元格需要呈现时,将打电话。
  3. Then you configure the cell. The pet category is available in item.title. Don’t worry if you don’t understand what’s going on yet. This tutorial has an entire section about modern cell configuration.

You’ll call categoryCellregistration() when configuring the data source.

配置数据

您已配置布局和单元格的集合视图。现在,您需要一种基于收集视图的基础数据创建这些单元格的机制。这就是数据源进入的地方。

Add the following method to 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. 你创建一个nd return a DataSource, passing in collectionView 和 a closure that provides a UICollectionViewCell to the data source.
  2. 在side the closure, you ask collectionView to dequeue a UICollectionViewCell. Then you pass the cell registration as a parameter, so collectionView will know which cell type it has to dequeue. categoryCellregistration(), which you created a moment ago, contains the logic for the cell configuration.

Add the following property to PetExplorerViewController:

lazy var dataSource = makeDataSource()

This creates the data source for collectionView when it’s first needed because you used lazy in the declaration.

You configured collectionView‘s layout, presentation and data. Now you’ll populate collectionView with data items.

还在 petexplorerviewcontroller.swift., add the following method to PetExplorerViewController:

func applyInitialSnapshots() {
  // 1
  var categorySnapshot = NSDiffableDataSourceSnapshot<Section, Item>()
  // 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)
}

此代码使用可扩展的数据源来更新列表的内容。 Apple在iOS 13中引入可扩展的数据源。代码还没有任何新的iOS 14功能。当您将列表可扩展并添加到列表时,将更改为列表。

With applyInitialSnapshots() you:

  1. Create a categorySnapshot that holds the pet category names.
  2. Then create an Item for each category 和 add it to categories.
  3. Append .availablePets to categorySnapshot.
  4. Then append the items in categories to .availablePets of categorySnapshot.
  5. Apply categorySnapshot to dataSource.

您已添加一个部分并指出属于该部分的所有元素。

Now, add a call to applyInitialSnapshots() at the end of viewDidLoad():

applyInitialSnapshots()

建立和运行。

宠物探险家群体外观

Congratulations! Here’s your first UICollectionView with a list.

A list supports appearances that match the styles of a UITableView: .plain, .grouped.insetGrouped. The list you created has the .grouped appearance.

iOS.14 has new appearances for presenting list as sidebars: .sidebar.sidebarPlain. They’re typically used as the primary view in a split view.

现在,您将使列表扩展。

使列表可扩展

现在是时候为类别添加宠物了。

Here, you’ll discover the powerful benefits of UICollectionView lists. With a UITableView, you would have to handle taps on category cells and pet cells, maintain the visible and expanded state of cells and write the code that shows or hides the pet cells.

With a UICollectionView list, you only have to provide a hierarchical data structure of categories and pets. The list will take care of the rest. You’ll soon discover how much you can achieve with only a few lines of code.

宠物 拥有所有宠物的数据和它们所属的类别。无需在布局中更改任何内容,因此您将从演示中开始。

配置演示文稿

早些时候,您为宠物类别创建了一个单元格。您了解了注册单元格的新方法。在这里,您将执行此操作,这次为宠物创建一个单元格。细胞将显示宠物的名称。

petexplorerviewcontroller.swift., 添加:

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

petCellRegistration() is similar to categoryCellregistration() you added earlier. You create a cell registration and use modern cell configuration to configure the cell. Here you use defaultContentConfiguration() 和 then assign the pet name as the text to display.

You’ll call petCellRegistration() when configuring the data source.

接下来,您将通过添加一个列表可扩展 概述披露配件 到类别单元格。这表明物品可以扩展和崩溃。点击类别时,列表会展开并显示该类别的宠物。

categoryCellregistration() 和 right below cell.contentConfiguration = configuration, 添加:

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

在这里,你:

  1. Create options you want to apply to disclosureAccessory. You use .header style to make the cell expandable.
  2. Then, create a disclosureAccessory with the configured options.
  3. Apply the accessory to the cell. Cells can have more than one accessory, so you add disclosureAccessory in an array.

建立和运行。

宠物探险家披露

概述泄露是可见的,但是当您点击单元格时,没有任何反应。为什么?您还没有将宠物添加到他们的类别中。你会这样做。

配置数据

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

现在,调整数据源以将PET单元添加到其类别。

makeDatasource(), replace:

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)
}

An item can either represent a category or a pet. This depends on the value of pet. In this code, the collectionView will dequeue:

  1. A cell for a pet if item.pet is not nil.
  2. A cell for a category if item.pet is nil.

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

Replace the body of applyInitialSnapshots() with:

// 1
var categorySnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
// 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. Create a categorySnapshot of type NSDiffableDataSourceSectionSnapshot. This is a 章节快照。使用截面快照可以表示具有分层结构的数据,例如具有可扩展项目的轮廓。
    目前,这就是您需要了解的一切快照。您将在本教程中稍后的介绍快照了解更多信息。
  2. Then, loop over the categories in Pet.Category.allCases. Within the loop, you add the pets to their categories.
  3. Create a categoryItem.
  4. Append the categoryItem to the categorySnapshot.
  5. Then, create an array, petItems, containing all pets that belong to the current category.
  6. Create the hierarchical relationship between categories and pets by appending the petItems to the current categoryItem.
  7. Apply categorySnapshot to .availablePets of dataSource.

建立和运行。

宠物探险家披露扩展

点按类别。列表扩展并显示宠物名称。伟大的!

现在是时候让细胞看起来更好。

什么是现代细胞配置?

If you’ve used a UITableView or UICollectionView, you’re used to configuring your cells by directly setting their properties. In iOS 14, cell configuration can be entirely decoupled from the cell itself.

你创建一个 单元格内容配置 of type UIContentConfiguration. Then, you set the properties of this content configuration as you like. Similarly, you can create a 单元格背景配置 of type UIBackgroundConfiguration.

结果是可重复使用的配置,您可以应用于您所喜欢的任何单元格。

现在是时候了解这是怎么回事!

配置单元格

你刚刚学习了现代细胞配置的理论。现在,您将通过添加代码来更新宠物单元以显示宠物形象和年龄,将单元格内容配置付诸实践。在下一节中,您将应用单元格背景配置。

petCellRegistration(), replace:

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. Create a configuration of type UIListContentConfiguration with default styling. With this configuration, you have a cell where you can set an image, text and secondary text.
  2. 将宠物的数据应用于配置,包括宠物的图像。
  3. 设置图像的大小。
  4. Apply configuration to contentConfiguration of the cell.

建立和运行。

宠物探险家图片

突然,宠物看起来更可爱。你有动力采用吗? :]

采用宠物

最后,你将采用宠物。迭戈正在等着你接他!

First, you’ll learn how to create and apply a background configuration for a cell. Cells with adopted pets will get a background color. You’ll be using UIBackgroundConfiguration, introduced in iOS 14 as a part of modern cell configuration.

The starter project already has a property to store the adopted pets: adoptions.

petCellRegistration() 和 below 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. Create a UIBackgroundConfiguration, configured with the default properties for a 列表PlainCell. Assign it to backgroundConfig.
  3. Next, modify backgroundConfig to your taste.
  4. Assign backgroundConfig to cell.backgroundConfiguration.

你却无法测试它。你需要先采用宠物。

The starter project has a PetDetailViewController. This view controller has the 采纳 按钮。 But how do you navigate to the PetDetailViewController?

You add a disclosure indicator to the pet cell. In petCellRegistration() 和 below cell.contentConfiguration = configuration, 添加:

cell.accessories = [.disclosureIndicator()]

在这里,您可以设置单元格的披露指示符。

Now you need to navigate to the PetDetailViewController when you tap a pet cell.

Add the following code to 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))

collectionView(_:didSelectItemAt:) 当您点击宠物单元格时被调用。在此代码中,您:

  1. Check if the item at the selected 指数Path exists.
  2. Safe-unwrap pet.
  3. Then, push PetDetailViewController on the navigation stack. pushDetailForPet() is part of the starter project.

建立和运行。寻找迭戈并点击单元格。

宠物探险家详情

这是你的朋友迭戈!点按 采纳 button.

宠物探险家没有背景

您已采用迭戈并导航回宠物探险家。你希望迭戈的牢房有一个蓝色背景,但它没有。发生了什么?

数据源尚未更新。你现在要这样做。

Add the following method to 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. Retrieve all items from dataSource.snapshot().
  2. Look for the item that represents pet 和 assign it to petItem.
  3. Reload petItem in snapshot.
  4. Then apply the updated snapshot to the dataSource.

Now make sure you call updateDataSource(for:) when you adopt a pet.

petDetailViewController(_:didAdoptPet:), 添加:

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

当用户采用宠物时调用此代码。在这里:

  1. 在sert the adopted pet in adoptions.
  2. Call updateDataSource(for:). This is the method you just created.

建立和运行。轻敲 迭戈。然后,在细节屏幕上,点击 采纳。导航后,您将看到以下屏幕。

宠物探险家背景

迭戈有蓝色背景。他现在是你的。 :]

什么是截面快照?

A 章节快照 encapsulates the data for a single section in a UICollectionView. This has two important benefits:

  1. 截面快照使得可以模拟分层数据。当您使用宠物类别实现列表时已应用此项。
  2. A UICollectionView data source can have a snapshot per section, instead of a single snapshot for the entire collection view. This lets you add multiple sections to a collection view, where each section can have a different layout and behavior.

您将添加一节为采用的宠物查看了这件事。

为采用宠物添加一节

You want to create your adopted pets list as a separate section in the collectionView, below the expandable list with the pet categories you created earlier.

配置布局

Replace the body of configureLayout() with:

// 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)

This configures the collectionView‘s layout on a per section basis. In this code, you:

  1. Create a closure that returns a NSCollectionLayoutSection. You have multiple sections now, and this closure can return a layout for each section separately, based on the sectionIndex. In this case, your sections are laid out identically so you don’t use the sectionIndex.

    You assign the closure to provider. layoutEnv provides information about the layout environment.

  2. Create a configuration for a list with .grouped appearance.
  3. Return NSCollectionLayoutSection.list for the section with the given configuration.
  4. Create UICollectionViewCompositionalLayout with provider as sectionProvider. You assign the layout to collectionView.collectionViewLayout.

接下来,您将配置演示文稿。

配置演示文稿

Add the following method to the first PetExplorerViewController extension block:

func adoptedPetCellRegistration() 
  -> UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
  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()]
  }
}

This code is applied to a cell in .adoptedPets. It should look familiar to you. It’s similar to petCellRegistration() you added in 配置单元格。现在,您将配置数据。

配置数据

makeDatasource(), replace:

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)
}

使用此代码,您将通过依赖于该部分的数据源返回的单元格。在这里:

  1. Safely unwrap section.
  2. Return a petCellRegistration() for .availablePets
  3. Return an adoptedPetCellRegistration() for .adoptedPets

是时候将部分添加到数据源时。

applyInitialSnapshots(), insert the following code at the beginning of the method:

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

在此代码中,您:

  1. Create a new snapshot.
  2. Append all sections to snapshot.
  3. Apply the snapshot to dataSource.

建立和运行。采用迭戈。 :]

宠物探险家没有新部分

迭戈有一个蓝色背景,让你知道采用成功了。但是你添加的部分在哪里?

The section is there, but it’s empty. You added Diego to adoptedPets, but didn’t insert him into the data source yet. That’s what you’ll do now.

petDetailViewController(_:didAdoptPet:), right below adoptions.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. Retrieve a snapshot for .adoptedPets from dataSource. You assign it to adoptedPetsSnapshot.
  2. Create a new Item for the adopted pet 和 assign it to newItem.
  3. Append newItem to adoptedPetsSnapshot.
  4. You apply the modified adoptedPetsSnapshot to .adoptedPets of the dataSource.

建立和运行。

宠物探险家最后的迭戈

有用!迭戈位于采用宠物的部分。 :]

然后去哪儿?

您可以通过使用下载最终项目 下载材料 此页面顶部或底部的按钮。

您已经学会了很多关于iOS14的UICollectionView的改进。这包括:

  1. 创建一个可扩展 列表 使用 UICollectionLayoutListConfiguration.
  2. 采用 现代电池配置 to configure UICollectionView cells.
  3. 采用 截面快照 to add multiple sections to a UICollectionView.

你只触及了表面。有关更多详细信息,请查看WWDC 2020会话 UICollectionView的进步.

如果您有任何疑问或意见,请加入下面的论坛!

平均评级

4.6/5

为此内容添加评级

5 ratings

更像这样的

贡献者

注释