为了账号安全,请及时绑定邮箱和手机立即绑定

UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)

标签:
Java

v

源码

1. Swift

首先看下工程组织结构

webp

看下sb中的内容

webp

下面就是源码了

1. Flickr.swift
import UIKitlet apiKey = "a35c883530bbe53c6db409d2a493991e"class Flickr {
  enum Error: Swift.Error {    case unknownAPIResponse    case generic
  }
  
  func searchFlickr(for searchTerm: String, completion: @escaping (Result<FlickrSearchResults>) -> Void) {
    guard let searchURL = flickrSearchURL(for: searchTerm) else {
      completion(Result.error(Error.unknownAPIResponse))      return
    }    
    let searchRequest = URLRequest(url: searchURL)
    
    URLSession.shared.dataTask(with: searchRequest) { (data, response, error) in
      if let error = error {
        DispatchQueue.main.async {
          completion(Result.error(error))
        }        return
      }
      
      guard        let _ = response as? HTTPURLResponse,        let data = data        else {
          DispatchQueue.main.async {
            completion(Result.error(Error.unknownAPIResponse))
          }          return
      }      
      do {
        guard          let resultsDictionary = try JSONSerialization.jsonObject(with: data) as? [String: AnyObject],          let stat = resultsDictionary["stat"] as? String
          else {
            DispatchQueue.main.async {
              completion(Result.error(Error.unknownAPIResponse))
            }            return
        }        
        switch (stat) {        case "ok":
          print("Results processed OK")        case "fail":
          DispatchQueue.main.async {
            completion(Result.error(Error.generic))
          }          return
        default:
          DispatchQueue.main.async {
            completion(Result.error(Error.unknownAPIResponse))
          }          return
        }
        
        guard          let photosContainer = resultsDictionary["photos"] as? [String: AnyObject],          let photosReceived = photosContainer["photo"] as? [[String: AnyObject]]          else {
            DispatchQueue.main.async {
              completion(Result.error(Error.unknownAPIResponse))
            }            return
        }        
        let flickrPhotos: [FlickrPhoto] = photosReceived.compactMap { photoObject in
          guard            let photoID = photoObject["id"] as? String,            let farm = photoObject["farm"] as? Int ,            let server = photoObject["server"] as? String ,            let secret = photoObject["secret"] as? String
            else {              return nil
          }          
          let flickrPhoto = FlickrPhoto(photoID: photoID, farm: farm, server: server, secret: secret)
          
          guard            let url = flickrPhoto.flickrImageURL(),            let imageData = try? Data(contentsOf: url as URL)            else {              return nil
          }          
          if let image = UIImage(data: imageData) {
            flickrPhoto.thumbnail = image            return flickrPhoto
          } else {            return nil
          }
        }        
        let searchResults = FlickrSearchResults(searchTerm: searchTerm, searchResults: flickrPhotos)
        DispatchQueue.main.async {
          completion(Result.results(searchResults))
        }
      } catch {
        completion(Result.error(error))        return
      }
    }.resume()
  }
  
  private func flickrSearchURL(for searchTerm:String) -> URL? {
    guard let escapedTerm = searchTerm.addingPercentEncoding(withAllowedCharacters: CharacterSet.alphanumerics) else {      return nil
    }    
    let URLString = "https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=\(apiKey)&text=\(escapedTerm)&per_page=20&format=json&nojsoncallback=1"
    return URL(string:URLString)
  }
}
2. FlickrPhoto.swift
import UIKitclass FlickrPhoto: Equatable {
  var thumbnail: UIImage?
  var largeImage: UIImage?
  let photoID: String
  let farm: Int
  let server: String
  let secret: String
  
  init (photoID: String, farm: Int, server: String, secret: String) {    self.photoID = photoID    self.farm = farm    self.server = server    self.secret = secret
  }
  
  func flickrImageURL(_ size: String = "m") -> URL? {    if let url =  URL(string: "https://farm\(farm).staticflickr.com/\(server)/\(photoID)_\(secret)_\(size).jpg") {      return url
    }    return nil
  }  
  enum Error: Swift.Error {    case invalidURL    case noData
  }
  
  func loadLargeImage(_ completion: @escaping (Result<FlickrPhoto>) -> Void) {
    guard let loadURL = flickrImageURL("b") else {
      DispatchQueue.main.async {
        completion(Result.error(Error.invalidURL))
      }      return
    }
    
    let loadRequest = URLRequest(url:loadURL)
    
    URLSession.shared.dataTask(with: loadRequest) { (data, response, error) in
      if let error = error {
        DispatchQueue.main.async {
          completion(Result.error(error))
        }        return
      }
      
      guard let data = data else {
        DispatchQueue.main.async {
          completion(Result.error(Error.noData))
        }        return
      }
      
      let returnedImage = UIImage(data: data)      self.largeImage = returnedImage
      DispatchQueue.main.async {
        completion(Result.results(self))
      }
    }.resume()
  }
  
  func sizeToFillWidth(of size:CGSize) -> CGSize {
    guard let thumbnail = thumbnail else {      return size
    }
    
    let imageSize = thumbnail.size
    var returnSize = size
    
    let aspectRatio = imageSize.width / imageSize.height
    
    returnSize.height = returnSize.width / aspectRatio    
    if returnSize.height > size.height {
      returnSize.height = size.height
      returnSize.width = size.height * aspectRatio
    }    
    return returnSize
  }  
  static func ==(lhs: FlickrPhoto, rhs: FlickrPhoto) -> Bool {    return lhs.photoID == rhs.photoID
  }
}
3. FlickrSearchResults.swift
import Foundation

struct FlickrSearchResults {  let searchTerm: String
  var searchResults: [FlickrPhoto]
}
4. Result.swift
import Foundationenum Result<ResultType> {  case results(ResultType)
  case error(Error)}
5. AppDelegate.swift
import UIKitlet themeColor = UIColor(red: 0.01, green: 0.41, blue: 0.22, alpha: 1.0)

@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    window?.tintColor = themeColor    return true
  }
}
6. FlickrPhotosViewController.swift
import UIKitfinal class FlickrPhotosViewController: UICollectionViewController {  // MARK: - Properties
  private let reuseIdentifier = "FlickrCell"
  private let sectionInsets = UIEdgeInsets(top: 50.0, left: 20.0, bottom: 50.0, right: 20.0)
  private var searches: [FlickrSearchResults] = []
  private let flickr = Flickr()
  private let itemsPerRow: CGFloat = 3
  private var selectedPhotos: [FlickrPhoto] = []
  private let shareLabel = UILabel()  
  // 1
  var largePhotoIndexPath: IndexPath? {
    didSet {      // 2
      var indexPaths: [IndexPath] = []      if let largePhotoIndexPath = largePhotoIndexPath {
        indexPaths.append(largePhotoIndexPath)
      }      
      if let oldValue = oldValue {
        indexPaths.append(oldValue)
      }      // 3
      collectionView.performBatchUpdates({        self.collectionView.reloadItems(at: indexPaths)
      }) { _ in
        // 4
        if let largePhotoIndexPath = self.largePhotoIndexPath {          self.collectionView.scrollToItem(at: largePhotoIndexPath,
                                           at: .centeredVertically,
                                           animated: true)
        }
      }
    }
  }
  
  var sharing: Bool = false {
    didSet {      // 1
      collectionView.allowsMultipleSelection = sharing      
      // 2
      collectionView.selectItem(at: nil, animated: true, scrollPosition: [])
      selectedPhotos.removeAll()
      
      guard let shareButton = self.navigationItem.rightBarButtonItems?.first else {        return
      }      
      // 3
      guard sharing else {
        navigationItem.setRightBarButton(shareButton, animated: true)        return
      }      
      // 4
      if largePhotoIndexPath != nil {
        largePhotoIndexPath = nil
      }      
      // 5
      updateSharedPhotoCountLabel()      
      // 6
      let sharingItem = UIBarButtonItem(customView: shareLabel)
      let items: [UIBarButtonItem] = [
        shareButton,
        sharingItem
      ]
      
      navigationItem.setRightBarButtonItems(items, animated: true)
    }
  }
  
  override func viewDidLoad() {    super.viewDidLoad()
    collectionView.dragInteractionEnabled = true
    collectionView.dragDelegate = self
    collectionView.dropDelegate = self
  }
    
  @IBAction func share(_ sender: UIBarButtonItem) {
    guard !searches.isEmpty else {      return
    }
    
    guard !selectedPhotos.isEmpty else {
      sharing.toggle()      return
    }
    
    guard sharing else {      return
    }
    
    let images: [UIImage] = selectedPhotos.compactMap { photo in
      if let thumbnail = photo.thumbnail {        return thumbnail
      }      
      return nil
    }
    
    guard !images.isEmpty else {      return
    }
    
    let shareController = UIActivityViewController(activityItems: images,
                                                   applicationActivities: nil)
    shareController.completionWithItemsHandler = { _, _, _, _ in
      self.sharing = false
      self.selectedPhotos.removeAll()      self.updateSharedPhotoCountLabel()
    }
    
    shareController.popoverPresentationController?.barButtonItem = sender
    shareController.popoverPresentationController?.permittedArrowDirections = .any
    present(shareController,
            animated: true,
            completion: nil)
  }
}// MARK: - Privateprivate extension FlickrPhotosViewController {
  func photo(for indexPath: IndexPath) -> FlickrPhoto {    return searches[indexPath.section].searchResults[indexPath.row]
  }
  
  func removePhoto(at indexPath: IndexPath) {
    searches[indexPath.section].searchResults.remove(at: indexPath.row)
  }
  
  func insertPhoto(_ flickrPhoto: FlickrPhoto, at indexPath: IndexPath) {
    searches[indexPath.section].searchResults.insert(flickrPhoto, at: indexPath.row)
  }
  
  func performLargeImageFetch(for indexPath: IndexPath, flickrPhoto: FlickrPhoto) {    // 1
    guard let cell = self.collectionView.cellForItem(at: indexPath) as? FlickrPhotoCell else {      return
    }    
    // 2
    cell.activityIndicator.startAnimating()    
    // 3
    flickrPhoto.loadLargeImage { [weak self] result in
      // 4
      guard let self = self else {        return
      }      
      // 5
      switch result {      // 6
      case .results(let photo):        if indexPath == self.largePhotoIndexPath {
          cell.imageView.image = photo.largeImage
        }      case .error(_):        return
      }
    }
  }
  
  func updateSharedPhotoCountLabel() {    if sharing {
      shareLabel.text = "\(selectedPhotos.count) photos selected"
    } else {
      shareLabel.text = ""
    }
    
    shareLabel.textColor = themeColor    
    UIView.animate(withDuration: 0.3) {      self.shareLabel.sizeToFit()
    }
  }
}// MARK: - UITextFieldDelegateextension FlickrPhotosViewController: UITextFieldDelegate {
  func textFieldShouldReturn(_ textField: UITextField) -> Bool {    // 1
    let activityIndicator = UIActivityIndicatorView(style: .gray)
    textField.addSubview(activityIndicator)
    activityIndicator.frame = textField.bounds
    activityIndicator.startAnimating()
    
    flickr.searchFlickr(for: textField.text!) { searchResults in
      activityIndicator.removeFromSuperview()      
      switch searchResults {      case .error(let error):
        print("Error Searching: \(error)")      case .results(let results):
        print("Found \(results.searchResults.count) matching \(results.searchTerm)")        self.searches.insert(results, at: 0)        self.collectionView?.reloadData()
      }
    }
    
    textField.text = nil
    textField.resignFirstResponder()    return true
  }
}// MARK: - UICollectionViewDataSourceextension FlickrPhotosViewController {
  override func numberOfSections(in collectionView: UICollectionView) -> Int {    return searches.count
  }
  
  override func collectionView(_ collectionView: UICollectionView,
                               numberOfItemsInSection section: Int) -> Int {    return searches[section].searchResults.count
  }
  
  override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(
      withReuseIdentifier: reuseIdentifier, for: indexPath) as? FlickrPhotoCell      else {
        preconditionFailure("Invalid cell type")
    }
    
    let flickrPhoto = photo(for: indexPath)    
    // 1
    cell.activityIndicator.stopAnimating()    
    // 2
    guard indexPath == largePhotoIndexPath else {
      cell.imageView.image = flickrPhoto.thumbnail      return cell
    }    
    // 3
    guard flickrPhoto.largeImage == nil else {
      cell.imageView.image = flickrPhoto.largeImage      return cell
    }    
    // 4
    cell.imageView.image = flickrPhoto.thumbnail    
    // 5
    performLargeImageFetch(for: indexPath, flickrPhoto: flickrPhoto)    
    return cell
  }
  
  override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {    // 1
    switch kind {    // 2
    case UICollectionView.elementKindSectionHeader:      // 3
      guard let headerView = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: "\(FlickrPhotoHeaderView.self)",        for: indexPath) as? FlickrPhotoHeaderView        else {
          fatalError("Invalid view type")
      }
      
      let searchTerm = searches[indexPath.section].searchTerm
      headerView.label.text = searchTerm      return headerView    default:      // 4
      assert(false, "Invalid element type")
    }
  }
}// MARK: - UICollectionViewDelegateFlowLayoutextension FlickrPhotosViewController: UICollectionViewDelegateFlowLayout {
  func collectionView(_ collectionView: UICollectionView,
                      layout collectionViewLayout: UICollectionViewLayout,
                      sizeForItemAt indexPath: IndexPath) -> CGSize {    if indexPath == largePhotoIndexPath {
      let flickrPhoto = photo(for: indexPath)
      var size = collectionView.bounds.size
      size.height -= (sectionInsets.top + sectionInsets.right)
      size.width -= (sectionInsets.left + sectionInsets.right)      return flickrPhoto.sizeToFillWidth(of: size)
    }
    
    let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
    let availableWidth = view.frame.width - paddingSpace
    let widthPerItem = availableWidth / itemsPerRow    
    return CGSize(width: widthPerItem, height: widthPerItem)
  }
  
  func collectionView(_ collectionView: UICollectionView,
                      layout collectionViewLayout: UICollectionViewLayout,
                      insetForSectionAt section: Int) -> UIEdgeInsets {    return sectionInsets
  }
  
  func collectionView(_ collectionView: UICollectionView,
                      layout collectionViewLayout: UICollectionViewLayout,
                      minimumLineSpacingForSectionAt section: Int) -> CGFloat {    return sectionInsets.left
  }
}// MARK: - UICollectionViewDelegateextension FlickrPhotosViewController {
  override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
    guard !sharing else {      return true
    }    
    if largePhotoIndexPath == indexPath {
      largePhotoIndexPath = nil
    } else {
      largePhotoIndexPath = indexPath
    }    
    return false
  }
  
  override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    guard sharing else {      return
    }
    
    let flickrPhoto = photo(for: indexPath)
    selectedPhotos.append(flickrPhoto)
    updateSharedPhotoCountLabel()
  }
  
  override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
    guard sharing else {      return
    }
    
    let flickrPhoto = photo(for: indexPath)    if let index = selectedPhotos.firstIndex(of: flickrPhoto) {
      selectedPhotos.remove(at: index)
      updateSharedPhotoCountLabel()
    }
  }
}// MARK: - UICollectionViewDragDelegateextension FlickrPhotosViewController: UICollectionViewDragDelegate {
  func collectionView(_ collectionView: UICollectionView,
                      itemsForBeginning session: UIDragSession,
                      at indexPath: IndexPath) -> [UIDragItem] {
    let flickrPhoto = photo(for: indexPath)
    guard let thumbnail = flickrPhoto.thumbnail else {      return []
    }
    let item = NSItemProvider(object: thumbnail)
    let dragItem = UIDragItem(itemProvider: item)    return [dragItem]
  }
}// MARK: - UICollectionViewDropDelegateextension FlickrPhotosViewController: UICollectionViewDropDelegate {
  func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {    return true
  }
  
  func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {    // 1
    guard let destinationIndexPath = coordinator.destinationIndexPath else {      return
    }    
    // 2
    coordinator.items.forEach { dropItem in
      guard let sourceIndexPath = dropItem.sourceIndexPath else {        return
      }      
      // 3
      collectionView.performBatchUpdates({
        let image = photo(for: sourceIndexPath)
        removePhoto(at: sourceIndexPath)
        insertPhoto(image, at: destinationIndexPath)
        collectionView.deleteItems(at: [sourceIndexPath])
        collectionView.insertItems(at: [destinationIndexPath])
      }, completion: { _ in
        // 4
        coordinator.drop(dropItem.dragItem,
                         toItemAt: destinationIndexPath)
      })
    }
  }
  
  func collectionView(_ collectionView: UICollectionView,
                      dropSessionDidUpdate session: UIDropSession,
                      withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {    return UICollectionViewDropProposal(operation: .move,
                                        intent: .insertAtDestinationIndexPath)
  }
}
7. FlickrPhotoCell.swift
import UIKitclass FlickrPhotoCell: UICollectionViewCell {
  @IBOutlet weak var imageView: UIImageView!
  @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
  
  override var isSelected: Bool {
    didSet {
      imageView.layer.borderWidth = isSelected ? 10 : 0
    }
  }
  
  override func awakeFromNib() {    super.awakeFromNib()
    imageView.layer.borderColor = themeColor.cgColor
    isSelected = false
  }
}
8. FlickrPhotoHeaderView.swift
import UIKitclass FlickrPhotoHeaderView: UICollectionReusableView {
  @IBOutlet weak var label: UILabel!
}

后记

本篇主要讲述了UICollectionView的重用、选择和重排序,感兴趣的给个赞或者关注~~~



作者:刀客传奇
链接:https://www.jianshu.com/p/6c20e218e16d


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消