美文网首页
UIKit框架(三十七) —— UICollectionView

UIKit框架(三十七) —— UICollectionView

作者: 刀客传奇 | 来源:发表于2020-04-28 22:17 被阅读0次

    版本记录

    版本号 时间
    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<Section, Video>
      typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Video>
      
      // 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

          本文链接:https://www.haomeiwen.com/subject/aueiwhtx.html