在OkCupid我的工188bet金宝搏官网作已经学到了很多关于约会的科学。但是,任何时候我不得不去的日期我总是争先恐后地找出穿什么衣服。也许一个普通的固体T恤和卡其裤,也许深色牛仔裤和一件衬衫?。问题的关键是,组织数据,并使用正确的自定义布局可能很难呈现它。

我可能没有所有的答案,当涉及到你的约会生活。不过,我可以提供关于如何写典雅,维护和测试的一些指导UICollectionView代码(哪一种比内部造型师更便宜)。为了实现这一目标,我们将使用命令模式创建一个抽象,让我们出队,配置和处理细胞的选择,但不知道他们的具体类型。

UICollectionView是一个非常灵活的工具,在提供UIKit的。苹果将其定义为

管理有序数据项集合并使用可自定义布局显示这些数据项的对象。

只要以相同的方式表示数据,它们可以相当简单。UICollectionViewDataSourceUICollectionViewDelegate复杂性的增加,当我们注册多个UICollectionViewCell,需要以自己独特的方式进行配置。If you've been working with iOS for a while, I'm pretty sure you have witnessed horrifying ( ) implementations offunc collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: indexPath) -> UICollectionViewCell

在Ok188bet金宝搏官网Cupid上,我们不断探索书写优雅、可维护和可测试的新方法UICollectionView码。为了实现这一目标,我们将使用命令模式创建一个抽象,让我们出队,配置和处理细胞的选择,但不知道他们的具体类型。

用户提要

在我们的示例项目我们将在feed中显示用户和广告。这是目前许多开发人员都面临的一个相当常见的场景。


提要中显示的异构内容如下:

  • 属性显示常规用户快照细胞(见绕口令)。它有一个背景图像,头像和用户名的标签。
  • 特性用户将使用圆形细胞(见书呆子)。它只有一个头像和用户名的标签。
  • 广告将与我们的用户交织在一起。对于MVP,我们只会包括图片广告

让我们开始工作☺️! !

分离细胞

去耦集合视图小区的配置和数据源/委托,我们需要使用下面详述的抽象。

命令模式

这种行为的设计图案封装了一个请求到一个对象,以便去耦来自调用器的具体实施。

此模式对于简化单元配置、取消或任何其他特定请求非常关键。

{func执行(cell: UICollectionViewCell)}

考虑到UICollectionViewCell是可重用的,命令需要接受一个单元作为参数。

视图模型

CollectionViewCellViewModel包含出队列、显示和与单元格交互所需的所有信息。

struct CollectionViewCellViewModel{让标识符:字符串让大小:CGSize让命令:[CollectionViewCellCommandKey: CollectionViewCellCommand]} enum CollectionViewCellCommandKey{配置情况下取消案件选择/ /您可以添加更多的对取消选择或任何其他情况下处理交互}

关于操作的所有具体细节在举行命令,这使得我们认为模型中使用任何UICollectionView任何类型的基础模型(低耦合不是很酷吗?)这意味着视图模型是独立的任何要么行动

异构数据是条件分支语句的主要驱动因素,因为由于业务需求或仅仅是数据不兼容,每种情况都需要进行不同的处理。CollectionViewCellViewModel成为事实上的标准表示任何数据要么行为需要通过在视觉上表示UICollectionView

如何使用命令:

他们是真正好用!CollectionViewCellCommand可在任何上下文只要适当参数被传递被执行。它们通常对应于指定的方法UICollectionViewDataSource要么UICollectionViewDelegate协议。

配置命令

标识符CollectionViewCellModel用于将适当的单元从UICollectionView。视图模型中的特定命令可以通过使用CollectionViewCellCommandKey,在这种情况下.configuration键。

FUNC的CollectionView(_的CollectionView:UICollectionView,cellForItemAt indexPath:IndexPath) - > UICollectionViewCell {让视图模型=的ViewModels [indexPath.item] //细胞是基于所述标识符绑定到视图模型出队让细胞= collectionView.dequeueReusableCell(withReuseIdentifier:viewModel.identifier,用于:indexPath)viewModel.commands [.configuration] ?.执行(细胞:细胞)的返回细胞}

请注意,可选的链接安全地处理缺失的情况下,命令为了.configuration键。

选择命令

对于选择命令的过程包括上获得视图模型对于一个IndexPath然后使用适当的方法CollectionViewCellCommandKey。的.selection键对应didSelectItemAt

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: indexPath) {guard let cell = collectionView。让viewModel = viewModels[indexPath。viewModel.commands[.selection]?.perform(cell: cell)}

宏命令

在样例项目中,当用户单元格被选中时,我们push一个视图控制器。如果我们还想跟踪一个分析事件,我们可以把它放在一个命令中,但这会打破[单一责任原则](https://en.wikipedia.org/wiki/SOLID_ (object-oriented_design)。为了避免这个陷阱,我们可以创建一个宏命令包含多个命令的数组。

结构CollectionViewCellMacroCommand:CollectionViewCellCommand {私人设命令:[CollectionViewCellCommand] INIT(命令:[CollectionViewCellCommand]){self.commands =命令} FUNC执行(单元:UICollectionViewCell){commands.forEach {$ 0.perform(小区:小区)}}}

用户快照细胞

一个作为理想的抽象来创建一个视图模型我们快照细胞。在其中,我们可以指定大小标识符细胞以及任何的命令

struct UserSnapshotCollectionViewCellViewModelFactory{函数创建(用户:用户)——> CollectionViewCellViewModel{让大小= CGSize(宽度:300,身高:200)让configurationCommand = UserSnapshotCollectionViewCellConfigurationCommand(用户:用户,imageNetworkManager: imageNetworkManager())让命令:[CollectionViewCellCommandKey: CollectionViewCellCommand] = [.configuration:可以在这里添加额外的(命令键,命令)键-值对,以解决不同的场景(选择,取消选择,等等)]

要配置用户快照细胞,我们需要创建一个类附着在CollectionViewCellCommand协议。属性通常初始化命令状态需要执行请求,这意味着我们需要将所有依赖项注入到它们中。

⚠️= =当考虑潜在的保留周期注射对象进命令。记住要使用在适当的时候= =⚠️

struct UserSnapshotCollectionViewCellConfigurationCommand: CollectionViewCellCommand{/ /执行命令私人所需内部状态让用户:用户私人让imageNetworkManager: ImageNetworkManagerProtocol init(用户:用户,imageNetworkManager: ImageNetworkManagerProtocol = imageNetworkManager()){自我。user =用户自身。imageNetworkManager = imageNetworkManager} //这是魔术发生的地方执行(cell: UICollectionViewCell)UserSnapshotCollectionViewCell else {return} cell.usernameLabel。=用户文本。imageNetworkManager。请求(url: user.avatarUrl) {(image)在cell.avatarImageView中。如果让backgroundUrl = user。{_ = imageNetworkManager。请求(url: backgroundUrl){(图像)在cell.backgroundImageView。image = image}}}

配置命令请求的图像的avatarImageViewbackgroundImageView通过imageNetworkManager。它还会更新usernameLabel

我们现在可以使用工厂在UsersViewController转变用户CollectionViewCellViewModel

类UsersViewController: UIViewController, UICollectionViewDataSource, uicollectiondelegateflowlayout {@IBOutlet弱var collectionView: UICollectionView!{didSet {collectionView.delegate = self collectionView。数据Source = self } } var viewModels = [CollectionViewCellViewModel]() override func viewDidLoad() { super.viewDidLoad() setupViewModels() } func setupViewModels() { let userFactory = UserSnapshotCollectionViewCellViewModelFactory() let userViewModels = User.all.map(userFactory.create) viewModels.append(contentsOf: userViewModels) }

广告细胞

为了表示广告,我们将使用结构包含一个的contentURLclickthroughUrl

返回[广告(id: 1, contentUrl: URL(字符串:"https://cdn.okccdn.com/media/img/hub/mediakit/okcupid_darkbg.png")] !188bet金宝搏官网URL(字符串:"https://okcupid.com/home")!188bet金宝搏官网,type: .image) ] } } enum AdvertisementType { case image case video case audio }

该图像广告将在视觉上由AdvertisementCollectionViewCell,它只包含an的UIImageView。当我们点击这个单元格时,点击通过的url将被UIApplication。的SelectionCommand要求UIApplication广告来执行。

import UIKit struct AdvertisementCollectionViewCellSelectionCommand: CollectionViewCellCommand {private let application: UIApplication private let advertisement: advertising init(广告:广告,应用:UIApplication) {self。应用=应用自身。如果应用程序。canopenurl(广告。clickthroughurl){应用程序。打开(广告。clickthroughUrl, options: [:], completionHandler: nil)}}}

AdvertisementCollectionViewCellViewModelFactory负责的转型广告CollectionViewCellViewModel。就像在用户- >视图模型,工厂定义大小,标识符和支持命令

struct AdvertisementCollectionViewCellViewModelFactory{函数创建(UIApplication广告:广告、应用:)- > CollectionViewCellViewModel{让大小= CGSize(宽度:220,身高:220)让configurationCommand = AdvertisementCollectionViewCellConfigurationCommand(广告:广告,imageNetworkManager: imageNetworkManager())让selectionCommand = AdvertisementCollectionViewCellSelectionCommand(广告:广告、应用:应用程序)让命令:[CollectionViewCellCommand key: CollectionViewCellCommand] = [.configuration: configurationCommand, .selection: selectionCommand]返回collectionviewviewmodel (identifier: "AdvertisementCollectionViewCell", size: size, commands: commands)}}

将这些更改合并到UsersViewController,我们可以修改setupViewModels ()函数添加几行:

let advertisementFactory = AdvertisementCollectionViewCellViewModelFactory(){advertisementFactory地图。创建(广告:$0,应用:.shared)}视图模型。插入(内容:广告,在:视图模型。数/ 2)

结论

通过应用命令模式抽象,通过将每个组件划分为服务于a的类/类型,职责变得清晰定义单一的目的。有了这样的做一两件事,不那么弱智征税软件工程师来思考它,更是让任何人是不熟悉的代码基类。这也有利于单位列入测试,因为的低耦合模型,视图调节器层。

您可能已经注意到,添加不同类型的细胞几乎没有改变ui。的命令模式也为我们打开了创造之门可撤销的操作,如果集合视图拥有可修改输入字段的单元格,这些字段支持清除挂起的更改,则非常方便。

闭包越来越流行,函数式编程的采用比以往任何时候都要高。然而,命令是回调的面向对象替代品,它们是可以操纵和扩展的一级对象。

这就是UserViewController在应用了所有更改之后。

进口的UIKit类UsersViewController:UIViewController中,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {@IBOutlet弱VAR的CollectionView:UICollectionView!{didSet {collectionView.delegate =自collectionView.dataSource =自}}变种的ViewModels = [CollectionViewCellViewModel]()重写FUNC viewDidLoad中(){super.viewDidLoad()setupViewModels()} FUNC setupViewModels(){让userFactory = UsersCollectionViewCellViewModelFactory()让advertisementFactory = AdvertisementCollectionViewCellViewModelFactory()让userViewModels = {User.all.map userFactory.create(用户:$ 0的viewController:个体经营)} viewModels.append(contentsOf:userViewModels)让广告= {Advertisement.all.map advertisementFactory.create(广告:$ 0时,应用:.shared)} viewModels.insert(contentsOf:广告,在:viewModels.count / 2)} // MARK: -  UICollectionViewDataSource FUNC的CollectionView(_的CollectionView:UICollectionView,numberOfItemsInSection部的:int) - >内部{返回的ViewModels.Count之间} FUNC的CollectionView(_的CollectionView:UICollectionView,cellForItemAt indexPath:IndexPath) - > UICollectionViewCell {让视图模型=的ViewModels [indexPath.item] //对CELL是基于所述标识符绑定到视图模型出队让细胞= collectionView.dequeueReusableCell(withReuseIdentifier:viewModel.identifier,用于:indexPath)viewModel.commands [.configuration] ?.执行(细胞:细胞)的返回细胞} // MARK:UICollectionViewDelegate FUNC的CollectionView(_的CollectionView:UICollectionView,didSelectItemAt indexPath:indexPath){后卫令细胞= collectionView.cellForItem(在:indexPath)否则{返回}让视图模型=的ViewModels [indexPath.item] viewModel.commands [.selection] ?.执行(小区:小区)} FUNC的CollectionView(_的CollectionView:UICollectionView,didEndDisplaying细胞:UICollectionViewCell,forItemAt indexPath:IndexPath){让视图模型=的ViewModels [indexPath.item] viewModel.commands [.cancellation] ?.执行(细胞:细胞)} FUNC的CollectionView(_的CollectionView:UICollectionView,布局collectionViewLayout:UICollectionViewLayout,sizeForItemAt indexPath:IndexPath) - > CGSize {//将适当的大小是由视图模型返回的ViewModels定义[indexPath.item] .size}}

你觉得怎么样命令模式吗?你会在你的下一个项目中使用它吗?加入讨论reddit !

188bet金宝搏官网OkCupid正在为iOS系统招聘员工点击这里了解更多