UIKit框架(五十一) —— 基于iOS14的UICollectionView List的創(chuàng)建(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2021.01.25 星期一

前言

iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面,用戶交互也是通過(guò)UIKit進(jìn)行的。感興趣的參考上面幾篇文章。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(二)
11. UIKit框架(十一) —— UICollectionView的重用、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(二)
17. UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(三)
18. UIKit框架(十八) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫(huà)的實(shí)現(xiàn)(一)
19. UIKit框架(十九) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫(huà)的實(shí)現(xiàn)(二)
20. UIKit框架(二十) —— 基于UILabel跑馬燈類似效果的實(shí)現(xiàn)(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場(chǎng)和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場(chǎng)和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定義布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定義布局 (二)
28. UIKit框架(二十八) —— 一個(gè)UISplitViewController的簡(jiǎn)單實(shí)用示例 (一)
29. UIKit框架(二十九) —— 一個(gè)UISplitViewController的簡(jiǎn)單實(shí)用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡(jiǎn)單示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡(jiǎn)單示例(二)
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轉(zhuǎn)盤效果的實(shí)現(xiàn)(一)
39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)
40. UIKit框架(四十) —— iOS 13中UISearchController 和 UISearchBar的新更改(二)
41. UIKit框架(四十一) —— 使用協(xié)議構(gòu)建自定義Collection(一)
42. UIKit框架(四十二) —— 使用協(xié)議構(gòu)建自定義Collection(二)
43. UIKit框架(四十三) —— CALayer的簡(jiǎn)單實(shí)用示例(一)
44. UIKit框架(四十四) —— CALayer的簡(jiǎn)單實(shí)用示例(二)
45. UIKit框架(四十五) —— 支持DarkMode的簡(jiǎn)單示例(一)
46. UIKit框架(四十六) —— 支持DarkMode的簡(jiǎn)單示例(二)
47. UIKit框架(四十七) —— 自定義Calendar Control的簡(jiǎn)單示例(一)
48. UIKit框架(四十八) —— 自定義Calendar Control的簡(jiǎn)單示例(二)
49. UIKit框架(四十九) —— UIVisualEffectView原理和簡(jiǎn)單使用(一)
50. UIKit框架(五十) —— UIVisualEffectView原理和簡(jiǎn)單使用(二)

開(kāi)始

首先看下主要內(nèi)容:

在本教程中,您將學(xué)習(xí)如何在單個(gè)collection view中創(chuàng)建列表,使用現(xiàn)代單元配置(cell configuration)以及配置多個(gè)section snapshot。內(nèi)容來(lái)自翻譯

接著看下寫作環(huán)境:

Swift 5, iOS 14, Xcode 12

下面就一起來(lái)看正文啦。

在iOS 14中,Apple為UICollectionView引入了新特性。Lists使您可以在UICollectionView中包括類似UITableView的部分。現(xiàn)代單元配置Modern Cell Configuration使注冊(cè)和配置collection view cell更加容易。并且,Section Snapshots允許UICollectionView中包含多個(gè)section,其中每個(gè)section可以具有不同的布局。

在本教程中,您將學(xué)習(xí)如何:

  • 使用UICollectionLayoutListConfiguration創(chuàng)建一個(gè)可擴(kuò)展列表list
  • 使用Modern Cell Configuration來(lái)配置UICollectionView cell
  • 使用Section Snapshots可將多個(gè)節(jié)(section)添加到UICollectionView

注意:本教程假定您熟悉Apple在iOS 13中引入的UICollectionViewDiffableDataSourceUICollectionViewCompositionalLayout。如果您以前從未使用過(guò)它們,請(qǐng)查看Collection View and Diffable Data Source and Modern Collection Views with Compositional Layouts

事不宜遲,該開(kāi)始了!

Xcode中打開(kāi)啟動(dòng)項(xiàng)目。構(gòu)建并運(yùn)行。

您會(huì)看到一個(gè)空的Pet Explorer屏幕。 它是Get a Pet應(yīng)用程序的一部分,該應(yīng)用程序顯示可供收養(yǎng)的寵物。 您將基于此應(yīng)用程序構(gòu)建。 在最終版本中,您可以瀏覽寵物類別并選擇一個(gè)寵物以查看其詳細(xì)信息。 然后,當(dāng)您找到喜歡的寵物時(shí),可以點(diǎn)擊Adopt以領(lǐng)養(yǎng)該寵物。

完整的應(yīng)用程序的Pet Explorer屏幕顯示可用和已收養(yǎng)的寵物:

注意可愛(ài)的狗Diego。完成本教程后,您將成為該虛擬小狗的驕傲擁有者。

打開(kāi)Xcode。瀏覽項(xiàng)目。當(dāng)應(yīng)用啟動(dòng)時(shí),它使用PetExplorerViewController作為根視圖控制器,將導(dǎo)航控制器設(shè)置為初始視圖控制器。打開(kāi)Main.storyboard選中該設(shè)置。

打開(kāi)PetExplorerViewController.swift以瀏覽此文件。 PetExplorerViewControllercollectionView為空。稍后,您將使用代表寵物和寵物類別的列表項(xiàng)來(lái)填充它。

Pet.swift具有與寵物有關(guān)的所有數(shù)據(jù)。

DataSource類型別名是為了方便起見(jiàn)。稍后在配置UICollectionView數(shù)據(jù)源時(shí)將使用它。

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

最后,在PetExplorerViewController擴(kuò)展程序中,您會(huì)找到pushDetailForPet(_:withAdoptionStatus :)。當(dāng)用戶選擇一個(gè)項(xiàng)目時(shí),此方法將顯示PetDetailViewController

打開(kāi)PetDetailViewController.swift。這是一個(gè)簡(jiǎn)單的視圖控制器類,可顯示寵物的圖像,姓名和出生年月。

現(xiàn)在,您已經(jīng)探索了應(yīng)用程序的結(jié)構(gòu),接下來(lái)是時(shí)候了解UICollectionView列表了。


What is a List?

listUICollectionView中的table view類似的視圖。您可以通過(guò)僅在少量代碼下將可配置的UICollectionViewCompositionalLayout應(yīng)用于UICollectionView的一section來(lái)創(chuàng)建列表。

您可以配置列表以顯示分層數(shù)據(jù),并可以折疊和展開(kāi)列表項(xiàng)或使其看起來(lái)類似于傳統(tǒng)的table view。如果您需要在應(yīng)用程序中使用table view,則可以使用帶有UICollectionView API的列表,也可以使用傳統(tǒng)的UITableView

在大多數(shù)情況下,列表list更易于創(chuàng)建和配置。

現(xiàn)在該創(chuàng)建您的第一個(gè)列表list了。


Creating a List

您將創(chuàng)建一個(gè)顯示寵物類別的平面列表。這將是您不使用UITableView的第一個(gè)table view。對(duì)于平面列表,與UITableView相比,UICollectionView list的優(yōu)勢(shì)可能不會(huì)立即顯現(xiàn)。稍后,當(dāng)您使列表可擴(kuò)展時(shí),您會(huì)發(fā)現(xiàn)使用UICollectionView list的真正好處。

注意:UICollectionView體系結(jié)構(gòu)在布局,表示形式和數(shù)據(jù)之間有清晰的分隔。本教程的示例代碼遵循這種模式。每次向Get a Pet添加新功能時(shí),您都會(huì)添加一段代碼,首先用于布局,然后用于演示,最后用于數(shù)據(jù)。

1. Configuring the Layout

iOS 13中,Apple引入了UICollectionViewCompositionalLayout,這是一種用于構(gòu)建復(fù)雜布局的新API。 在iOS 14中,Apple添加了:

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

這使您可以在一行代碼中創(chuàng)建list layout,而無(wú)需了解UICollectionViewCompositionalLayout API的詳細(xì)知識(shí)。 您可以使用UICollectionLayoutListConfiguration配置列表的外觀,顏色,分隔符,頁(yè)眉和頁(yè)腳。

現(xiàn)在該將其應(yīng)用于您的代碼了:

打開(kāi)PetExplorerViewController.swift。 在帶有// MARK: - Functions的行下方添加以下方法:

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

這將配置collectionView的布局。 在這里,您:

  • 1) 創(chuàng)建具有.grouped外觀的configuration。 這為您提供了一個(gè)看起來(lái)像table view的布局配置。
  • 2) 接下來(lái),您將創(chuàng)建一個(gè)具有list sectionUICollectionViewCompositionalLayout,該節(jié)section將使用該configuration。 您需要將此布局應(yīng)用于collectionView

如您所見(jiàn),整個(gè)布局配置只有兩行代碼。

通過(guò)添加以下內(nèi)容在viewDidLoad()的末尾調(diào)用此方法:

configureLayout()

2. Configuring the Presentation

現(xiàn)在是時(shí)候?yàn)榱斜?code>list創(chuàng)建一個(gè)collection view cell了。 單元格顯示寵物類別。 您將了解注冊(cè)單元格的新方法。

在第一個(gè)PetExplorerViewController擴(kuò)展塊內(nèi),添加:

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

這是您首次接觸modern cell registration and configuration。 代碼是這樣的:

  • 1) categoryCellregistration()UICollectionViewListCell類型的單元格和Item類型的數(shù)據(jù)項(xiàng)創(chuàng)建單元格注冊(cè)。 這是注冊(cè)collection view cell的現(xiàn)代方法。
  • 2) 您創(chuàng)建單元格注冊(cè),并傳入一個(gè)閉包來(lái)配置單元格。 當(dāng)cell需要渲染時(shí)調(diào)用閉包。
  • 3) 然后配置cell。 寵物類別在item.title中可用。 如果您不了解正在發(fā)生的事情,請(qǐng)不要擔(dān)心。 本教程的整個(gè)章節(jié)都涉及現(xiàn)代單元配置(modern cell configuration)

在配置數(shù)據(jù)源時(shí),您將調(diào)用categoryCellregistration()

3. Configuring the Data

您已為collection view配置了布局和單元格。 現(xiàn)在,您需要一種機(jī)制,可以基于collection view的基礎(chǔ)數(shù)據(jù)創(chuàng)建這些單元格。 這就是數(shù)據(jù)源的來(lái)源。

將以下方法添加到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) 您創(chuàng)建并返回一個(gè)DataSource,傳入collectionView和一個(gè)閉包,該閉包將UICollectionViewCell提供給數(shù)據(jù)源。
  • 2) 在閉包內(nèi)部,您要求collectionView使UICollectionViewCelldequeue。 然后,您將單元格注冊(cè)作為參數(shù)傳遞,因此collectionView將知道其必須出隊(duì)的單元格類型。 您剛才創(chuàng)建的categoryCellregistration()包含單元配置的邏輯。

將以下屬性添加到PetExplorerViewController

lazy var dataSource = makeDataSource()

由于您在聲明中使用了lazy,因此在第一次需要collectionView時(shí)會(huì)為其創(chuàng)建數(shù)據(jù)源。

您配置了collectionView的布局,展示和數(shù)據(jù)。 現(xiàn)在,您將使用數(shù)據(jù)項(xiàng)填充collectionView

仍在PetExplorerViewController.swift中,將以下方法添加到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)
}

此代碼使用可擴(kuò)散的數(shù)據(jù)源來(lái)更新list的內(nèi)容。 Apple在iOS 13中引入了diffable data source。該代碼尚無(wú)任何新的iOS 14功能。 當(dāng)您使列表可擴(kuò)展并將section snapshots添加到列表時(shí),情況將會(huì)改變。

使用applyInitialSnapshots()可以:

  • 1) 創(chuàng)建一個(gè)categorySnapshot,其中包含寵物類別名稱。
  • 2) 然后為每個(gè)類別category創(chuàng)建一個(gè)項(xiàng)目Item并將其添加到categories中。
  • 3) 將.availablePets附加到categorySnapshot
  • 4) 然后將類別categories中的項(xiàng)目附加到categorySnapshot.availablePets
  • 5) 將categorySnapshot應(yīng)用于dataSource

您已經(jīng)添加了一個(gè)部分section,并指明了屬于該部分的所有元素。

現(xiàn)在,在viewDidLoad()的末尾添加對(duì)applyInitialSnapshots()的調(diào)用:

applyInitialSnapshots()

構(gòu)建并運(yùn)行

恭喜你! 這是您的第一個(gè)帶有列表的UICollectionView

列表list支持與UITableView樣式匹配的外觀:.plain,.grouped.insetGrouped。 您創(chuàng)建的列表具有.grouped外觀。

iOS 14具有用于將列表顯示為邊欄的新外觀:.sidebar.sidebarPlain。 它們通常在拆分視圖(split view)中用作主視圖。

現(xiàn)在,您可以擴(kuò)展列表。


Making the List Expandable

現(xiàn)在是時(shí)候?qū)?code>pets添加到類別中了。

在這里,您將發(fā)現(xiàn)UICollectionView列表的強(qiáng)大功能。 使用UITableView,您將不得不處理category cells and pet cell上的點(diǎn)擊tap,保持單元格的可見(jiàn)狀態(tài)和展開(kāi)狀態(tài),并編寫顯示或隱藏寵物單元格的代碼。

使用UICollectionView列表,您只需要提供類別和寵物的分層數(shù)據(jù)結(jié)構(gòu)。 該列表將處理其余的內(nèi)容。 您很快就會(huì)發(fā)現(xiàn),僅需幾行代碼就可以實(shí)現(xiàn)很多目標(biāo)。

Pet.swift包含所有寵物及其所屬類別的數(shù)據(jù)。 無(wú)需更改布局中的任何內(nèi)容,因此您將從展示開(kāi)始。

1. Configuring the Presentation

之前,您為pet category創(chuàng)建了一個(gè)單元格。 您了解了注冊(cè)單元格的新方法。 在這里,您將執(zhí)行相同的操作,這次將為寵物創(chuàng)建一個(gè)cell。 單元格將顯示寵物的名字。

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()與您之前添加的categoryCellregistration()類似。 您創(chuàng)建一個(gè)單元注冊(cè)并使用現(xiàn)代單元配置來(lái)配置該單元。 在這里,您使用defaultContentConfiguration(),然后將寵物名稱分配為要顯示的文本。

在配置數(shù)據(jù)源時(shí),您將調(diào)用petCellRegistration()

接下來(lái),您可以通過(guò)向類別單元格添加outline disclosure accessory來(lái)擴(kuò)展列表。 這表明一個(gè)項(xiàng)目可以展開(kāi)和折疊。 當(dāng)您點(diǎn)擊一個(gè)類別時(shí),列表將展開(kāi)并顯示該類別的寵物。

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

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

在這里,您:

  • 1) 創(chuàng)建要應(yīng)用于disclosureAccessoryoptions。 您使用.header樣式使單元格可擴(kuò)展。
  • 2) 然后,使用配置的options創(chuàng)建一個(gè)disclosureAccessory
  • 3) 將accessory應(yīng)用于cell。 單元cell可以具有多個(gè)附件,因此您可以在一個(gè)數(shù)組中添加discoveryAccessory

構(gòu)建并運(yùn)行。

outline disclosure是可見(jiàn)的,但是當(dāng)您點(diǎn)擊一個(gè)單元格時(shí),什么也不會(huì)發(fā)生。 為什么? 您尚未將寵物添加到其類別中。 接下來(lái),您將要執(zhí)行此操作。

2. Configuring the Data

接下來(lái),您將學(xué)習(xí)如何將分層數(shù)據(jù)添加到列表中。 完成后,您會(huì)看到列表自動(dòng)支持折疊和展開(kāi)單元格。

現(xiàn)在,調(diào)整數(shù)據(jù)源以將寵物細(xì)胞添加到其類別中。

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

一個(gè)item可以代表一個(gè)類別或一個(gè)寵物。 這取決于pet的值。 在此代碼中,collectionViewdequeue

  • 1) 如果item.pet不為nil,則為petcell
  • 2) 如果item.petnil,則為categorycell

您已配置了展示寵物所需的一切,但尚未添加任何寵物。 為此,您必須更新數(shù)據(jù)的初始快照。

body替換為applyInitialSnapshots()

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

要在類別和寵物之間建立層次關(guān)系,請(qǐng)執(zhí)行以下操作:

  • 1) 創(chuàng)建類型為NSDiffableDataSourceSectionSnapshotcategorySnapshot。 這是一個(gè)section snapshot。 使用section snapshot,可以用分層結(jié)構(gòu)表示數(shù)據(jù),例如帶有可擴(kuò)展項(xiàng)的輪廓。目前,這是有關(guān)section snapshot的全部知識(shí)。 在本教程的后面,您將了解有關(guān)section snapshot的更多信息。
  • 2) 然后,遍歷Pet.Category.allCases中的類別。 在循環(huán)中,將寵物添加到其類別中。
  • 3) 創(chuàng)建一個(gè)categoryItem
  • 4) 將categoryItem追加到categorySnapshot
  • 5) 然后,創(chuàng)建一個(gè)數(shù)組petItems,其中包含屬于當(dāng)前類別category的所有寵物。
  • 6) 通過(guò)將petItems附加到當(dāng)前categoryItem來(lái)創(chuàng)建類別和寵物之間的層次關(guān)系。
  • 7) 將categorySnapshot應(yīng)用于dataSource.availablePets

構(gòu)建并運(yùn)行。

點(diǎn)擊一個(gè)類別。列表將展開(kāi)并顯示寵物名稱。很好!

現(xiàn)在是時(shí)候讓cell看起來(lái)更好一點(diǎn)了。


What is Modern Cell Configuration?

如果您使用過(guò)UITableViewUICollectionView,則習(xí)慣于通過(guò)直接設(shè)置單元格的屬性來(lái)對(duì)其進(jìn)行配置。在iOS 14中,單元配置可以完全與單元本身分離。

您創(chuàng)建類型為UIContentConfigurationcell content configuration。然后,根據(jù)需要設(shè)置此內(nèi)容配置的屬性。同樣,您可以創(chuàng)建類型為UIBackgroundConfigurationcell background configuration

結(jié)果是可重用的配置,您可以將其應(yīng)用于您喜歡的任何cell

現(xiàn)在該看看它是如何工作的!


Configuring the Cells

您剛剛學(xué)習(xí)了modern cell configuration的理論。現(xiàn)在,您將通過(guò)添加代碼來(lái)更新pet cell以顯示寵物的圖像和年齡,從而實(shí)施單元格內(nèi)容配置。在下一部分中,您將應(yīng)用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

在這里,您將看到實(shí)際的單元格內(nèi)容配置。 您:

  • 1) 使用默認(rèn)樣式創(chuàng)建類型UIListContentConfiguration的配置。使用此配置,您將在一個(gè)單元格中設(shè)置圖像,文本和輔助文本。
  • 2) 將寵物的數(shù)據(jù)應(yīng)用于配置,包括寵物的圖像。
  • 3) 設(shè)置圖像的尺寸。
  • 4) 將configuration應(yīng)用于單元的contentConfiguration

構(gòu)建并運(yùn)行。

突然,寵物看起來(lái)更可愛(ài)了。 您是否有動(dòng)機(jī)領(lǐng)養(yǎng)個(gè)?


Adopting a Pet

最后,您領(lǐng)養(yǎng)一只寵物。 Diego正在等你接他!

首先,您將學(xué)習(xí)如何為單元?jiǎng)?chuàng)建和應(yīng)用后臺(tái)配置。 養(yǎng)有寵物的牢Cells將具有背景色。 您將使用iOS 14中引入的UIBackgroundConfiguration,它是現(xiàn)代單元配置的一部分。

入門項(xiàng)目已經(jīng)具有存儲(chǔ)被收養(yǎng)寵物的屬性: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
}

要為單元格提供彩色背景,請(qǐng)執(zhí)行以下操作:

  • 1) 檢查寵物是否被收養(yǎng)。 只有收養(yǎng)的寵物才會(huì)有彩色背景。
  • 2) 創(chuàng)建一個(gè)UIBackgroundConfiguration,為listPlainCell配置默認(rèn)屬性。 將其分配給backgroundConfig
  • 3) 接下來(lái),根據(jù)您的喜好修改backgroundConfig
  • 4) 將backgroundConfig分配給cell.backgroundConfiguration

您尚無(wú)法測(cè)試。 您需要先養(yǎng)寵物。

入門項(xiàng)目有一個(gè)PetDetailViewController。 該視圖控制器具有Adopt按鈕。 但是,如何導(dǎo)航到PetDetailViewController

您向?qū)櫸?code>cell添加一個(gè)disclosure indicator。 在petCellRegistration()cell.contentConfiguration = configuration下,添加:

cell.accessories = [.disclosureIndicator()]

在這里設(shè)置單元格的disclosure indicator

現(xiàn)在,當(dāng)您點(diǎn)擊寵物單元格時(shí),您需要導(dǎo)航到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))

當(dāng)您點(diǎn)擊pet cell時(shí),將調(diào)用collectionView(_:didSelectItemAt :)。 在此代碼中,您:

  • 1) 檢查所選indexPath處的item是否存在。
  • 2) 安全解包pet
  • 3) 然后,將PetDetailViewController推入導(dǎo)航堆棧。 pushDetailForPet()是入門項(xiàng)目的一部分。

構(gòu)建并運(yùn)行。 尋找Diego,然后點(diǎn)按單元格。

這是你的朋友Diego! 點(diǎn)擊Adopt按鈕。

您已采用Diego,然后導(dǎo)航回Pet Explorer。 您會(huì)期望Diego的單元格具有藍(lán)色背景,但背景并非如此。 發(fā)生了什么?

數(shù)據(jù)源尚未更新。 您現(xiàn)在就要做。

將以下方法添加到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應(yīng)用于dataSource

現(xiàn)在確保收養(yǎng)寵物時(shí)調(diào)用updateDataSource(for :)

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

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

用戶收養(yǎng)寵物時(shí),將調(diào)用此代碼。 在這里:

  • 1) 將收養(yǎng)的pet插入adoptions
  • 2) 調(diào)用updateDataSource(for :)。 這是您剛創(chuàng)建的方法。

構(gòu)建并運(yùn)行。 點(diǎn)擊Diego。 然后,在詳細(xì)信息屏幕上,點(diǎn)擊Adopt。 向后瀏覽后,您會(huì)看到以下屏幕。

Diego有藍(lán)色背景。 他現(xiàn)在是你的。


What is a Section Snapshot?

section snapshotUICollectionView中單個(gè)section的數(shù)據(jù)封裝起來(lái)。 這有兩個(gè)重要的好處:

  • 1) Section snapshot使model hierarchical data成為可能。 在實(shí)施帶有寵物類別的列表時(shí),您已經(jīng)應(yīng)用了此功能。
  • 2) UICollectionView數(shù)據(jù)源每個(gè)section可以有一個(gè)snapshot,而不是整個(gè)collection view的單個(gè)快照。 這使您可以將多個(gè)sections添加到collection view中,其中每個(gè)部分可以具有不同的布局和行為。

您將為收養(yǎng)的寵物添加一個(gè)section,以了解其工作原理。


Adding a Section for Adopted Pets

您想在collectionView中的單獨(dú)section中創(chuàng)建收養(yǎng)的寵物列表,其中包含您之前創(chuàng)建的寵物類別的可擴(kuò)展列表下方。

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) 創(chuàng)建一個(gè)返回NSCollectionLayoutSection的閉包。 現(xiàn)在您有多個(gè)sections,并且此閉包可以根據(jù)sectionIndex分別返回每個(gè)section的布局。 在這種情況下,各section的布局相同,因此您無(wú)需使用sectionIndex。您將閉包分配給providerlayoutEnv提供有關(guān)布局環(huán)境的信息。
  • 2) 為具有.grouped外觀的列表創(chuàng)建配置。
  • 3) 返回具有給定configurationsectionNSCollectionLayoutSection.list
  • 4) 使用provider作為sectionProvider創(chuàng)建UICollectionViewCompositionalLayout。 您將布局分配給collectionView.collectionViewLayout

接下來(lái),您將配置展示。

2. Configuring the Presentation

將以下方法添加到第一個(gè)PetExplorerViewController擴(kuò)展塊中:

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

此代碼將應(yīng)用于.adoptedPets中的單元格。 它對(duì)您應(yīng)該看起來(lái)很熟悉。 與您在Configuring the Cells中添加的petCellRegistration()類似。 現(xiàn)在,您將配置數(shù)據(jù)。

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

使用此代碼,可以使數(shù)據(jù)源返回的單元格依賴于該section。 在這里:

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

是時(shí)候?qū)⑦@些sections添加到數(shù)據(jù)源中了。

applyInitialSnapshots()中,在方法的開(kāi)頭插入以下代碼:

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

在此代碼中,您:

  • 1) 創(chuàng)建一個(gè)新的snapshot
  • 2) 將所有sections追加到snapshot
  • 3) 將snapshot應(yīng)用于dataSource

構(gòu)建并運(yùn)行。 領(lǐng)養(yǎng)Diego

Diego的背景是藍(lán)色,所以您知道收養(yǎng)成功了。 但是您添加的section在哪里?

section在那里,但為空。 您已將Diego添加到了收養(yǎng)的寵物中,但尚未將他插入數(shù)據(jù)源中。 這就是您現(xiàn)在要做的。

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) 從數(shù)據(jù)源中檢索.adoptedPetssnapshot。 您將其分配給adoptedPetsSnapshot
  • 2) 為收養(yǎng)的pet創(chuàng)建一個(gè)新Item,并將其分配給newItem
  • 3) 將newItem追加到adoptedPetsSnapshot
  • 4) 您將修改后的acceptedPetsSnapshot應(yīng)用于dataSource.adoptedPets

構(gòu)建并運(yùn)行。

起作用了! Diego在收養(yǎng)寵物部分。

您已經(jīng)了解了iOS 14UICollectionView的許多改進(jìn),其中包括:

  • 1) 使用UICollectionLayoutListConfiguration創(chuàng)建可擴(kuò)展list
  • 2) 使用Modern Cell Configuration來(lái)配置UICollectionView單元。
  • 3) 使用Section Snapshots可將多個(gè)sections添加到UICollectionView

而您只接觸了表面。 有關(guān)更多詳細(xì)信息,請(qǐng)查看WWDC 2020中的Advances in UICollectionView

后記

本篇主要講述了基于iOS14UICollectionView List的創(chuàng)建,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容