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原理和簡單使用(二)
51. UIKit框架(五十一) —— 基於iOS14的UICollectionView List的創建(一)

源碼

1. Swift

首先看下工程組織結構

下面就是源碼了

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的創建,感興趣的給個贊或者關注~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章