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)單使用(二)
51. UIKit框架(五十一) —— 基于iOS14的UICollectionView List的創(chuàng)建(一)

源碼

1. Swift

首先看下工程組織結(jié)構(gòu)

下面就是源碼了

1. Pet.swift
import Foundation

struct Pet: Hashable {
  enum Category: CaseIterable, CustomStringConvertible {
    case birds, cats, chameleons, cows, dogs, monkeys, penguins, pigs, rats, snakes, squirrels
  }
  let imageName: String
  let name: String
  let birthYear: Int
  var age: Int {
    let thisYear = Calendar.current.component(.year, from: Date())
    return thisYear - birthYear
  }
  let category: Category
  private let id = UUID()
}

extension Pet.Category {
  var description: String {
    switch self {
    case .birds: return "Birds"
    case .cats: return "Cats"
    case .chameleons: return "Chameleons"
    case .cows: return "Cows"
    case .dogs: return "Dogs"
    case .monkeys: return "Monkeys"
    case .penguins: return "Penguins"
    case .pigs: return "Pigs"
    case .rats: return "Rats"
    case .snakes: return "Snakes"
    case .squirrels: return "Squirrels"
    }
  }

  var pets: [Pet] {
    switch self {
    case .birds:
      return [
        Pet(imageName: "bird1", name: "Happy", birthYear: 2017, category: self),
        Pet(imageName: "bird2", name: "Swifty", birthYear: 2018, category: self),
        Pet(imageName: "bird3", name: "Speedy", birthYear: 2018, category: self)
      ]
    case .cats:
      return [
        Pet(imageName: "cat1", name: "Max", birthYear: 2015, category: self),
        Pet(imageName: "cat2", name: "Jake", birthYear: 2018, category: self),
        Pet(imageName: "cat3", name: "Daisy", birthYear: 2012, category: self),
        Pet(imageName: "cat4", name: "Sunny", birthYear: 2008, category: self),
        Pet(imageName: "cat5", name: "Oscar", birthYear: 2017, category: self)
      ]
    case .chameleons:
      return [
        Pet(imageName: "chameleon1", name: "Zoe", birthYear: 2015, category: self)
      ]
    case .cows:
      return [
        Pet(imageName: "cow1", name: "Betty", birthYear: 2016, category: self),
        Pet(imageName: "cow2", name: "Rosie", birthYear: 2013, category: self)
      ]
    case .dogs:
      return [
        Pet(imageName: "dog1", name: "Buddy", birthYear: 2018, category: self),
        Pet(imageName: "dog2", name: "Molly", birthYear: 2014, category: self),
        Pet(imageName: "dog3", name: "Bella", birthYear: 2009, category: self),
        Pet(imageName: "dog4", name: "Dixie", birthYear: 2018, category: self),
        Pet(imageName: "dog5", name: "Freddy", birthYear: 2012, category: self),
        Pet(imageName: "dog6", name: "Lucky", birthYear: 2016, category: self),
        Pet(imageName: "dog7", name: "Snoopy", birthYear: 2015, category: self),
        Pet(imageName: "dog8", name: "Joker", birthYear: 2018, category: self),
        Pet(imageName: "dog9", name: "Diego", birthYear: 2018, category: self),
        Pet(imageName: "dog10", name: "Bruno", birthYear: 2016, category: self)
      ]
    case .monkeys:
      return [
        Pet(imageName: "monkey1", name: "Turbo", birthYear: 2015, category: self)
      ]
    case .penguins:
      return [
        Pet(imageName: "penguin1", name: "Helen", birthYear: 2017, category: self),
        Pet(imageName: "penguin2", name: "Fred", birthYear: 2014, category: self)
      ]
    case .pigs:
      return [
        Pet(imageName: "pig1", name: "Piggy", birthYear: 2015, category: self)
      ]
    case .rats:
      return [
        Pet(imageName: "rat1", name: "Cutie", birthYear: 2018, category: self)
      ]
    case .snakes:
      return [
        Pet(imageName: "snake1", name: "Worm", birthYear: 2013, category: self),
        Pet(imageName: "snake2", name: "Noodles", birthYear: 2018, category: self),
        Pet(imageName: "snake3", name: "Slider", birthYear: 2017, category: self)
      ]
    case .squirrels:
      return [
        Pet(imageName: "squirrel1", name: "Chippy", birthYear: 2017, category: self)
      ]
    }
  }
}
2. Item.swift
import Foundation

struct Item: Hashable {
  let title: String
  let pet: Pet?
  private let id = UUID()

  init(pet: Pet? = nil, title: String) {
    self.pet = pet
    self.title = title
  }
}
3. PetExplorerViewController.swift
import UIKit

class PetExplorerViewController: UICollectionViewController {
  // MARK: - Properties
  lazy var dataSource = makeDataSource()
  var adoptions = Set<Pet>()

  // MARK: - Types
  enum Section: Int, CaseIterable, Hashable {
    case availablePets
    case adoptedPets
  }
  typealias DataSource = UICollectionViewDiffableDataSource<Section, Item>

  // MARK: - Life Cycle
  override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.title = "Pet Explorer"
    configureLayout()
    applyInitialSnapshots()
  }

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

  func makeDataSource() -> DataSource {
    return DataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in
      if item.pet != nil {
        guard let section = Section(rawValue: indexPath.section) else {
          return nil
        }
        switch section {
        case .availablePets:
          return collectionView.dequeueConfiguredReusableCell(
            using: self.petCellRegistration(), for: indexPath, item: item)
        case .adoptedPets:
          return collectionView.dequeueConfiguredReusableCell(
            using: self.adoptedPetCellRegistration(), for: indexPath, item: item)
        }
      } else {
        return collectionView.dequeueConfiguredReusableCell(
          using: self.categoryCellregistration(), for: indexPath, item: item)
      }
    }
  }

  func applyInitialSnapshots() {
    var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
    snapshot.appendSections(Section.allCases)
    dataSource.apply(snapshot, animatingDifferences: false)
    var categorySnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
    for category in Pet.Category.allCases {
      let categoryItem = Item(title: String(describing: category))
      categorySnapshot.append([categoryItem])
      let petItems = category.pets.map { Item(pet: $0, title: $0.name) }
      categorySnapshot.append(petItems, to: categoryItem)
    }
    dataSource.apply(categorySnapshot, to: .availablePets, animatingDifferences: false)
  }

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

// MARK: - CollectionView Cells
extension PetExplorerViewController {
  func categoryCellregistration() -> UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
    return .init { cell, _, item in
      var configuration = cell.defaultContentConfiguration()
      configuration.text = item.title
      cell.contentConfiguration = configuration
      let options = UICellAccessory.OutlineDisclosureOptions(style: .header)
      let disclosureAccessory = UICellAccessory.outlineDisclosure(options: options)
      cell.accessories = [disclosureAccessory]
    }
  }

  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
      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 = [UICellAccessory.disclosureIndicator()]
      if self.adoptions.contains(pet) {
        var backgroundConfig = UIBackgroundConfiguration.listPlainCell()
        backgroundConfig.backgroundColor = .systemBlue
        backgroundConfig.cornerRadius = 5
        backgroundConfig.backgroundInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
        cell.backgroundConfiguration = backgroundConfig
      }
      cell.contentConfiguration = configuration
      cell.accessories = [UICellAccessory.disclosureIndicator()]
    }
  }

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

// MARK: - UICollectionViewDelegate
extension PetExplorerViewController {
  override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    guard let item = dataSource.itemIdentifier(for: indexPath) else {
      collectionView.deselectItem(at: indexPath, animated: true)
      return
    }
    guard let pet = item.pet else {
      return
    }
    pushDetailForPet(pet, withAdoptionStatus: adoptions.contains(pet))
  }

  func pushDetailForPet(_ pet: Pet, withAdoptionStatus isAdopted: Bool) {
    let storyboard = UIStoryboard(name: "Main", bundle: .main)
    let petDetailViewController =
      storyboard.instantiateViewController(identifier: "PetDetailViewController") { coder in
        return PetDetailViewController(coder: coder, pet: pet)
      }
    petDetailViewController.delegate = self
    petDetailViewController.isAdopted = isAdopted
    navigationController?.pushViewController(petDetailViewController, animated: true)
  }
}

// MARK: - PetDetailViewControllerDelegate
extension PetExplorerViewController: PetDetailViewControllerDelegate {
  func petDetailViewController(_ petDetailViewController: PetDetailViewController, didAdoptPet pet: Pet) {
    adoptions.insert(pet)
    var adoptedPetsSnapshot = dataSource.snapshot(for: .adoptedPets)
    let newItem = Item(pet: pet, title: pet.name)
    adoptedPetsSnapshot.append([newItem])
    dataSource.apply(adoptedPetsSnapshot, to: .adoptedPets, animatingDifferences: true, completion: nil)
    updateDataSource(for: pet)
  }
}
4. PetDetailViewController.swift
import UIKit

protocol PetDetailViewControllerDelegate: class {
  func petDetailViewController(_ petDetailViewController: PetDetailViewController, didAdoptPet pet: Pet)
}

class PetDetailViewController: UIViewController {
  // MARK: - Properties
  var pet: Pet
  var isAdopted = false
  weak var delegate: PetDetailViewControllerDelegate?

  // MARK: - IBOutlets
  @IBOutlet weak var imageView: UIImageView!
  @IBOutlet weak var name: UILabel!
  @IBOutlet weak var age: UILabel!
  @IBOutlet weak var adoptButton: UIButton!

  // MARK: - Life Cycle
  init?(coder: NSCoder, pet: Pet) {
    self.pet = pet
    super.init(coder: coder)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    adoptButton.setTitle("Adopt", for: .normal)
    adoptButton.isHidden = isAdopted
    imageView.image = UIImage(named: pet.imageName)
    name.text = isAdopted ? "Your pet: \(pet.name)" : pet.name
    age.text = "\(pet.age) years old"
  }
}

// MARK: - IBActions
extension PetDetailViewController {
  @IBAction func didTapAdoptButton(_ sender: UIButton) {
    delegate?.petDetailViewController(self, didAdoptPet: pet)
    navigationController?.popViewController(animated: true)
  }
}
5. AppDelegate.swift
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate { }
6. SceneDelegate.swift
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?
}

后記

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饵史,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子胜榔,更是在濱河造成了極大的恐慌胳喷,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夭织,死亡現(xiàn)場(chǎng)離奇詭異吭露,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)尊惰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門讲竿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)泥兰,“玉大人,你說(shuō)我怎么就攤上這事题禀⌒” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵投剥,是天一觀的道長(zhǎng)师脂。 經(jīng)常有香客問(wèn)我,道長(zhǎng)江锨,這世上最難降的妖魔是什么吃警? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮啄育,結(jié)果婚禮上酌心,老公的妹妹穿的比我還像新娘。我一直安慰自己挑豌,他們只是感情好安券,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著氓英,像睡著了一般侯勉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铝阐,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天址貌,我揣著相機(jī)與錄音,去河邊找鬼徘键。 笑死练对,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吹害。 我是一名探鬼主播螟凭,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼它呀!你這毒婦竟也來(lái)了螺男?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤纵穿,失蹤者是張志新(化名)和其女友劉穎烟号,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體政恍,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年达传,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了篙耗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迫筑。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宗弯,靈堂內(nèi)的尸體忽然破棺而出脯燃,到底是詐尸還是另有隱情,我是刑警寧澤蒙保,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布辕棚,位于F島的核電站,受9級(jí)特大地震影響邓厕,放射性物質(zhì)發(fā)生泄漏逝嚎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一详恼、第九天 我趴在偏房一處隱蔽的房頂上張望补君。 院中可真熱鬧,春花似錦昧互、人聲如沸挽铁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)叽掘。三九已至,卻和暖如春玖雁,著一層夾襖步出監(jiān)牢的瞬間更扁,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工茄菊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疯潭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓面殖,卻偏偏與公主長(zhǎng)得像竖哩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脊僚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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