版本记录
版本号 | 时间 |
---|---|
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 view
是UIScrollView
子类。 - 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
设置为Search
(Search
后留一些空格,以方便填充该字段)和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 key
为6593783efea8e7f6dfc6b70bc03d2afb
注意:如果您使用示例
API key
,请记住它几乎每天都在变化。如果您连续几天进行本教程,则可能必须经常获取一个新的API密钥。因此,如果您计划在此项目上花费几天,从Flickr
获取API密钥可能会更容易。
解决了这些问题之后,就该探讨Flickr API
类了。
1. Flickr API Classes
由于本教程是关于UICollectionView
而不是Flickr API
的,因此该项目包含抽象Flickr
搜索代码的类。
Flickr
支持包括两个类和一个结构:
- FlickrSearchResults:一种结构体,用于包装搜索词和为该搜索找到的结果。
- FlickrPhoto:有关从
Flickr
检索的照片的数据:其缩略图,图像和元数据信息(例如其ID)。还有一些构建Flickr URL
的方法和一些大小计算。FlickrSearchResults
包含这些对象的数组。 - Flickr:提供一个简单的基于块
block
的API
来执行搜索并返回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中了解更多信息。
在本部分中,您将在视图控制器上实现必需的UICollectionViewDataSource
和UICollectionViewDelegateFlowLayout
,因此将设置为与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
数是来自相关FlickrSearch
的searchResults
的计数。 - 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 API
的UICollectionView
体验,感兴趣的给个赞或者关注~~~