美文网首页
UIKit框架(十) —— UICollectionView的数

UIKit框架(十) —— UICollectionView的数

作者: 刀客传奇 | 来源:发表于2018-11-28 11:54 被阅读76次

    版本记录

    版本号 时间
    V1.0 2018.11.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的数据异步预加载(一)

    源码

    1. Swift

    首先看一下代码组织结构

    接着看一下sb中的内容

    下面就是源码了

    1. EmojiViewController.swift
    
    import UIKit
    
    class EmojiViewController: UICollectionViewController {
      let dataStore = DataStore()
      let loadingQueue = OperationQueue()
      var loadingOperations: [IndexPath: DataLoadOperation] = [:]
      var ratingOverlayView: RatingOverlayView?
      var previewInteraction: UIPreviewInteraction?
    
      override func viewDidLoad() {
        super.viewDidLoad()
        collectionView?.prefetchDataSource = self
        
        ratingOverlayView = RatingOverlayView(frame: view.bounds)
        guard let ratingOverlayView = ratingOverlayView else { return }
        
        view.addSubview(ratingOverlayView)
        view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
          ratingOverlayView.leftAnchor.constraint(equalTo: view.leftAnchor),
          ratingOverlayView.rightAnchor.constraint(equalTo: view.rightAnchor),
          ratingOverlayView.topAnchor.constraint(equalTo: view.topAnchor),
          ratingOverlayView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
          ])
        ratingOverlayView.isUserInteractionEnabled = false
        
        if let collectionView = collectionView {
          previewInteraction = UIPreviewInteraction(view: collectionView)
          previewInteraction?.delegate = self
        }
      }
    }
    
    // MARK: - UICollectionViewDataSource
    extension EmojiViewController {
      override func collectionView(_ collectionView: UICollectionView,
                                   numberOfItemsInSection section: Int) -> Int {
        return dataStore.numberOfEmoji
      }
      
      override func collectionView(_ collectionView: UICollectionView,
          cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "EmojiCell", for: indexPath)
        
        if let cell = cell as? EmojiViewCell {
          cell.updateAppearanceFor(.none, animated: false)
        }
        return cell
      }
    }
    
    // MARK: - UICollectionViewDelegate
    extension EmojiViewController {
      override func collectionView(_ collectionView: UICollectionView,
          willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        guard let cell = cell as? EmojiViewCell else { return }
        
        // How should the operation update the cell once the data has been loaded?
        let updateCellClosure: (EmojiRating?) -> () = { [weak self] emojiRating in
          guard let self = self else {
            return
          }
          cell.updateAppearanceFor(emojiRating, animated: true)
          self.loadingOperations.removeValue(forKey: indexPath)
        }
        
        // Try to find an existing data loader
        if let dataLoader = loadingOperations[indexPath] {
          // Has the data already been loaded?
          if let emojiRating = dataLoader.emojiRating {
            cell.updateAppearanceFor(emojiRating, animated: false)
            loadingOperations.removeValue(forKey: indexPath)
          } else {
            // No data loaded yet, so add the completion closure to update the cell
            // once the data arrives
            dataLoader.loadingCompleteHandler = updateCellClosure
          }
        } else {
          // Need to create a data loaded for this index path
          if let dataLoader = dataStore.loadEmojiRating(at: indexPath.item) {
            // Provide the completion closure, and kick off the loading operation
            dataLoader.loadingCompleteHandler = updateCellClosure
            loadingQueue.addOperation(dataLoader)
            loadingOperations[indexPath] = dataLoader
          }
        }
      }
      
      override func collectionView(_ collectionView: UICollectionView,
          didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        // If there's a data loader for this index path we don't need it any more.
        // Cancel and dispose
        if let dataLoader = loadingOperations[indexPath] {
          dataLoader.cancel()
          loadingOperations.removeValue(forKey: indexPath)
        }
      }
    }
    
    // MARK: - UICollectionViewDataSourcePrefetching
    extension EmojiViewController: UICollectionViewDataSourcePrefetching {
      func collectionView(_ collectionView: UICollectionView,
          prefetchItemsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
          if let _ = loadingOperations[indexPath] {
            continue
          }
          if let dataLoader = dataStore.loadEmojiRating(at: indexPath.item) {
            loadingQueue.addOperation(dataLoader)
            loadingOperations[indexPath] = dataLoader
          }
        }
      }
      
      func collectionView(_ collectionView: UICollectionView,
          cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
          if let dataLoader = loadingOperations[indexPath] {
            dataLoader.cancel()
            loadingOperations.removeValue(forKey: indexPath)
          }
        }
      }
    }
    
    // MARK: - UIPreviewInteractionDelegate
    extension EmojiViewController: UIPreviewInteractionDelegate {
      func previewInteractionShouldBegin(_ previewInteraction: UIPreviewInteraction) -> Bool {
        if let indexPath = collectionView?.indexPathForItem(at: previewInteraction.location(in: collectionView!)),
          let cell = collectionView?.cellForItem(at: indexPath) {
          ratingOverlayView?.beginPreview(forView: cell)
          collectionView?.isScrollEnabled = false
          return true
        } else {
          return false
        }
      }
      
      func previewInteractionDidCancel(_ previewInteraction: UIPreviewInteraction) {
        ratingOverlayView?.endInteraction()
        collectionView?.isScrollEnabled = true
      }
      
      func previewInteraction(_ previewInteraction: UIPreviewInteraction,
          didUpdatePreviewTransition transitionProgress: CGFloat, ended: Bool) {
        ratingOverlayView?.updateAppearance(forPreviewProgress: transitionProgress)
      }
      
      func previewInteraction(_ previewInteraction: UIPreviewInteraction,
          didUpdateCommitTransition transitionProgress: CGFloat, ended: Bool) {
        let hitPoint = previewInteraction.location(in: ratingOverlayView!)
        if ended {
          let updatedRating = ratingOverlayView?.completeCommit(at: hitPoint)
          if let indexPath = collectionView?.indexPathForItem(at: previewInteraction.location(in: collectionView!)),
            let cell = collectionView?.cellForItem(at: indexPath) as? EmojiViewCell,
            let oldEmojiRating = cell.emojiRating {
            let newEmojiRating = EmojiRating(emoji: oldEmojiRating.emoji, rating: updatedRating!)
            dataStore.update(emojiRating: newEmojiRating)
            cell.updateAppearanceFor(newEmojiRating)
            collectionView?.isScrollEnabled = true
          }
        } else {
          ratingOverlayView?.updateAppearance(forCommitProgress: transitionProgress, touchLocation: hitPoint)
        }
      }
    }
    
    2. RatingOverlayView.swift
    
    import UIKit
    
    class RatingOverlayView: UIView {
      var blurView: UIVisualEffectView?
      var animator: UIViewPropertyAnimator?
      private var overlaySnapshot: UIView?
      private var ratingStackView: UIStackView?
      
      func updateAppearance(forPreviewProgress progress: CGFloat) {
        animator?.fractionComplete = progress
      }
      
      func updateAppearance(forCommitProgress progress: CGFloat, touchLocation: CGPoint) {
        guard let ratingStackView = ratingStackView else { return }
        // During the commit phase the user can select a rating based on touch location
        for subview in ratingStackView.arrangedSubviews {
          let translatedPoint = convert(touchLocation, to: subview)
          if subview.point(inside: translatedPoint, with: .none) {
            subview.backgroundColor = #colorLiteral(red: 0.501960814, green: 0.501960814, blue: 0.501960814, alpha: 1).withAlphaComponent(0.6)
          } else {
            subview.backgroundColor = #colorLiteral(red: 0.501960814, green: 0.501960814, blue: 0.501960814, alpha: 1).withAlphaComponent(0.2)
          }
        }
      }
      
      func completeCommit(at touchLocation: CGPoint) -> String {
        // At commit, find the selected rating and pass it back
        var selectedRating = ""
        
        guard let ratingStackView = ratingStackView else {
          return selectedRating
        }
        
        for subview in ratingStackView.arrangedSubviews where subview is UILabel {
          let subview = subview as! UILabel
          let translatedPoint = convert(touchLocation, to: subview)
          if subview.point(inside: translatedPoint, with: .none) {
            selectedRating = subview.text!
          }
        }
        
        // Tidy everything away
        endInteraction()
        
        return selectedRating
      }
      
      func beginPreview(forView view: UIView) {
        // Reset any previous animations / blurs
        animator?.stopAnimation(false)
        blurView?.removeFromSuperview()
        // Create the visual effect
        prepareBlurView()
        // Create and configure the snapshot of the view we are picking out
        overlaySnapshot?.removeFromSuperview()
        overlaySnapshot = view.snapshotView(afterScreenUpdates: false)
        if let overlaySnapshot = overlaySnapshot {
          blurView?.contentView.addSubview(overlaySnapshot)
          // Calculate the position (adjusted for scroll views)
          let adjustedCenter = view.superview?.convert(view.center, to: self)
          overlaySnapshot.center = adjustedCenter!
          // Create ratings labels
          prepareRatings(for: overlaySnapshot)
        }
        // Create the animator that'll track the preview progress
        animator = UIViewPropertyAnimator(duration: 0.3, curve: .linear) {
          // Specifying a blur type animates the blur radius
          self.blurView?.effect = UIBlurEffect(style: .regular)
          // Pull out the snapshot
          self.overlaySnapshot?.layer.shadowRadius = 8
          self.overlaySnapshot?.layer.shadowColor = #colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1).cgColor
          self.overlaySnapshot?.layer.shadowOpacity = 0.3
          // Fade the ratings in
          self.ratingStackView?.alpha = 1
        }
        animator?.addCompletion { position in
          // Remove the blur view when animation gets back to the beginning
          switch position {
          case .start:
            self.blurView?.removeFromSuperview()
          default:
            break
          }
        }
      }
      
      func endInteraction() {
        // Animate back to the beginning (no blur)
        animator?.isReversed = true
        animator?.startAnimation()
      }
      
      private func prepareBlurView() {
        // Create a visual effect view and make it completely fill self. Start with no
        // effect - will animate the blur in.
        blurView = UIVisualEffectView(effect: .none)
        if let blurView = blurView {
          addSubview(blurView)
          blurView.translatesAutoresizingMaskIntoConstraints = false
          NSLayoutConstraint.activate([
            blurView.leftAnchor.constraint(equalTo: leftAnchor),
            blurView.rightAnchor.constraint(equalTo: rightAnchor),
            blurView.topAnchor.constraint(equalTo: topAnchor),
            blurView.bottomAnchor.constraint(equalTo: bottomAnchor)
            ])
        }
      }
      
      private func prepareRatings(for view: UIView) {
        // Build the two ratings labels
        let 👍label = UILabel()
        👍label.text = "👍"
        👍label.font = UIFont.systemFont(ofSize: 50)
        👍label.textAlignment = .center
        👍label.backgroundColor = #colorLiteral(red: 0.501960814, green: 0.501960814, blue: 0.501960814, alpha: 1).withAlphaComponent(0.2)
        let 👎label = UILabel()
        👎label.text = "👎"
        👎label.font = UIFont.systemFont(ofSize: 50)
        👎label.textAlignment = .center
        👎label.backgroundColor = #colorLiteral(red: 0.501960814, green: 0.501960814, blue: 0.501960814, alpha: 1).withAlphaComponent(0.2)
        
        // Pop them in a stack view
        ratingStackView = UIStackView(arrangedSubviews: [👍label, 👎label])
        if let ratingStackView = ratingStackView {
          ratingStackView.axis = .vertical
          ratingStackView.alignment = .fill
          ratingStackView.distribution = .fillEqually
          // Ratings should completely cover the supplied view
          view.addSubview(ratingStackView)
          ratingStackView.translatesAutoresizingMaskIntoConstraints = false
          NSLayoutConstraint.activate([
            view.leftAnchor.constraint(equalTo: ratingStackView.leftAnchor),
            view.rightAnchor.constraint(equalTo: ratingStackView.rightAnchor),
            view.topAnchor.constraint(equalTo: ratingStackView.topAnchor),
            view.bottomAnchor.constraint(equalTo: ratingStackView.bottomAnchor)
            ])
          ratingStackView.alpha = 0
        }
      }
    }
    
    3. EmojiViewCell.swift
    
    import UIKit
    
    class EmojiViewCell: UICollectionViewCell {
      @IBOutlet weak var emojiLabel: UILabel!
      @IBOutlet weak var ratingLabel: UILabel!
      @IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
      
      var emojiRating: EmojiRating?
      
      override func prepareForReuse() {
        DispatchQueue.main.async {
          self.displayEmojiRating(.none)
        }
      }
      
      func updateAppearanceFor(_ emojiRating: EmojiRating?, animated: Bool = true) {
        DispatchQueue.main.async {
          if animated {
            UIView.animate(withDuration: 0.5) {
              self.displayEmojiRating(emojiRating)
            }
          } else {
            self.displayEmojiRating(emojiRating)
          }
        }
      }
      
      private func displayEmojiRating(_ emojiRating: EmojiRating?) {
        self.emojiRating = emojiRating
        if let emojiRating = emojiRating {
          emojiLabel?.text = emojiRating.emoji
          ratingLabel?.text = emojiRating.rating
          emojiLabel?.alpha = 1
          ratingLabel?.alpha = 1
          loadingIndicator?.alpha = 0
          loadingIndicator?.stopAnimating()
          backgroundColor = #colorLiteral(red: 0.9338415265, green: 0.9338632822, blue: 0.9338515401, alpha: 1)
          layer.cornerRadius = 10
        } else {
          emojiLabel?.alpha = 0
          ratingLabel?.alpha = 0
          loadingIndicator?.alpha = 1
          loadingIndicator?.startAnimating()
          backgroundColor = #colorLiteral(red: 0.7450980544, green: 0.1568627506, blue: 0.07450980693, alpha: 1)
          layer.cornerRadius = 10
        }
      }
    }
    
    4. DataStore.swift
    
    import Foundation
    
    let emoji = "🐍,👍,💄,🎏,🐠,🍔,🏩,🎈,🐷,👠,🐣,🐙,✈️,💅,⛑,👑,👛,🐝,🌂,🌻,🎼,🎧,🚧,📎,🍻".components(separatedBy: ",")
    
    
    class DataStore {
      private var emojiRatings = emoji.map { EmojiRating(emoji: $0, rating: "") }
      
      public var numberOfEmoji: Int {
        return emojiRatings.count
      }
      
      public func loadEmojiRating(at index: Int) -> DataLoadOperation? {
        if (0..<emojiRatings.count).contains(index) {
          return DataLoadOperation(emojiRatings[index])
        }
        return .none
      }
      
      public func update(emojiRating: EmojiRating) {
        if let index = emojiRatings.index(where: { $0.emoji == emojiRating.emoji }) {
          emojiRatings.replaceSubrange(index...index, with: [emojiRating])
        }
      }
    }
    
    
    class DataLoadOperation: Operation {
      var emojiRating: EmojiRating?
      var loadingCompleteHandler: ((EmojiRating) ->Void)?
      
      private let _emojiRating: EmojiRating
      
      init(_ emojiRating: EmojiRating) {
        _emojiRating = emojiRating
      }
      
      override func main() {
        if isCancelled { return }
        
        let randomDelayTime = Int.random(in: 500..<2000)
        usleep(useconds_t(randomDelayTime * 1000))
        
        if isCancelled { return }
        emojiRating = _emojiRating
        
        if let loadingCompleteHandler = loadingCompleteHandler {
          DispatchQueue.main.async {
            loadingCompleteHandler(self._emojiRating)
          }
        }
      }
    }
    
    5. EmojiRating.swift
    
    import Foundation
    
    struct EmojiRating {
      let emoji: String
      let rating: String
    }
    

    后记

    本篇主要讲述了UICollectionView的数据异步预加载,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

          本文标题:UIKit框架(十) —— UICollectionView的数

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