在OkCupid工作,188bet金宝搏官网我学到了很多关于约会的科学知识。但是,每当我要去约会的时候,我总是在纠结该穿什么。也许是纯色t恤配卡其裤,也许是深色牛仔裤配正装衬衫?.关键是,组织数据并使用正确的自定义布局来显示它是很困难的。

在您的约会生活中可能没有所有答案。但是,我可以提供一些关于如何编写优雅,可维护和可测试的指导UICollectionView.代码 (比内部造型师便宜)。为了实现这一目标,我们将使用命令模式要创建一个将允许我们出发,配置和处理单元格的抽象而不知道其具体类型。

UICollectionView.是一个令人难以置信的灵活的工具uikit..Apple将其定义为

管理数据项的有序集合的对象,并使用可自定义的布局呈现它们。

只要您以同样的方式代表您的数据,它们就能相当简单。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.代码。为了实现这一目标,我们将使用命令模式要创建一个将允许我们出发,配置和处理单元格的抽象而不知道其具体类型。

用户喂食

在我们的样本项目,我们将在饲料中显示用户以及广告。这是许多开发商今天面对的相当常见的场景。


饲料中显示的异构内容如下:

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

让我们上班♥!

去耦细胞

要将集合视图单元格和数据源/委托进行分离,我们需要使用下面详述的抽象。

命令模式

该行为设计模式将请求封装到对象中,以便将具体实现从调用者删除。

此模式是简化我们的小区配置,取消或任何其他特定请求的关键。

协议CollectionViewCellCommand {Func执行(Cell:UICollectionViewCell)}

鉴于UICollectionViewCell.s是可重用的,命令需要将单元格作为参数。

查看模型

CollectionViewCellViewModel.包含Deueue,显示和与我们的单元格所需的所有信息。

struct collectionviewcellviewmondel {令标识符:字符串允许尺寸:cgsize let命令:[collectionViewCellCommand:collectionViewCellCommand]} Enum CollectionViewCellCommand {案例配置案例取消案例选择//您可以添加更多案例来处理取消选择或任何其他互动}

有关运营的所有具体细节都持有命令,这允许我们的视图模型用于任何UICollectionView.任何类型的潜在模型(不低耦合凉爽?)。这意味着视图模型是独立的任何班级要么行动

异构数据是条件分支陈述的主要驱动程序,因为每种情况都需要由于业务要求或仅仅是仅仅数据不兼容而不同的方式处理。CollectionViewCellViewModel.成为事实上的标准表示任何数据要么行为需要在视觉上代表UICollectionView.

如何使用命令:

他们真的很容易使用!CollectionViewCellCommand.只要传递了适当的参数,就可以在任何上下文中执行。它们通常对应于所指定的方法UICollectionViewDataSource要么UICollectionViewDelegate.协议。

配置命令

标识符A.CollectionViewCellModel.用于排出适当的细胞UICollectionView..可以通过使用a从视图模型中挑选来自视图模型的特定命令CollectionViewCellCommandKey.,在这种情况下。配置钥匙。

* * * * * * * * * * * * * * * * * * * * * * * * * * * * *//单元格基于绑定到视图模型的标识符退出队列let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.)viewModel.commands[.configuration]?.perform(cell: cell) return cell}

注意可选的链接安全地处理失踪的情况命令。配置钥匙。

选择命令

选择命令的过程包括获取viewmodel.索引路径然后使用适当的CollectionViewCellCommandKey..这.selection键对应于DidselectItemat

Func CollectionView(_ collectionView:uicollectionView,didselectItemat IndexPath:indexPath){Guard et elt = collectionView.cellfelItem(at:indexpath)els {return} viewmodel = ViewModels [IndepPath.Item] ViewModel.commands [.selection] ?.执行(细胞:细胞)}

宏观指挥

在示例项目中,我们在选择用户单元格时,我们正在按下视图控制器。如果我们还想跟踪分析事件,我们可以在一个命令中将其全部放在一起,但这会破坏[单责任原则](https://en.wikipedia.org/wiki/solid_ (Object-oriented_design).为了避免这种陷阱,我们可以创建一个宏茂包含多个命令数组。

struct collectionVellMacrocommand:CollectionViewCellCommand {Private Let命令:[CollectionViewCellCommand] init(命令:[CollectionViewCellCommand]){self.commands = commands} func执行(cell:uicollectionviewcell){command.foreach {$ 0perform(cell:cell)}}}

用户快照单元格

一种工厂作为创建视图模型的理想抽象快照单元格.在其中,我们可以指定尺寸标识符细胞以及任何命令

structuremensnapshotcollectionviewcellviewmodelfactory {func create(user:user) - > collectionViewCellViewModel(宽度:300,高度:200)Let ConfigurationCommand = UsersnapshotCollectionViewWellConfigurationCommand(用户:用户,ImagenetWorkManager:ImageNetWorkManager())Let命令:[CollectionViewCellCommandKey:CollectionViewCellCommand]= [.comfiguration:configuration:configurecommand //其他(commandKey,command)键值对可以在此处添加以解决不同的方案(选择,取消选择等)]返回CollectionViewCellViewModel(标识符:“UsersNapshotCollectionViewCell”,大小:大小,命令:命令)}}}

要配置用户快照单元格,我们需要创建一种遵守的类型CollectionViewCellCommand.协议。命令通常初始化状态需要执行请求,这意味着我们需要将所有依赖项注入它们。

⚠️==考虑到潜在的保持周期注射对象命令.记得使用虚弱的适当==⚠️

structuremensnapshotcollectionviewcellconfigurationCommand:collectionViewCellCommand:collectionViewCellCommand {//执行命令的内部状态私有允许用户:用户私有才能让用户:ImageNetWorkManagerProtocol init(user:用户,ImageNetWorkManager:ImageNetWorkManagerProtocol = ImageNetWorkManager()){self.user = user self.imagenetworkmanager}//这是魔法发生的地方func执行(cell:uicollectionviewcell){guard让cell = cell as?UsersnapshotCollectionViewCell else {return} cell.usernamelabel.text = user.username _ = ImageNetWork.request(url:user.avatarurl){(图像)在Cell.avatarImageView.Image = Image}如果让BackgroundURL = User.BackgroundURL {_ = ImageNetWorkManager.Request(URL:BackgroundURL){(图像)在Cell.BackgroundimageView.Image = Image}}}}}}}}}}}

Configuration命令请求图像avatarimageview.BackgroundimageView.通过ImageNetWorkManager..它还更新Usernamelabel.

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

Class UsiversViewController:UIVIEWController,UICollectionViewDataSource,UicollectionViewDelegateFlowLayout {@iboutlet弱var collectionView:UICollectionView!{didset {collectionView.delegate = self collectionview.dataSource = self}} var ViewModels = [CollectionViewCellViewModel]()覆盖Func ViewDidload(){super.viewdidload()setupviewmodels()} func setupviewmodels(){let useractory = usersnapshotcollectionviewcellviewmodelfactory()让userviewmodels = user.all.map(userfactory.create)ViewModels.Append(ContentsOf:UserviewModels)}

广告细胞

要代表广告,我们将使用一个塑造包含A.contentUrlA.Clickthroughurl.

STRUCT Advertisement {允许ID:INT Let ContentURL:URL Let Let ClickThroole:URL vet类型:广告类型//测试数据静态var全部:[广告] {return [通告(ID:1,contentull:url(String:'https://cdn.okccdn.com/media/img/hub/mediakit/188bet金宝搏官网okcupid_darkbg.png“)!clickthroughUrl:URL(字符串: ”https://okcupid.com/home“)!类型:图像配)]}}枚举广告型{案例图像案例视频盒音频}

图像广告将在视觉上表示广告CollectionViewCell.,它只是包括一个UiImageView..当我们点击此单元格时,会单击滚动URLUIApplication..这选择命令需要这一点UIApplication.广告执行。

导入uikit struct广告collectionViewCellSelectionCommand:CollectionViewCellCommand {私人允许应用程序:UIAPplication私人允许广告:广告init(广告:广告,应用程序:UIApplication){self.application =应用self.advertisement = delivertisement} func执行(cell:uicollectionviewcell){如果应用程序。canOpenURL(advertisement.clickthroughUrl){application.open(advertisement.clickthroughUrl,选项:[:],completionHandler:无)}}}

广告CollectionViewCellViewModelfactory.负责转型广告进入A.CollectionViewCellViewModel..就像用户- >viewmodel.案例,工厂定义了尺寸标识符并支持命令

struct advertisementcollectionviewsviewmodelacory {func create(广告:广告,应用程序:UIApplication) - > collectionViewCellViewModel {leat = cgsize(宽度:220,高度:220)Let ConfigurationCommand = AdvertisementCollectionViewCellConfigurationCommand(Advertisement,ImageNetWorkManager:ImageNetWorkManager())Let SelectionCommand = AdvertisementCollectionViewCellSelectionCommand(广告:Advertisement,Application:application)let命令:[CollectionViewCellCommandKey:CollectionViewCellCommand] = [.configuration:configurationCommand,.selection:SelectionCommand]返回CollectionViewCellViewModel(标识符:“AdvertisementCollectionViewWell”,Size:Size,Commands:Commands)}}}

将这些变化纳入了UsersViewController.,我们可以修改setupviewmodels()通过添加几行:

让advertisementfactory = divertisementCollectionViewCellViewModelvactory()让ADS = Advertisement.all.map {advertisementFactory.create(广告:$ 0,应用程序:.shared)} ViewModels.insert(Containsof:广告:AD:ViewModels.Count / 2)

结论

通过应用命令模式抽象,可以通过将每个组件分离为服务A的类/类型来清楚地定义职责单一目的.对于软件工程师来说,使用只做一件事的类可以减少他们思考这件事的脑力负担,对于那些不熟悉代码库的人来说更是如此。它还有助于包含单元测试,因为模型看法控制器层。

正如您可能已经注意到的那样,添加不同类型的细胞几乎没有修改UIViewController..这命令模式也打开了我们创造的门可撤消的操作如果集合视图拥有具有可修改的输入字段的单元格,则可以非常方便地实现,该字段支持清除待定更改。

关闭越来越受欢迎,功能规划采用高于以往。但是,命令是一个面向对象的回调的替代,它们是可以操纵和扩展的一流对象。

这是什么UserviewController.在应用所有更改后看起来像。

导入uikit class usersViewController:UIViewController,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {@iboutlet弱var collectionView:uicollectionView!{didset {collectionview.delegate = self collectionview.dataSource = self}} var ViewModels = [CollectiveViewCellViewModel]()覆盖Func ViewDidload(){super.viewdidload()setupviewmodels()} func setupviewmodels(){let useructory = userscollectionviewcellviewmodelfactory()让AdvertisementFollat​​ory = AdvertisementCollectionViewCellViewModelfactory()让UserviewModels = User.All.Map {userFactory.create(用户:$ 0,ViewController:self)} ViewModels.Append(Containsof:UserviewModels)让广告= Adverisement.all.map {AdvertisementFactory.Create(广告:$ 0,应用程序:.shared)}} ViewModels.insert(内容:广告:ADS,AT:ViewModels.Count / 2)} // Mark: -  UICollectionViewDataSource Func CollectionView(_ CollectionView:UICollectionView,NumberofiteMsinsection部分:int) - > int {return viewmodels.count} func collectionview(_ collectionView:uicollectionView,CellforiteMat IndexPath:IndexPath) - > UICollectionViewCell {exteiveModel = ViewModels [IndexPath.Item] // the cell基于与视图模型相关的标识符的标识符排列,让Cell = CollectiveView.dequeuereusablecell(forreuseidentifier:ViewModel.identifier,for:IndexPath)ViewModel.command [.configuration] ?.执行(cell:cell)返回小区} //标记:UICollectionViewDelegate Func CollectionView(_ CollectionView:uicollectionView,didselectitemat IndexPath:indexPath){Guard Let et retcleview.CellFellItem(at:IndexPath)else {return} viewmodel = ViewModels [IndexPath.Item] ViewModel.command [.selection] ?.执行(cell:cell)} func collectionview(_ collectionView:uicollectionView,didenddisplaying cell:UicollectionViewCell,foriTemat IndexPath:IndexPath){exteppath.Item] viewModel.command [.cancellation]?support(cell:cell)Func CollectionView(_ CollectionView:uicollectionView,layout collectionViewLayout:UICollectionViewLayout,sizeForitemat IndexPath:IndexPath) - > CGSIZE {//由视图模型返回ViewModels定义了适当的大小[indexpath.item] .size}}

你觉得什么?命令模式?你会在你的下一个项目中使用它吗?加入对话reddit!

188bet金宝搏官网Okcupid正在为iOS招聘点击这里了解更多