v
源码
1. Swift
首先看下工程组织结构
看下sb中的内容
下面就是源码了
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 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦