UIKit框架(五十三) —— 基于Flickr API的UICollectionView体验(一)

版本记录

版本号 时间
V1.0 2021.02.14 星期日

前言

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的创建(二)

开始

首先看下主要内容:

通过使用Flickr API创建自己的基于网格的照片浏览应用程序来获得UICollectionView的经验。

接着看下写作环境:

Swift 5, iOS 14, Xcode 12

下面就是正文了。

iOS Photos应用程序具有一种通过多种布局显示照片的时尚方式。 您可以在网格视图中查看照片:

或者,您可以按堆栈stacks查看相册:

您甚至可以通过冷捏手势在两种布局之间切换。您可能会想,“哇,我希望我的应用程序做到这一点!”

它可以用UICollectionView做到这一点。 UICollectionView使添加自定义布局和布局过渡(如Photos中的布局)变得容易构建。

您不仅可以使用堆栈和网格(stacks and grids),还可以自定义集合视图(collection view)。您可以使用它们来制作圆形布局,封面流样式布局,Pulse新闻样式布局以及几乎所有您可以梦想的东西!

在本教程中,您将通过创建基于网格的照片浏览应用程序来获得UICollectionView的经验。在此过程中,您将学习如何:

  • 将自定义标头添加到集合视图(collection view)
  • 拖动它们即可轻松移动单元格。
  • 实现单个单元格选择以显示详细视图。
  • 实现多单元选择。

当您完成本教程时,您将准备在应用程序中使用这项惊人的技术!


Anatomy of a UICollectionView

首先,查看完成的项目。 UICollectionView包含几个关键组件,如下所示:

一对一看一下这些组件:

  • 1) UICollectionView:显示内容的主视图,类似于UITableView。像表视图一样(table view)collection viewUIScrollView子类。
  • 2) UICollectionViewCell:这类似于table view中的UITableViewCell。这些单元格构成视图的内容,是collection view的子视图。您可以以编程方式或在Interface Builder中创建单元。
  • 3) Supplementary Views:当您有更多信息要显示时,请使用补充视图(supplementary view),这些信息应该在collection view中,但不在单元格中。开发人员通常将它们用作页眉或页脚。

注意:集合视图Collection view也可以具有Decoration Views。使用装饰视图可添加不包含有用数据但可增强collection view外观的其他视图。背景图像或其他视觉装饰是装饰视图的很好示例。在本教程中,您将不使用装饰视图,因为它们需要您编写自定义布局类。

1. Using the UICollectionViewLayout

除上述可视组件外,集合视图collection view还包含一个布局对象,负责内容的大小,位置和其他属性。

布局对象是UICollectionViewLayout的子类。您可以在运行时交换布局。collection view甚至可以自动设置从一种布局切换到另一种的动画!

您可以将UICollectionViewLayout子类化以创建自己的自定义布局,但是Apple优雅地提供了一种基于流的基本布局,称为UICollectionViewFlowLayout。元素根据其大小一个接一个地布置,例如网格视图。您可以直接使用此布局类,也可以将其子类化以获得一些有趣的行为和视觉效果。

在本教程中,您将深入了解这些元素。现在,是时候动手做一个项目了!


Introducing FlickrSearch

在本教程中,您将创建一个很酷的照片浏览应用程序,名为FlickrSearch。 它使您可以在流行的照片共享网站Flickr上搜索术语,然后下载并在网格视图中显示所有匹配的照片,如您在上面的屏幕快照中所看到的。

查看下载项目文件。 打开FlickrSearch入门项目。

在内部,您会发现一个空的Main.storyboard以及一些与Flickr接口的文件。 进行下一步操作之前,请先看一下别的相关知识。


Starting Your Collection

打开Main.storyboard并将其拖到Collection View Controller中。 转到Editor ▸ Embed in ▸ Navigation Controller创建导航控制器,并自动将collection view controller设置为root

现在,故事板storyboard中的布局如下:

接下来,选择已安装的Navigation Controller,并将其设置为Attributes inspector中的初始视图控制器:

专注于collection view controller。 首先,选择里面的UICollectionView。 然后将背景颜色更改为白色:

注意:想知道Scroll Direction是什么? 此属性特定于UICollectionViewFlowLayout,并且默认为Vertical

垂直流布局是指布局类在视图顶部从左到右放置项目,直到到达视图的右边缘。 此时,它向下移动到下一行。 如果视图中包含太多元素,则用户可以垂直滚动以查看更多内容。

相反,水平流布局在视图的左边缘从上到下放置项目,直到到达底部边缘为止。 用户水平滚动以查看屏幕上不适合的项目。 在本教程中,您将坚持使用更常见的Vertical collection view

在集合视图中选择单个单元格。 使用属性检查器将Reuse Identifier设置为FlickrCell

table view可能也很熟悉此过程。 数据源使用此标识符出队或创建新的单元格。

接下来,您将添加一个搜索框。

1. Adding Search

text field拖到collection view上方导航栏的中心。 用户在此处输入搜索文本。 在“属性”检查器中,将搜索字段的Placeholder Text设置为SearchSearch后留一些空格,以方便填充该字段)和Return Key以进行Search

现在,将控件从text field拖动到collection view controller,然后选择delegate

接下来,将子类化UICollectionViewController

2. Subclassing Your UICollectionViewController

尽管UICollectionViewController做了很多事情,但是您通常需要创建一个子类,以便可以添加UIKit免费提供的其他行为。

转到File ▸ New ▸ File。 选择iOS ▸ Source ▸ Cocoa Touch Class,然后单击Next。 将新类命名为FlickrPhotosViewController,使其成为UICollectionViewController的子类。

该模板有很多代码。 了解其作用的最佳方法是从头开始。

打开FlickrPhotosViewController.swift并将文件中的代码替换为:

import UIKit

final class FlickrPhotosViewController: UICollectionViewController {
  // MARK: - Properties
  private let reuseIdentifier = "FlickrCell"
}

接下来,为section插入添加一个常量,稍后将在reuseIdentifier下方使用它:

private let sectionInsets = UIEdgeInsets(
  top: 50.0,
  left: 20.0,
  bottom: 50.0,
  right: 20.0)

在学习本教程的过程中,您将填补其余的空白。

返回Main.storyboard。 然后,选择collection view controller。 在Identity inspector中,将Class设置为FlickrPhotosViewController以匹配您的新类:

现在是时候获取Flickr照片以显示在collection view中了。


Fetching Flickr Photos

section的首要任务是说出该section title的速度快十倍。

好,开个玩笑。

Flickr是一种图像共享服务,具有供开发人员使用的公开访问且简单的API。 使用API,您可以搜索照片,添加照片,对照片发表评论等等。

您需要一个API key才能使用Flickr API。 如果您使用的是真实应用,请在Flickr网站Flickr’s website上注册一个。

但是,对于这样的测试项目,Flickr的示例密钥会经常轮换显示。 您无需注册即可使用密钥。

只需在Flickr’s website上执行任何搜索,然后从底部的URL复制API密钥即可。 从&api_key=&,它开始。将其粘贴到文本编辑器中以备后用。

例如,如果URL为:

http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=6593783efea8e7f6dfc6b70bc03d2afb&;format=rest&api_sig=f24f4e98063a9b8ecc8b522b238d5e2f

API key6593783efea8e7f6dfc6b70bc03d2afb

注意:如果您使用示例API key,请记住它几乎每天都在变化。如果您连续几天进行本教程,则可能必须经常获取一个新的API密钥。因此,如果您计划在此项目上花费几天,从Flickr获取API密钥可能会更容易。

解决了这些问题之后,就该探讨Flickr API类了。

1. Flickr API Classes

由于本教程是关于UICollectionView而不是Flickr API的,因此该项目包含抽象Flickr搜索代码的类。

Flickr支持包括两个类和一个结构:

  • FlickrSearchResults:一种结构体,用于包装搜索词和为该搜索找到的结果。
  • FlickrPhoto:有关从Flickr检索的照片的数据:其缩略图,图像和元数据信息(例如其ID)。还有一些构建Flickr URL的方法和一些大小计算。 FlickrSearchResults包含这些对象的数组。
  • Flickr:提供一个简单的基于块blockAPI来执行搜索并返回FlickrSearchResult

随意看一下代码。这很简单,可能会激发您在项目中使用Flickr

搜索Flickr之前,需要输入API密钥。打开Flickr.swift。将apiKey的值替换为之前获得的API密钥。

看起来像这样:

let apiKey = "hh7ef5ce0a54b6f5b8fbc36865eb5b32"

现在,是时候进行一些准备工作,然后再使用Flickr

2. Preparing Data Structures

在此项目中,您执行的每个搜索都会在集合视图中显示带有结果的新section,而不是替换前一部分。 换句话说,如果先搜索ninjas然后是pirates,您会在collection view中看到一部分ninjas section和一部分pirates section

要创建这些单独的sections,您需要一个数据结构以使每个部分的数据保持独立。 FlickrSearchResults数组可以解决这个问题。

打开FlickrPhotosViewController.swift。 在sectionInsets下添加以下属性:

private var searches: [FlickrSearchResults] = []
private let flickr = Flickr()

searches是一个数组,用于跟踪应用程序中进行的所有搜索。 flickr是对搜索您的对象的引用。

接下来,在文件底部添加以下扩展名:

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

photo(for :)是一种便捷的方法,可在您的collection view中获取与索引路径相关的特定照片。 您将大量访问特定索引路径的照片,并且不想重复代码。

您现在就可以开始Flickr搜索了!

3. Getting Good Results

当用户在输入查询后点击Search时,您希望执行搜索。 您已经将text field’s delegate outlet连接到了collection view控制器。 现在您可以对此进行处理。

打开FlickrPhotosViewController.swift。 添加扩展以保存text field代理方法:

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

以下是代码细分:

  • 1) 添加activity view后,可以使用Flickr包装器类异步搜索Flickr以查找与给定搜索词匹配的照片。 搜索完成后,您将使用FlickrPhoto对象的结果集和任何错误来调用完成块。
  • 2) 您将任何错误记录到控制台。 显然,在生产应用程序中,您希望向用户显示这些错误。
  • 3) 然后,您记录结果并将其添加到搜索数组的开头。
  • 4) 最后,刷新UI以显示新数据。 您可以使用reloadData(),它的工作原理与在table view中一样。

构建并运行。 在文本框中执行搜索。 您会在控制台中看到一条日志消息,指出搜索结果的数量,如下所示:

Found 20 matching bananas

请注意,Flickr帮助器将结果限制为20,以减少加载时间。

不幸的是,您在collection view中看不到任何照片!除非您实现相关的数据源和委托方法(类似于table view),否则collection view不会做很多事情。

您将在下一section中进行操作。


Feeding the UICollectionView

使用table view时,必须设置数据源和委托以提供数据以显示和处理事件,例如row selection

同样,在使用collection view时,还必须设置数据源和委托。它们的作用如下:

  • 数据源UICollectionViewDataSource返回有关collection view及其视图中的items数的信息。
  • 当事件发生时(例如,用户选择,突出显示或删除单元格时),UICollectionViewDelegate代理将获得另一个通知。

UICollectionViewFlowLayout还具有委托协议UICollectionViewDelegateFlowLayout。它使您可以调整布局的行为以配置诸如单元格间距,滚动方向和单元格大小之类的内容。您可以在此Apple documentation中了解更多信息。

在本部分中,您将在视图控制器上实现必需的UICollectionViewDataSourceUICollectionViewDelegateFlowLayout,因此将设置为与collection view一起使用。本教程不需要UICollectionViewDelegate,但是您可以在UICollectionView: Reusable Views Selection Reordering中使用它。

1. UICollectionViewDataSource

打开FlickrPhotosViewController.swift。将以下扩展名添加到UICollectionViewDataSource的文件中:

// 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 {
    let cell = collectionView.dequeueReusableCell(
      withReuseIdentifier: reuseIdentifier, 
      for: indexPath)
    cell.backgroundColor = .black
    // Configure the cell
    return cell
  }
}

这些方法非常简单:

  • 1) 每个section只能进行一次搜索,因此sections数就是searches次数。
  • 2) section中的items数是来自相关FlickrSearchsearchResults的计数。
  • 3) 这是一个返回空白单元格的占位符方法。 稍后再填充。 请注意,collection view要求您使用重用标识符注册单元格。 如果不这样做,将发生运行时错误。

构建并再次运行。 执行搜索。 您会看到20个新的单元格,尽管看起来很沉闷:

接下来,您将改进单元的布局。

2. UICollectionViewFlowLayoutDelegate

如前所述,每个collection view都有一个关联的布局。 由于该项目易于使用,并为您提供所需的网格视图样式,因此您将为其使用预制的流程布局。

仍在FlickrPhotosViewController.swift中,在flickr下添加以下常量:

private let itemsPerRow: CGFloat = 3

接下来,添加UICollectionViewDelegateFlowLayout以使视图控制器遵循流布局委托协议。 在文件底部添加此扩展名:

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

细分:

  • 1) collectionView(_:layout:sizeForItemAt :)告诉布局给定单元格的大小。
  • 2) 在这里,您可以计算出填充所占用的空间总量。 您将拥有n + 1个均匀大小的空格,其中n是该行中的items数。 您可以从左侧插图中获取空格大小。从视图的宽度中减去该宽度并除以一行中的项目数,即可得出每个项目的宽度。 然后,您将尺寸返回为正方形。
  • 3) collectionView(_:layout:insetForSectionAt :)返回单元格,headers and footers之间的间距。 常数存储值。
  • 4) 此方法控制布局中每行之间的间距。 您希望此间距与左侧和右侧的padding相匹配。

构建并再次运行。 执行搜索。

看哪! 黑色方块比以前更大!

有了这个基础架构,您现在就可以在屏幕上显示一些照片了!


Creating Custom UICollectionViewCells

关于UICollectionView的最好的事情之一就是像table views一样,很容易在Storyboard编辑器中直观地设置collection views。 您可以将collection views拖放到视图控制器中,并在Storyboard editor中设计单元的布局。 是时候看看它如何工作了。

打开Main.storyboard,然后选择collection view。 通过在Size inspector中将像元大小设置为200×200,为自己留出空间来工作:

注意:设置此大小不会影响您应用中的单元格,因为您实现了委托方法来为每个单元格指定大小,这会覆盖storyboard中设置的所有内容。

image view拖到单元格上并拉伸它,使其占据整个单元格。 在仍然选择图像视图的情况下,打开图钉菜单。 取消选中Constrain to margins并始终添加0点约束:

在仍然选择image view的情况下,在Attributes inspector中将其Mode更改为Aspect Fit,这样就不会以任何方式裁剪或拉伸图像:

现在该继承UICollectionViewCell了。

1. Subclassing UICollectionViewCell

除了更改背景颜色之外,UICollectionViewCell不允许进行大量自定义。 您几乎总是想要创建一个子类来轻松访问您添加的任何内容子视图。

选择File ▸ New ▸ File。 然后选择iOS ▸ Source ▸ Cocoa Touch Class。 点击Next

将新类命名为FlickrPhotoCell,使其成为UICollectionViewCell的子类。

打开Main.storyboard并选择单元格。 在Identity inspector中,将单元格的类设置为FlickrPhotoCell

打开Assistant editor,确保它显示FlickrPhotoCell.swift。 然后,按住Control键从image view拖动到类中以添加新的outlet。 将其命名为imageView

现在,您有了一个带有image view的自定义单元格类。 是时候在上面放照片了!

打开FlickrPhotosViewController.swift。 将collectionView(_:cellForItemAt :)替换为:

override func collectionView(
  _ collectionView: UICollectionView, 
  cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
  // 1
  let cell = collectionView.dequeueReusableCell(
    withReuseIdentifier: reuseIdentifier,
    for: indexPath
  ) as! FlickrPhotoCell
  // 2
  let flickrPhoto = photo(for: indexPath)
  cell.backgroundColor = .white
  // 3
  cell.imageView.image = flickrPhoto.thumbnail
    
  return cell
}

这与您之前定义的占位符方法有些不同:

  • 1) 现在返回的单元格是FlickrPhotoCell
  • 2) 您需要使用之前的便捷方法来获取代表要显示照片的FlickrPhoto
  • 3) 您用缩略图填充image view

构建并运行。 进行搜索,您将最终看到正在搜索的图片!

是的!成功了!

至此,您有了一个不错的UICollectionView示例。

在本教程中,您学习了如何创建全新的UICollectionView,指定布局,将其数据源连接到远程API,甚至创建显示图像的自定义UICollectionViewCell

您只了解了UICollectionView可以做的事情,但还有更多!查看UICollectionView: Reusable Views Selection Reordering 。通过教您如何进行以下操作,它可以增强您构建的应用程序:

  • 将自定义headers添加到collection views
  • 拖动它们即可轻松移动单元格。
  • 实现单个单元格选择以显示详细视图。
  • 实现多单元选择。

如果您对处理UICollectionView的数据源和单元格的其他好方法感兴趣,请查看:

  • UICollectionViewDiffableDataSource
  • WWDC 2020的CellRegistration

后记

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

你可能感兴趣的:(UIKit框架(五十三) —— 基于Flickr API的UICollectionView体验(一))