UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)

版本记录

版本号 时间
V1.0 2020.04.28 星期二

前言

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的使用(一)

源码

1. Swift

首先看下工程组织结构

下面就是源码了

1. AppDelegate.swift
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  // MARK: UISceneSession Lifecycle
  func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
  }
}
2. SceneDelegate.swift
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?
}
3. VideosViewController.swift
import UIKit
import SafariServices

class VideosViewController: UICollectionViewController {
  // MARK: - Properties
  private var sections = Section.allSections
  private lazy var dataSource = makeDataSource()
  private var searchController = UISearchController(searchResultsController: nil)
  
  // MARK: - Value Types
  typealias DataSource = UICollectionViewDiffableDataSource
  typealias Snapshot = NSDiffableDataSourceSnapshot
  
  // MARK: - Life Cycles
  override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .white
    configureSearchController()
    configureLayout()
    applySnapshot(animatingDifferences: false)
  }
  
  // MARK: - Functions
  func makeDataSource() -> DataSource {
    // 1
    let dataSource = DataSource(
      collectionView: collectionView,
      cellProvider: { (collectionView, indexPath, video) ->
        UICollectionViewCell? in
        // 2
        let cell = collectionView.dequeueReusableCell(
          withReuseIdentifier: "VideoCollectionViewCell",
          for: indexPath) as? VideoCollectionViewCell
        cell?.video = video
        return cell
    })
    dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
      guard kind == UICollectionView.elementKindSectionHeader else {
        return nil
      }
      let section = self.dataSource.snapshot()
        .sectionIdentifiers[indexPath.section]
      let view = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: SectionHeaderReusableView.reuseIdentifier,
        for: indexPath) as? SectionHeaderReusableView
      view?.titleLabel.text = section.title
      return view
    }
    return dataSource
  }
  
  // 1
  func applySnapshot(animatingDifferences: Bool = true) {
    // 2
    var snapshot = Snapshot()
    // 3
    snapshot.appendSections(sections)
    // 4
    sections.forEach { section in
      snapshot.appendItems(section.videos, toSection: section)
    }
    // 5
    dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
  }
}

// MARK: - UICollectionViewDataSource Implementation
extension VideosViewController {
  override func collectionView(
    _ collectionView: UICollectionView,
    didSelectItemAt indexPath: IndexPath
  ) {
    guard let video = dataSource.itemIdentifier(for: indexPath) else {
      return
    }
    guard let link = video.link else {
      print("Invalid link")
      return
    }
    let safariViewController = SFSafariViewController(url: link)
    present(safariViewController, animated: true, completion: nil)
  }
}

// MARK: - UISearchResultsUpdating Delegate
extension VideosViewController: UISearchResultsUpdating {
  func updateSearchResults(for searchController: UISearchController) {
    sections = filteredSections(for: searchController.searchBar.text)
    applySnapshot()
  }
  
  func filteredSections(for queryOrNil: String?) -> [Section] {
    let sections = Section.allSections
    guard
      let query = queryOrNil,
      !query.isEmpty
      else {
        return sections
    }
    
    return sections.filter { section in
      var matches = section.title.lowercased().contains(query.lowercased())
      for video in section.videos {
        if video.title.lowercased().contains(query.lowercased()) {
          matches = true
          break
        }
      }
      return matches
    }
  }
  
  func configureSearchController() {
    searchController.searchResultsUpdater = self
    searchController.obscuresBackgroundDuringPresentation = false
    searchController.searchBar.placeholder = "Search Videos"
    navigationItem.searchController = searchController
    definesPresentationContext = true
  }
}

// MARK: - Layout Handling
extension VideosViewController {
  private func configureLayout() {
    collectionView.register(
      SectionHeaderReusableView.self,
      forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
      withReuseIdentifier: SectionHeaderReusableView.reuseIdentifier
    )
    collectionView.collectionViewLayout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
      let isPhone = layoutEnvironment.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiom.phone
      let size = NSCollectionLayoutSize(
        widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
        heightDimension: NSCollectionLayoutDimension.absolute(isPhone ? 280 : 250)
      )
      let itemCount = isPhone ? 1 : 3
      let item = NSCollectionLayoutItem(layoutSize: size)
      let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: itemCount)
      let section = NSCollectionLayoutSection(group: group)
      section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
      section.interGroupSpacing = 10
      // Supplementary header view setup
      let headerFooterSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .estimated(20)
      )
      let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
        layoutSize: headerFooterSize,
        elementKind: UICollectionView.elementKindSectionHeader,
        alignment: .top
      )
      section.boundarySupplementaryItems = [sectionHeader]
      return section
    })
  }
  
  override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { context in
      self.collectionView.collectionViewLayout.invalidateLayout()
    }, completion: nil)
  }
}

// MARK: - SFSafariViewControllerDelegate Implementation
extension VideosViewController: SFSafariViewControllerDelegate {
  func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
    controller.dismiss(animated: true, completion: nil)
  }
}
4. VideoCollectionViewCell.swift
import UIKit

class VideoCollectionViewCell: UICollectionViewCell {
  @IBOutlet weak var thumbnailView: UIImageView!
  @IBOutlet weak var titleLabel: UILabel!
  @IBOutlet weak var subtitleLabel: UILabel!
  
  var video: Video? {
    didSet {
      thumbnailView.image = video?.thumbnail
      titleLabel.text = video?.title
      subtitleLabel.text = "\(video?.lessonCount ?? 0) lessons"
    }
  }
}
5. Video.swift
import UIKit

class Video: Hashable {
  var id = UUID()
  var title: String
  var thumbnail: UIImage?
  var lessonCount: Int
  var link: URL?
  
  init(title: String, thumbnail: UIImage? = nil, lessonCount: Int, link: URL?) {
    self.title = title
    self.thumbnail = thumbnail
    self.lessonCount = lessonCount
    self.link = link
  }
  // 1
  func hash(into hasher: inout Hasher) {
    // 2
    hasher.combine(id)
  }
  // 3
  static func == (lhs: Video, rhs: Video) -> Bool {
    lhs.id == rhs.id
  }
}
6. Section.swift
import UIKit

// 1
class Section: Hashable {
  var id = UUID()
  // 2
  var title: String
  // 3
  var videos: [Video]
  
  init(title: String, videos: [Video]) {
    self.title = title
    self.videos = videos
  }
  // 4
  func hash(into hasher: inout Hasher) {
    hasher.combine(id)
  }
  
  static func == (lhs: Section, rhs: Section) -> Bool {
    lhs.id == rhs.id
  }
}

extension Section {
  static var allSections: [Section] = [
    Section(title: "SwiftUI", videos: [
      Video(
        title: "SwiftUI",
        thumbnail: UIImage(named: "swiftui"),
        lessonCount: 37,
        link: URL(string: "https://www.raywenderlich.com/4001741-swiftui")
      )
    ]),
    Section(title: "UIKit", videos: [
      Video(
        title: "Demystifying Views in iOS",
        thumbnail: UIImage(named: "views"),
        lessonCount: 26,
        link:
        URL(string:
          "https://www.raywenderlich.com/4518-demystifying-views-in-ios")
      ),
      Video(
        title: "Reproducing Popular iOS Controls",
        thumbnail: UIImage(named: "controls"),
        lessonCount: 31,
        link: URL(string: """
          https://www.raywenderlich.com/5298-reproducing
          -popular-ios-controls
          """)
      )
    ]),
    Section(title: "Frameworks", videos: [
      Video(
        title: "Fastlane for iOS",
        thumbnail: UIImage(named: "fastlane"),
        lessonCount: 44,
        link: URL(string:
          "https://www.raywenderlich.com/1259223-fastlane-for-ios")
      ),
      Video(
        title: "Beginning RxSwift",
        thumbnail: UIImage(named: "rxswift"),
        lessonCount: 39,
        link: URL(string:
          "https://www.raywenderlich.com/4743-beginning-rxswift")
      )
    ]),
    Section(title: "Miscellaneous", videos: [
      Video(
        title: "Data Structures & Algorithms in Swift",
        thumbnail: UIImage(named: "datastructures"),
        lessonCount: 29,
        link: URL(string: """
          https://www.raywenderlich.com/977854-data-structures
          -algorithms-in-swift
        """)
      ),
      Video(
        title: "Beginning ARKit",
        thumbnail: UIImage(named: "arkit"),
        lessonCount: 46,
        link: URL(string:
          "https://www.raywenderlich.com/737368-beginning-arkit")
      ),
      Video(
        title: "Machine Learning in iOS",
        thumbnail: UIImage(named: "machinelearning"),
        lessonCount: 15,
        link: URL(string: """
          https://www.raywenderlich.com/1320561-machine-learning-in-ios
        """)
      ),
      Video(
        title: "Push Notifications",
        thumbnail: UIImage(named: "notifications"),
        lessonCount: 33,
        link: URL(string:
          "https://www.raywenderlich.com/1258151-push-notifications")
      ),
    ])
  ]
}
7. SectionHeaderReusableView.swift
import UIKit

class SectionHeaderReusableView: UICollectionReusableView {
  static var reuseIdentifier: String {
    return String(describing: SectionHeaderReusableView.self)
  }
  
  lazy var titleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(
      ofSize: UIFont.preferredFont(forTextStyle: .title1).pointSize,
      weight: .bold)
    label.adjustsFontForContentSizeCategory = true
    label.textColor = .label
    label.textAlignment = .left
    label.numberOfLines = 1
    label.setContentCompressionResistancePriority(
      .defaultHigh, for: .horizontal)
    return label
  }()
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    backgroundColor = .systemBackground
    addSubview(titleLabel)
    if UIDevice.current.userInterfaceIdiom == .pad {
      NSLayoutConstraint.activate([
        titleLabel.leadingAnchor.constraint(
          equalTo: leadingAnchor,
          constant: 5),
        titleLabel.trailingAnchor.constraint(
          lessThanOrEqualTo: trailingAnchor,
          constant: -5)])
    } else {
      NSLayoutConstraint.activate([
        titleLabel.leadingAnchor.constraint(
          equalTo: readableContentGuide.leadingAnchor),
        titleLabel.trailingAnchor.constraint(
          lessThanOrEqualTo: readableContentGuide.trailingAnchor)
      ])
    }
    NSLayoutConstraint.activate([
      titleLabel.topAnchor.constraint(
        equalTo: topAnchor,
        constant: 10),
      titleLabel.bottomAnchor.constraint(
        equalTo: bottomAnchor,
        constant: -10)
    ])
  }
  
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}
8. UIView+IBInspectable.swift
import UIKit

extension UIView {
  @IBInspectable
  var cornerRadius: CGFloat {
    get {
      return layer.cornerRadius
    }
    set {
      layer.cornerRadius = newValue
    }
  }
  
  @IBInspectable
  var shadowOpacity: Float {
    get {
      return layer.shadowOpacity
    }
    set {
      layer.shadowOpacity = newValue
    }
  }
  
  @IBInspectable
  var shadowOffset: CGSize {
    get {
      return layer.shadowOffset
    }
    set {
      layer.shadowOffset = newValue
    }
  }
  
  @IBInspectable
  var shadowColor: CGColor? {
    get {
      return layer.shadowColor
    }
    set {
      layer.shadowColor = newValue
    }
  }
  
  @IBInspectable
  var shadowRadius: CGFloat {
    get {
      return layer.shadowRadius
    }
    set {
      layer.shadowRadius = newValue
    }
  }
}

后记

本篇主要讲述了UICollectionView UICollectionViewDiffableDataSource的使用,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二))