美文网首页
UIKit框架(五十四) —— 基于Flickr API的UIC

UIKit框架(五十四) —— 基于Flickr API的UIC

作者: 刀客传奇 | 来源:发表于2021-02-15 09:35 被阅读0次

版本记录

版本号 时间
V1.0 2021.02.15 星期一

前言

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的使用(一)
37. UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)
38. UIKit框架(三十八) —— 基于CollectionView转盘效果的实现(一)
39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)
40. UIKit框架(四十) —— iOS 13中UISearchController 和 UISearchBar的新更改(二)
41. UIKit框架(四十一) —— 使用协议构建自定义Collection(一)
42. UIKit框架(四十二) —— 使用协议构建自定义Collection(二)
43. UIKit框架(四十三) —— CALayer的简单实用示例(一)
44. UIKit框架(四十四) —— CALayer的简单实用示例(二)
45. UIKit框架(四十五) —— 支持DarkMode的简单示例(一)
46. UIKit框架(四十六) —— 支持DarkMode的简单示例(二)
47. UIKit框架(四十七) —— 自定义Calendar Control的简单示例(一)
48. UIKit框架(四十八) —— 自定义Calendar Control的简单示例(二)
49. UIKit框架(四十九) —— UIVisualEffectView原理和简单使用(一)
50. UIKit框架(五十) —— UIVisualEffectView原理和简单使用(二)
51. UIKit框架(五十一) —— 基于iOS14的UICollectionView List的创建(一)
52. UIKit框架(五十二) —— 基于iOS14的UICollectionView List的创建(二)
53. UIKit框架(五十三) —— 基于Flickr API的UICollectionView体验(一)

源码

1. Swift

首先看下工程组织结构

下面就是源码了

1. Flickr.swift
import UIKit

let apiKey = "02cc6fbff846e19cbc74e5b2e3d27bf4"

class Flickr {
  enum Error: Swift.Error {
    case unknownAPIResponse
    case generic
  }

  func searchFlickr(for searchTerm: String, completion: @escaping (Result<FlickrSearchResults, Swift.Error>) -> Void) {
    guard let searchURL = flickrSearchURL(for: searchTerm) else {
      completion(.failure(Error.unknownAPIResponse))
      return
    }

    URLSession.shared.dataTask(with: URLRequest(url: searchURL)) { data, response, error in
      if let error = error {
        completion(.failure(error))
        return
      }

      guard
        (response as? HTTPURLResponse) != nil,
        let data = data
      else {
        completion(.failure(Error.unknownAPIResponse))
        return
      }

      do {
        guard
          let resultsDictionary = try JSONSerialization.jsonObject(with: data) as? [String: AnyObject],
          let stat = resultsDictionary["stat"] as? String
        else {
          completion(.failure(Error.unknownAPIResponse))
          return
        }

        switch stat {
        case "ok":
          print("Results processed OK")
        case "fail":
          completion(.failure(Error.generic))
          return
        default:
          completion(.failure(Error.unknownAPIResponse))
          return
        }

        guard
          let photosContainer = resultsDictionary["photos"] as? [String: AnyObject],
          let photosReceived = photosContainer["photo"] as? [[String: AnyObject]]
        else {
          completion(.failure(Error.unknownAPIResponse))
          return
        }

        let flickrPhotos = self.getPhotos(photoData: photosReceived)
        let searchResults = FlickrSearchResults(searchTerm: searchTerm, searchResults: flickrPhotos)
        completion(.success(searchResults))
      } catch {
        completion(.failure(error))
        return
      }
    }
    .resume()
  }

  private func getPhotos(photoData: [[String: AnyObject]]) -> [FlickrPhoto] {
    let photos: [FlickrPhoto] = photoData.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
      }
    }
    return photos
  }

  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 UIKit

class 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? {
    return URL(string: "https://farm\(farm).staticflickr.com/\(server)/\(photoID)_\(secret)_\(size).jpg")
  }

  enum Error: Swift.Error {
    case invalidURL
    case noData
  }

  func loadLargeImage(_ completion: @escaping (Result<FlickrPhoto, Swift.Error>) -> Void) {
    guard let loadURL = flickrImageURL("b") else {
      DispatchQueue.main.async {
        completion(.failure(Error.invalidURL))
      }
      return
    }

    let loadRequest = URLRequest(url: loadURL)

    URLSession.shared.dataTask(with: loadRequest) { data, _, error in
      DispatchQueue.main.async {
        if let error = error {
          completion(.failure(error))
          return
        }

        guard let data = data else {
          completion(.failure(Error.noData))
          return
        }

        let returnedImage = UIImage(data: data)
        self.largeImage = returnedImage
        completion(.success(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
  let searchResults: [FlickrPhoto]
}
4. FlickrPhotosViewController.swift
import UIKit

final 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
}

// MARK: - Private
private extension FlickrPhotosViewController {
  func photo(for indexPath: IndexPath) -> FlickrPhoto {
    return searches[indexPath.section].searchResults[indexPath.row]
  }
}

// MARK: - Text Field Delegate
extension FlickrPhotosViewController: UITextFieldDelegate {
  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    guard
      let text = textField.text,
      !text.isEmpty
    else { return true }

    // 1
    let activityIndicator = UIActivityIndicatorView(style: .gray)
    textField.addSubview(activityIndicator)
    activityIndicator.frame = textField.bounds
    activityIndicator.startAnimating()

    flickr.searchFlickr(for: text) { searchResults in
      DispatchQueue.main.async {
        activityIndicator.removeFromSuperview()

        switch searchResults {
        case .failure(let error) :
          // 2
          print("Error Searching: \(error)")
        case .success(let results):
          // 3
          print("""
            Found \(results.searchResults.count) \
            matching \(results.searchTerm)
            """)
          self.searches.insert(results, at: 0)
          // 4
          self.collectionView?.reloadData()
        }
      }
    }

    textField.text = nil
    textField.resignFirstResponder()
    return true
  }
}

// MARK: - UICollectionViewDataSource
extension FlickrPhotosViewController {
  // 1
  override func numberOfSections(in collectionView: UICollectionView) -> Int {
    return searches.count
  }

  // 2
  override func collectionView(
    _ collectionView: UICollectionView,
    numberOfItemsInSection section: Int
  ) -> Int {
    return searches[section].searchResults.count
  }

  // 3
  override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    // 1
    let cell = collectionView.dequeueReusableCell(
      withReuseIdentifier: reuseIdentifier,
      for: indexPath
    ) as! FlickrPhotoCell
    // swiftlint:disable:previous force_cast

    // 2
    let flickrPhoto = photo(for: indexPath)
    cell.backgroundColor = .white
    // 3
    cell.imageView.image = flickrPhoto.thumbnail

    return cell
  }
}

// MARK: - Collection View Flow Layout Delegate
extension FlickrPhotosViewController: UICollectionViewDelegateFlowLayout {
  // 1
  func collectionView(
    _ collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    sizeForItemAt indexPath: IndexPath
  ) -> CGSize {
    // 2
    let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
    let availableWidth = view.frame.width - paddingSpace
    let widthPerItem = availableWidth / itemsPerRow

    return CGSize(width: widthPerItem, height: widthPerItem)
  }

  // 3
  func collectionView(
    _ collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    insetForSectionAt section: Int
  ) -> UIEdgeInsets {
    return sectionInsets
  }

  // 4
  func collectionView(
    _ collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    minimumLineSpacingForSectionAt section: Int
  ) -> CGFloat {
    return sectionInsets.left
  }
}
5. FlickrPhotoCell.swift
import UIKit

class FlickrPhotoCell: UICollectionViewCell {
  @IBOutlet weak var imageView: UIImageView!
}

后记

本篇主要讲述了基于Flickr APIUICollectionView体验,感兴趣的给个赞或者关注~~~

相关文章

网友评论

      本文标题:UIKit框架(五十四) —— 基于Flickr API的UIC

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